kataster 0.4.0 → 0.6.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 (62) hide show
  1. package/.claude/THIRD_PARTY_NOTICES.md +4 -0
  2. package/.claude/agents/domain-architect.md +7 -3
  3. package/.claude/agents/researcher.md +77 -0
  4. package/.claude/hooks/validate-docs.ts +15 -0
  5. package/.claude/lib/architecture.ts +3 -33
  6. package/.claude/lib/backlog.ts +2 -0
  7. package/.claude/lib/config.ts +48 -0
  8. package/.claude/lib/discovery.ts +71 -1
  9. package/.claude/lib/engineering.ts +6 -0
  10. package/.claude/lib/gaps.ts +179 -0
  11. package/.claude/lib/glob.ts +39 -0
  12. package/.claude/lib/repo.ts +143 -19
  13. package/.claude/lib/research.ts +303 -0
  14. package/.claude/lib/slices.ts +529 -0
  15. package/.claude/settings.json +1 -0
  16. package/.claude/skills/adopt/SKILL.md +106 -31
  17. package/.claude/skills/adopt/scripts/gap-register.ts +61 -0
  18. package/.claude/skills/adopt/scripts/repo-map.ts +34 -2
  19. package/.claude/skills/adopt/scripts/slice-doc.ts +117 -0
  20. package/.claude/skills/adopt/scripts/slice.ts +163 -0
  21. package/.claude/skills/adopt/templates/context-map.as-is.md +22 -0
  22. package/.claude/skills/architect/SKILL.md +6 -1
  23. package/.claude/skills/domain-model/CONTEXT-FORMAT.md +1 -1
  24. package/.claude/skills/domain-model/SKILL.md +23 -3
  25. package/.claude/skills/event-storming/SKILL.md +3 -2
  26. package/.claude/skills/event-storming/schema/events.schema.json +2 -0
  27. package/.claude/skills/event-storming/templates/events.example.yaml +6 -0
  28. package/.claude/skills/feature/SKILL.md +8 -5
  29. package/.claude/skills/grilling/SKILL.md +15 -0
  30. package/.claude/skills/implement/SKILL.md +39 -3
  31. package/.claude/skills/implement/scripts/characterize.ts +252 -0
  32. package/.claude/skills/implement/scripts/story-context.ts +64 -0
  33. package/.claude/skills/implement/scripts/verify.ts +3 -0
  34. package/.claude/skills/improve-codebase-architecture/SKILL.md +4 -1
  35. package/.claude/skills/research/SKILL.md +149 -0
  36. package/.claude/skills/research/scripts/research-check.ts +105 -0
  37. package/.claude/skills/research/templates/research.md +29 -0
  38. package/.claude/skills/review-stories/SKILL.md +15 -5
  39. package/.claude/skills/shape/SKILL.md +24 -5
  40. package/.claude/skills/tdd/SKILL.md +10 -0
  41. package/.claude/skills/user-stories/SKILL.md +21 -2
  42. package/.claude/skills/user-stories/schema/story.schema.json +4 -0
  43. package/.claude/skills/user-stories/templates/story-template.md +1 -0
  44. package/.opencode/agents/domain-architect.md +7 -3
  45. package/.opencode/agents/researcher.md +91 -0
  46. package/.opencode/commands/adopt.md +1 -1
  47. package/.opencode/commands/feature.md +1 -1
  48. package/.opencode/commands/research.md +7 -0
  49. package/.opencode/commands/shape.md +1 -1
  50. package/AGENTS.md +14 -1
  51. package/CHANGELOG.md +49 -0
  52. package/README.md +78 -19
  53. package/guide/brownfield.md +41 -12
  54. package/guide/concepts.md +12 -1
  55. package/package.json +1 -1
  56. package/src/detect.mjs +9 -0
  57. package/src/install.mjs +4 -0
  58. package/templates/docs/domain/context-map.md +5 -1
  59. package/templates/docs/domain/events.yaml +3 -0
  60. package/templates/docs/research/README.md +70 -0
  61. package/templates/docs/stories/README.md +2 -0
  62. package/templates/docs/workflow.md +54 -13
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Grill a large idea before it becomes features – frame the problem, decide where it belongs (existing or new bounded context, shared library), map what changes vs. what is new, slice it, and produce a rough cost range with confidence. Creates no stories; ends with a go / slice / spike / discover / park / no-go recommendation.
2
+ description: Grill a large idea before it becomes features – frame the problem, decide where it belongs (existing or new bounded context, shared library), research external facts before placement and cost, map what changes vs. what is new, slice it, and produce a rough cost range with confidence. Creates no stories; ends with a go / slice / spike / discover / park / no-go recommendation.
3
3
  ---
4
4
 
5
5
  Load the `shape` skill with the `skill` tool and follow it exactly. Do not restate it.
package/AGENTS.md CHANGED
@@ -20,7 +20,7 @@ Rules:
20
20
  `project.json`, `AGENTS.md` block) is not. Never make an upgrade overwrite user content.
21
21
  - Run `npm test` and `npm run typecheck --prefix .claude` before committing.
22
22
 
23
- ## Workflow contract (0.4)
23
+ ## Workflow contract (0.5)
24
24
 
25
25
  The workflow is spec-driven and mechanical, not a simulated organisation:
26
26
 
@@ -31,7 +31,20 @@ The workflow is spec-driven and mechanical, not a simulated organisation:
31
31
  scenarios. `aggregate` and `command` are required frontmatter and are cross-checked against `docs/domain/events.yaml`.
32
32
  - **Spec linter:** `.claude/skills/user-stories/scripts/lint-story.ts` (also `kataster lint-story`) is the gate.
33
33
  Green → `ready-for-human-review`; the user approves `ready`. No LLM pre-implementation review.
34
+ - **Research is evidence, not a specification.** Only the read-only `researcher` fetches web content (`/research`,
35
+ `/feature --research`); findings live in `docs/research/<topic>.md` with an expiry and are cited from a story's
36
+ `sources:`. A gated story (`risk: high` or a configured label) needs a resolvable source before
37
+ `ready-for-human-review`; only a confirmed finding becomes a testable criterion.
34
38
  - **Fast lane:** `type: chore` / `type: task`; `/implement` skips Gherkin matching and formal reviews.
39
+ - **Honest aggregates:** `status: target` (default when missing), `as-is` with evidence, or `unresolved` with an
40
+ open hotspot. A story on an `unresolved` aggregate cannot leave `review`. `/adopt` writes `as-is`/`unresolved`,
41
+ never an invented target.
42
+ - **Concept slices:** a layered repository is adopted by concept, not by package. The repo map marks generic layer
43
+ packages and clusters shared stems/tables; `slice.ts` resolves globs, routes, tables, events, clusters or terms.
44
+ - **Characterization before test-first:** in the domain lane, an untested affected path gets a characterization
45
+ test first, committed separately; a contradiction sends the story back to `review`.
46
+ - **As-is vs target:** `docs/domain/context-map.md` is the target; `/adopt` writes only
47
+ `docs/domain/context-map.as-is.md`; the gap register links both.
35
48
  - **Reviewers report, never plan:** `code-reviewer` and `acceptance-tester` create no stories. Findings are fixed in
36
49
  the story or handed back as open questions.
37
50
  - The installer prunes the retired workflow files named in `OBSOLETE` (only manifest-tracked, unmodified ones), so a
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 – 2026-11-07
4
+
5
+ - **Research as evidence.** New read-only `researcher` agent (Claude Code and OpenCode) that searches specs,
6
+ standards and exact-version official documentation and returns structured findings with source, retrieval date,
7
+ tier and confidence – no writes, no shell, no decisions. `/research <question>` runs the workflow directly;
8
+ `/feature --research` forces the step when the trigger list (auth/identity, security, payment, privacy,
9
+ accessibility, performance, new technology) would not catch it.
10
+ - **Persisted research files.** Findings live in `docs/research/<topic>.md` with `topic`, `question`, `created`,
11
+ `retrieved`, `expires` and `stack`; the main session writes them verbatim and computes `expires` from
12
+ `research.expiryMonths` (default 12, month-end clamped). `research-check.ts` validates the format and expiry
13
+ (`--topic`, `--referenced`, `--all`, `--json`) and `verify.ts` runs the referenced check as an advisory step.
14
+ - **Deterministic research gate.** Stories can cite `sources:` (an `https://…` URL or a local research file). A story
15
+ with `risk: high` or a configured gate label (`security`, `auth`, `payment`, `privacy` by default, matched exactly
16
+ or by prefix) needs at least one resolvable source before `ready-for-human-review`; `lint-story` warns for
17
+ `draft`/`review`, `story-status.ts` and the validation hook block the transition. Existing `ready`/`done` stories
18
+ stay green, and expiry is a warning, never a gate error. `sources` survives the GitHub/Jira backlog round-trip and
19
+ appears in the implementation context pack.
20
+ - **Grilling integration.** `grilling`, `feature`, `shape`, `user-stories`, `review-stories` and `implement` cite
21
+ research findings instead of inventing "best practice"; a finding that contradicts the model is a grilling
22
+ question. `shape` adds `## Evidence` to the initiative and uses findings as a cost anchor, never as the estimate.
23
+ The payload ships `templates/docs/research/README.md`, the research skill and template, and the installer allows
24
+ the research scripts.
25
+
26
+ ## 0.5.0 – 2026-11-07
27
+
28
+ - **Honest aggregate status.** Aggregates in `docs/domain/events.yaml` can be `target` (the default when the field
29
+ is missing), `as-is` (a factual brownfield boundary, with `evidence`) or `unresolved` (the boundary is unclear
30
+ and needs an open hotspot). The validator rejects `as-is` without evidence; the spec linter blocks a story on an
31
+ `unresolved` aggregate from leaving `review`. `/adopt` and `domain-model` prefer `as-is`/`unresolved` over an
32
+ invented aggregate.
33
+ - **Gap register.** `docs/domain/gap-register.md` gets one generated row per `as-is`/`unresolved` aggregate,
34
+ linking the aggregate and both context maps. The managed block is refreshed by
35
+ `node .claude/skills/adopt/scripts/gap-register.ts`; decisions live outside it.
36
+ - **Slices by concept, not by package.** The repository map marks generic layer packages (`services`,
37
+ `repositories`, `utils`) instead of proposing them as domains, and clusters files by shared concept stems and
38
+ tables. `/adopt` accepts a package path, `--paths`, `--route`, `--table`, `--event`, `--cluster` or a term;
39
+ `slice.ts` resolves the candidate files across layers and the user confirms the scope. A cross-cutting slice
40
+ documents its domain in `.claude/rules/<context>.md` with `paths:`; `<package>/AGENTS.md` stays for real
41
+ domain packages.
42
+ - **Characterization tests before test-first.** `characterize.ts --story ST-NNN` (or `--changed`/`--base`) names
43
+ the affected source files and the tests it finds. In the domain lane, an affected path with no test for the
44
+ behaviour about to change gets a characterization test first: current behaviour pinned, green on unchanged code,
45
+ committed separately, then the story tests. A discrepancy is a finding – the story goes back to `review`, never
46
+ the test to the expected value. The fast lane skips this except for an explicit refactoring.
47
+ - **As-is and target are separate.** `docs/domain/context-map.md` is the target map; `/adopt` writes only
48
+ `docs/domain/context-map.as-is.md` and links both through the gap register. `/shape` and
49
+ `/improve-codebase-architecture` write the target map; `/bootstrap`, `/feature` and `domain-architect` do the
50
+ same. Existing projects keep their `context-map.md` as the target – no file is moved or renamed.
51
+
3
52
  ## 0.4.0 – 2026-11-03
4
53
 
5
54
  - **Spec-driven instead of corporate roleplay.** The `product-owner`, `requirements-engineer` and `lead-dev`
package/README.md CHANGED
@@ -78,7 +78,8 @@ npx kataster init . --yes --name "Acme" --ui-language de --artifacts en
78
78
  skills/ the workflow skills (grilling, event-storming, domain-model, architect, backlog, …)
79
79
  skills/adopt/scripts/ repository map and per-package docs for brownfield adoption
80
80
  skills/feature/scripts/ read-only intake: story, GitHub/Jira ticket or URL → route
81
- agents/ review agents and the on-demand UX utility (Claude Code)
81
+ skills/research/ research workflow, template and the deterministic research check
82
+ agents/ review agents, the read-only researcher and the on-demand UX utility (Claude Code)
82
83
  hooks/ plain-Node guardrails and validators
83
84
  lib/ deterministic domain/story/backlog logic
84
85
  .opencode/
@@ -94,10 +95,13 @@ docs/
94
95
  product/initiatives/ proposals for large ideas from /shape (no stories)
95
96
  product/ux-guidelines.md the interface yardstick
96
97
  prototypes/ throwaway prototypes on their own branch; the default branch keeps the README
98
+ research/ evidence for cross-cutting questions, with an expiry (format in its README)
97
99
  domain/events.yaml the event storming model (empty is valid)
98
- domain/context-map.md
100
+ domain/context-map.md target context map
101
+ domain/context-map.as-is.md as-is snapshot – created by /adopt
102
+ domain/gap-register.md as-is ↔ target gaps – created by /adopt
99
103
  architecture/data-model.md
100
- architecture/repo-map.* generated by /adopt: packages, edges, adoption ranking
104
+ architecture/repo-map.* generated by /adopt: packages, roles, edges, adoption ranking, concept slices
101
105
  engineering/conventions.md how this repo builds code + the seam catalog
102
106
  stories/ the backlog – or the local mirror of GitHub/Jira
103
107
  adr/ architecture decisions
@@ -111,13 +115,14 @@ Existing `CONTEXT.md` and `docs/` are never touched.
111
115
 
112
116
  | Command | What it does |
113
117
  |---|---|
114
- | `/adopt [scope] [--stories]` | Brownfield: repository map → one slice (domain + shared library) → evidence → grilling → package docs and checks → gap register; stories only with `--stories`; repeat per slice |
118
+ | `/adopt [scope] [--stories]` | Brownfield: repository map → one concept or package slice (+ shared library) → evidence → grilling → context docs and checks → gap register; scope is a package path, `--paths`, `--route`, `--table`, `--event`, `--cluster` or a term; stories only with `--stories`; repeat per slice |
115
119
  | `/bootstrap` | Greenfield: vision → event storming → domain model → architecture → UX → foundation stories |
116
120
  | `/grill-me` | Relentless interview on a plan, decision or idea |
117
121
  | `/grill-with-docs` | The same, writing glossary and ADRs as you go |
118
122
  | `/event-storming [process]` | Big-picture or single-process event storming |
119
- | `/feature <idea \| ST-NNN \| #123 \| PROJ-123>` | New feature from an idea, an existing story or a ticket: intake and routing, grilling, focused storming, model, stories, review; bugs become a regression story; `--prototype` adds a throwaway prototype before the stories |
120
- | `/shape <idea \| ticket> [--dry-run]` | Before a large feature: frame, place (existing/new context, shared library), impact, slices, rough cost range, recommendation; creates no stories |
123
+ | `/feature <idea \| ST-NNN \| #123 \| PROJ-123>` | New feature from an idea, an existing story or a ticket: intake and routing, grilling, focused storming, model, stories, review; bugs become a regression story; `--prototype` adds a throwaway prototype before the stories; `--research` forces the research step |
124
+ | `/shape <idea \| ticket> [--dry-run]` | Before a large feature: frame, place (existing/new context, shared library), research external facts, impact, slices, rough cost range, recommendation; creates no stories |
125
+ | `/research <question>` | One cross-cutting question as evidence: check existing research and expiry, dispatch the read-only researcher, persist findings in `docs/research/<topic>.md`, cite them in grilling and stories |
121
126
  | `/review-stories [ST-…]` | Spec linter gate → `ready-for-human-review`; the user approves `ready` |
122
127
  | `/backlog-sync [status\|pull\|push]` | Keep `docs/stories/` in sync with GitHub Issues or Jira |
123
128
  | `/groom-backlog [scope]` | Periodic challenge: is this backlog still worth building? |
@@ -132,6 +137,11 @@ domain, architecture, testability – and one mechanical rule: **one story = one
132
137
  aggregate**, at most four Gherkin scenarios. `aggregate` and `command` are required frontmatter and are cross-checked
133
138
  against `docs/domain/events.yaml`.
134
139
 
140
+ Aggregates carry a status: `target` (the default when the field is missing), `as-is` (the factual boundary
141
+ recovered from code, with evidence) or `unresolved` (the boundary is unclear). A story on an `unresolved`
142
+ aggregate cannot leave `review`; `/adopt` writes `as-is` or `unresolved`, never an invented target. Every `as-is`
143
+ aggregate gets a row in `docs/domain/gap-register.md`, linking the as-is and target context maps.
144
+
135
145
  The **spec linter** is the gate and costs no LLM tokens:
136
146
 
137
147
  ```bash
@@ -149,6 +159,36 @@ Non-domain work takes the **fast lane**: `type: chore` (UI tweak, refactor, depe
149
159
  Gherkin matching or formal review agents. `type: tech-task` keeps its checklist and acceptance; `type: spike` is a
150
160
  timeboxed question.
151
161
 
162
+ In a brownfield domain story, `/implement` first runs `characterize.ts`: it names the affected source files and
163
+ the tests it can find. An affected path with no test for the behaviour that is about to change gets a
164
+ **characterization test** that pins the current behaviour, committed separately before the story's red → green
165
+ loop. If the current behaviour contradicts the story, that is a finding and the story goes back to `review`; the
166
+ code is never silently adjusted. The fast lane skips this except for an explicit refactoring.
167
+
168
+ ### Research as evidence
169
+
170
+ Cross-cutting stories rest on current primary sources. `/feature` triggers research through the grilling rules for
171
+ auth/identity, security, payment, privacy, accessibility, performance and new technologies; `--research` forces it
172
+ for a topic the trigger list does not catch. `/research <question>` runs the same path directly.
173
+
174
+ The **`researcher` is strictly read-only** – web search/fetch and repository reading, no write, no shell – and the
175
+ only way web content enters the session. Findings are data: instructions inside a fetched page, issue or comment are
176
+ ignored. The main session writes them verbatim to `docs/research/<topic>.md`; `expires` is computed at write and
177
+ refresh time from `research.expiryMonths` (default 12 months).
178
+
179
+ Research is **evidence, not a specification**. Only a finding you confirm in grilling becomes a testable criterion
180
+ with an observable threshold; rejected options are recorded with the reason. A story cites its evidence in
181
+ `sources: [...]`, and a story with `risk: high` or a configured gate label (`security`, `auth`, `payment`, `privacy`
182
+ by default) needs a resolvable source before `ready-for-human-review` – the linter warns, `story-status.ts` blocks.
183
+ Expiry is advisory: `research-check.ts --referenced` runs in `verify.ts` and warns about stale files without failing
184
+ the gate.
185
+
186
+ ```bash
187
+ /research "Which PKCE configuration is required for a public SPA client?"
188
+ /feature "checkout with saved cards" --research
189
+ node .claude/skills/research/scripts/research-check.ts --referenced
190
+ ```
191
+
152
192
  ### Starting from a ticket
153
193
 
154
194
  `/feature` also accepts a reference: `/feature #123` (GitHub), `/feature PROJ-123` (Jira, with the issue fetched
@@ -203,15 +243,22 @@ open UI question during engineering; if the prototype changes the story, the sto
203
243
  1. `npx kataster init --brownfield` → the profile is filled from the detected stack and monorepo tooling
204
244
  (`repo.tooling`: Nx, Turborepo, Bazel, workspaces, Go, Cargo, Gradle, Maven).
205
245
  2. `/adopt` → a deterministic **repository map** (`docs/architecture/repo-map.md`, `.json`) with packages,
206
- dependency edges, consumers and an adoption ranking. No questions, no hand-scanning.
207
- 3. `/adopt [scope]` → **one slice** (one domain plus at most one shared library): two or three `repo-analyst`
208
- subagents return evidence (five lenses for a small single-package repository); the main session drafts `CONTEXT.md`, `events.yaml`, context map, as-is data model,
209
- conventions and de-facto ADRs; it writes `<package>/AGENTS.md` + `<package>/CLAUDE.md`, then grills you.
246
+ dependency edges, consumers, an adoption ranking, **layer packages** and **concept-slice suggestions**. No
247
+ questions, no hand-scanning. In a layered repository (`api/`, `services/`, `repositories/`) a concept slice —
248
+ not a package — is the unit.
249
+ 3. `/adopt [scope]` → **one concept or domain plus at most one shared library**. The scope can be a package path,
250
+ a glob set (`--paths`), an entry point (`--route`, `--table`, `--event`), a suggested cluster (`--cluster`) or
251
+ a term; `slice.ts` resolves the candidate files across all layers. Two or three `repo-analyst` subagents return
252
+ evidence (five lenses for a small single-package repository); the main session drafts `CONTEXT.md`,
253
+ `events.yaml`, the as-is context map, as-is data model, conventions and de-facto ADRs. A real domain package
254
+ gets `<package>/AGENTS.md` + `<package>/CLAUDE.md`; a cross-cutting concept slice gets
255
+ `.claude/rules/<context>.md` with `paths:` frontmatter. Then it grills you.
210
256
  4. The safety net turns confirmed rules into checks (types, lint/boundary, tests, hooks) – report-only with a
211
257
  baseline first, so a brownfield repository does not start red.
212
- 5. The gap analysis records **contradictions, missing behaviour and dead code** in the adoption report. It creates
213
- **no stories by default** – what already works is documented, not re-storied. `/adopt <scope> --stories` turns
214
- the selected gaps into a backlog when you want one.
258
+ 5. The gap analysis records **contradictions, missing behaviour and dead code** in the adoption report and the
259
+ **gap register** (`docs/domain/gap-register.md`): one row per `as-is`/`unresolved` aggregate, linking the
260
+ as-is boundary to the target model. It creates **no stories by default** – what already works is documented,
261
+ not re-storied. `/adopt <scope> --stories` turns the selected gaps into a backlog when you want one.
215
262
  6. `/adopt <next path>` for the next slice; `/adopt <scope> --stories`, `/review-stories`, then `/implement`.
216
263
 
217
264
  Adopt in slices: one bounded context completely beats the whole repository shallowly. The root `AGENTS.md` stays
@@ -221,9 +268,11 @@ free of domain; the package docs are loaded only when someone works in that pack
221
268
 
222
269
  The installer detects the build tool (Nx, Turborepo, Bazel, pnpm/npm/yarn workspaces, Go, Cargo, Gradle, Maven)
223
270
  and stores it in `.claude/project.json` → `repo.tooling`. `/adopt` builds the repository map once, then adopts
224
- **one domain plus at most one shared library per session**. Every adopted package gets an `AGENTS.md` with its
225
- purpose, entities or public API, interfaces and pitfalls – plus a one-line `CLAUDE.md` so Claude Code loads it on
226
- demand; OpenCode loads the nested `AGENTS.md` itself. The root `AGENTS.md` stays short and free of domain.
271
+ **one concept or domain plus at most one shared library per session**. Every adopted domain package gets an
272
+ `AGENTS.md` with its purpose, entities or public API, interfaces and pitfalls – plus a one-line `CLAUDE.md` so
273
+ Claude Code loads it on demand; OpenCode loads the nested `AGENTS.md` itself. A cross-cutting concept slice gets
274
+ `.claude/rules/<context>.md` with `paths:` instead, loaded by both harnesses when a matching file is touched. The
275
+ root `AGENTS.md` stays short and free of domain.
227
276
 
228
277
  Commands use the detected tool instead of assuming a single package: `nx affected -t test`,
229
278
  `turbo run test --filter=<package>`, `bazel test //<package>/...`, `pnpm --filter <package> test`. The map also
@@ -270,6 +319,10 @@ node .claude/skills/implement/scripts/affected.ts --base origin/main
270
319
  "statusMap": { "draft": "To Do", "ready": "Ready", "done": "Done" },
271
320
  "priorityMap": { "must": "Highest", "should": "High" }
272
321
  }
322
+ },
323
+ "research": {
324
+ "expiryMonths": 12,
325
+ "gate": { "risk": ["high"], "labels": ["security", "auth", "payment", "privacy"] }
273
326
  }
274
327
  }
275
328
  ```
@@ -327,7 +380,7 @@ For Jira, `jira-plan.ts` prints the exact MCP calls, `jira-record.ts` writes the
327
380
  node .claude/skills/adopt/scripts/boundary-check.ts --update-baseline
328
381
  ```
329
382
  - **`verify.ts`** is the one gate: project checks, artifact validity, scenario ↔ test traceability, domain IDs in
330
- the code, glossary language.
383
+ the code, glossary language. Referenced research files are checked for expiry as an advisory step.
331
384
  - **Review agents** never write code or stories: `code-reviewer` and `acceptance-tester` report findings, the
332
385
  acceptance verdict decides whether a domain story is `done`, and a finding is fixed in the story or handed back
333
386
  as an open question. `ux-designer` is an on-demand utility (`ui: true`), never a gate.
@@ -343,6 +396,11 @@ npx kataster doctor
343
396
  overwrite files you changed locally – it lists them instead. `--force` overwrites and writes a backup to
344
397
  `.kataster/backup/`.
345
398
 
399
+ Project content includes `docs/workflow.md` and the context maps. An upgrade does not rewrite them; compare
400
+ `docs/workflow.md` with `templates/docs/workflow.md` if you want the new rules in an existing project. The schema
401
+ changes are additive: an aggregate without `status` is `target`, a missing `context-map.as-is.md` or gap register is
402
+ not an error, and the new checks are advisory until the artifacts exist.
403
+
346
404
  ## Design notes
347
405
 
348
406
  - **`.claude/` is the source of truth.** OpenCode and Claude Code share skills, agents and hooks; `.opencode/` is an
@@ -372,8 +430,9 @@ Kataster stands on other people's work:
372
430
  - **Matt Pocock** – [skills](https://github.com/mattpocock/skills), [aihero.dev](https://www.aihero.dev). The grilling
373
431
  interview (`grilling`, `grill-me`, `grill-with-docs`), test-driven development (`tdd`), the deep-module vocabulary
374
432
  (`codebase-design`), the architecture survey (`improve-codebase-architecture`), the throwaway prototype
375
- (`prototype`), the retro, the domain-modeling discipline and `writing-for-agents` come from his skill repository –
376
- partly adapted, partly copied verbatim under MIT. The per-file mapping and the license text are in
433
+ (`prototype`), the research skill (adapted: read-only researcher, source tiers, expiry, grilling integration), the
434
+ retro, the domain-modeling discipline and `writing-for-agents` come from his skill repository – partly adapted,
435
+ partly copied verbatim under MIT. The per-file mapping and the license text are in
377
436
  [.claude/THIRD_PARTY_NOTICES.md](.claude/THIRD_PARTY_NOTICES.md).
378
437
  - **John Ousterhout**, *A Philosophy of Software Design* – "Design It Twice" and the depth, seam and deletion-test
379
438
  vocabulary.
@@ -9,6 +9,9 @@ npx kataster init --brownfield
9
9
  /adopt # map the repository, then choose a slice
10
10
  /adopt apps/billing # adopt one package
11
11
  /adopt libs/shared-kernel # adopt a shared library
12
+ /adopt --cluster order # adopt a concept across api/, services/, repositories/
13
+ /adopt --table orders # start from a table and resolve the layers behind it
14
+ /adopt --route "POST /orders" # start from an entry point
12
15
  ```
13
16
 
14
17
  ## Adopt in slices, not in one pass
@@ -20,15 +23,21 @@ A monorepo with eighty packages cannot be understood in one session. `/adopt` th
20
23
  2. **One slice** – one domain plus at most one shared library. Deep evidence, grilling, package docs, checks.
21
24
  Then the next slice in a later session.
22
25
 
23
- The map is generated from manifests and git history:
26
+ The map is generated from manifests, source files and git history:
24
27
 
25
- - `docs/architecture/repo-map.json` – packages, paths, kinds, internal dependencies, consumers, adoption ranking
28
+ - `docs/architecture/repo-map.json` – packages, paths, kinds, roles, internal dependencies, consumers, adoption
29
+ ranking, concept-slice suggestions
26
30
  - `docs/architecture/repo-map.md` – the same as a readable map, plus dependency edges and a suggested first slice
27
31
  - the **repository block in `AGENTS.md`** – tooling, layout, commands, adopted slices
28
32
  - `.claude/project.json` → `repo.tooling` – Nx, Turborepo, Bazel, pnpm/npm/yarn workspaces, Go, Cargo, Gradle, Maven
29
33
 
30
34
  The map is facts, not domain. It says where code lives, never what a word means.
31
35
 
36
+ In a layered repository (`api/`, `services/`, `repositories/`, `utils/`) a package is not a domain. The map marks
37
+ generic packages as **layer packages** and clusters files by shared concept stems and tables. A domain such as
38
+ “order” shows up as an `order` slice across all layers; `slice.ts` resolves the candidate files from a cluster, a
39
+ table, a route, an event ID, a glob or a term. The resolver is evidence – you confirm the scope.
40
+
32
41
  ## What `/adopt` does
33
42
 
34
43
  ### 1. Chooses the slice with you
@@ -56,18 +65,26 @@ A monorepo gets three lenses:
56
65
  A small repository without monorepo tooling gets the five classic lenses: structure and stack, domain language,
57
66
  data and integrations, behaviour and tests, history and decisions.
58
67
 
68
+ For a concept slice, the analysts receive the confirmed file list from `slice.ts`, not only a package path. They
69
+ report facts inside that scope; widening the slice is the user's decision, not theirs.
70
+
59
71
  Each analyst returns facts with `path:line`, candidate vocabulary, candidate events/commands/read models,
60
72
  contradictions, and questions with a recommended answer. Confidence is `high`, `medium` or `low`.
61
73
 
62
74
  ### 4. Drafts the slice
63
75
 
64
76
  - `CONTEXT.md` – the slice's terms, competing spellings under `_Avoid_`
65
- - `docs/domain/events.yaml` – 10–25 candidate events, commands, actors, read models, flows, hotspots
66
- - `docs/domain/context-map.md`, `docs/architecture/data-model.md` (as-is)
77
+ - `docs/domain/events.yaml` – 10–25 candidate events, commands, actors, read models, flows, hotspots. Each
78
+ aggregate is `as-is` with evidence (table, service method, path) or `unresolved` with an open hotspot – never
79
+ an invented `target`
80
+ - `docs/domain/context-map.as-is.md` (recovered boundaries, with evidence) and
81
+ `docs/architecture/data-model.md` (as-is). The target map `docs/domain/context-map.md` is read, never written
82
+ here; if it does not exist yet, the gap register says “target not decided”.
67
83
  - `docs/engineering/conventions.md` – how the code actually works today, including the monorepo tooling
68
84
  - `docs/adr/` – de-facto decisions worth recording (`status: proposed`)
69
- - **package docs** – `<package>/AGENTS.md` plus `<package>/CLAUDE.md`, written by
70
- `node .claude/skills/adopt/scripts/package-doc.ts <path>`
85
+ - **domain docs, by scope** – a real domain package gets `<package>/AGENTS.md` plus `<package>/CLAUDE.md` from
86
+ `package-doc.ts <path>`; a cross-cutting concept slice gets `.claude/rules/<context>.md` with `paths:` from
87
+ `slice-doc.ts <slug> --context BC-… --paths "…"`. One mechanism per scope, never both.
71
88
 
72
89
  Everything uncertain is marked. The draft is a proposal, not a verdict.
73
90
 
@@ -92,19 +109,25 @@ baseline of existing violations; only new violations fail. A hook that is red on
92
109
  `/adopt` may add characterization tests and tooling. It never changes production code, and it never writes a test
93
110
  that fails on existing behaviour – that would be a story.
94
111
 
112
+ The same net carries into `/implement`: `characterize.ts --story ST-NNN` names the affected source files and the
113
+ tests it can find. A path with no test for the behaviour that is about to change gets a characterization test
114
+ first – current behaviour pinned, green on unchanged code, committed separately, then the story's red → green loop.
115
+ If the current behaviour contradicts the story, the story goes back to `review`; the test is not bent to fit.
116
+
95
117
  ### 7. Records the gap – stories only on request
96
118
 
97
119
  | Class | Default |
98
120
  |---|---|
99
- | Exists and is intended | Documentation (data model, context map) – no action |
121
+ | Exists and is intended | Documentation (data model, as-is context map) – no action |
100
122
  | Exists but contradicts the model | Candidate in the gap register |
101
123
  | Missing | Candidate in the gap register |
102
124
  | Dead | Candidate: removal or an explicit "leave it" |
103
125
 
104
- **The first adoption pass creates no stories.** It records the gap in `docs/domain/adoption-report.md` (and open
105
- questions in `OPEN_QUESTIONS.md`), so the model can be reviewed before anyone commits to work. Only
106
- `/adopt <scope> --stories` (or an explicit "turn these into stories") turns the selected gaps into stories in the
107
- main session (spec engineer, spec linter gate).
126
+ **The first adoption pass creates no stories.** It records the gap in `docs/domain/adoption-report.md` and
127
+ `docs/domain/gap-register.md` (one generated row per `as-is`/`unresolved` aggregate, with the as-is and target
128
+ context links), and open questions in `OPEN_QUESTIONS.md`, so the model can be reviewed before anyone commits to
129
+ work. Only `/adopt <scope> --stories` (or an explicit "turn these into stories") turns the selected gaps into
130
+ stories in the main session (spec engineer, spec linter gate).
108
131
 
109
132
  **Behaviour that already works does not become a story.** That turns the backlog into a description of the past.
110
133
 
@@ -114,11 +137,14 @@ main session (spec engineer, spec linter gate).
114
137
  and checks, gaps, what was deliberately left alone, open hotspots, next slice.
115
138
 
116
139
  ```bash
140
+ # a real domain package
117
141
  node .claude/skills/adopt/scripts/repo-map.ts --adopt apps/billing
142
+ # a concept slice across layers
143
+ node .claude/skills/adopt/scripts/repo-map.ts --adopt-slice order
118
144
  ```
119
145
 
120
146
  The next session sees the progress in `AGENTS.md` and the map, and starts the next slice with
121
- `/adopt <path>`.
147
+ `/adopt <scope>`.
122
148
 
123
149
  ## What lives where
124
150
 
@@ -134,6 +160,9 @@ The next session sees the progress in `AGENTS.md` and the map, and starts the ne
134
160
  | `docs/engineering/checks.yaml` | on check | what runs at edit time and at Stop/CI |
135
161
  | `docs/architecture/repo-map.md` | on demand | generated map: packages, edges, consumers, ranking |
136
162
  | `CONTEXT.md` | every session | the domain language |
163
+ | `docs/domain/context-map.md` | on demand | **target** contexts and relationships |
164
+ | `docs/domain/context-map.as-is.md` | on demand | **as-is** snapshot recovered by `/adopt`, with evidence |
165
+ | `docs/domain/gap-register.md` | on demand | generated as-is ↔ target rows and the user's decisions |
137
166
 
138
167
  Nested `AGENTS.md` is loaded on demand by OpenCode; Claude Code loads the nested `CLAUDE.md` when it reads or
139
168
  edits a file there. That is why every package gets both files.
package/guide/concepts.md CHANGED
@@ -14,6 +14,14 @@ Two rules make it work:
14
14
  user something you could find yourself.
15
15
  - **Do not act until the frontier is empty** and the user confirms a shared understanding.
16
16
 
17
+ When a frontier question rests on an **external fact** – a standard, a library capability, a regulatory requirement,
18
+ a security or privacy rule – the agent researches it before the round. The `researcher` is read-only and returns
19
+ findings with source, tier, retrieval date and confidence; the main session writes them to
20
+ `docs/research/<topic>.md` with an expiry. The finding is evidence, not a decision: the recommendation cites it
21
+ (`➡️ … (RFC 9700 §2.1.1; F1 in docs/research/oauth-pkce.md)`), and only a confirmed finding becomes a testable
22
+ criterion. A trigger is not a mandate – if the model, an ADR or a valid research file already answers it, nothing
23
+ runs.
24
+
17
25
  `/grill-me` is the pure form. `/grill-with-docs` adds the glossary and ADRs. Every discovery command embeds it.
18
26
 
19
27
  ## The artifacts
@@ -22,13 +30,16 @@ Two rules make it work:
22
30
  |---|---|---|
23
31
  | `CONTEXT.md` | Glossary – the ubiquitous language. One canonical word per concept, alternatives under `_Avoid_` | user, maintained by the agent |
24
32
  | `docs/domain/events.yaml` | Event storming model: actors, contexts, aggregates, events, commands, policies, read models, hotspots, flows | user, written by the agent |
25
- | `docs/domain/context-map.md` | Bounded contexts and their relationships (DDD patterns) | domain-architect |
33
+ | `docs/domain/context-map.md` | **Target** bounded contexts and their relationships (DDD patterns) | domain-architect, `/bootstrap`, `/shape`, `/feature`, `/improve-codebase-architecture` |
34
+ | `docs/domain/context-map.as-is.md` | **As-is** snapshot recovered from code, with evidence | `/adopt` only |
35
+ | `docs/domain/gap-register.md` | Generated rows for every `as-is`/`unresolved` aggregate, linking as-is and target; decisions outside the block | `/adopt`, user |
26
36
  | `docs/architecture/data-model.md` | Logical, stack-neutral model per aggregate; lifecycle and ER diagrams | domain-architect |
27
37
  | `docs/engineering/conventions.md` | How code is built here, including the seam catalog | user |
28
38
  | `docs/adr/` | Architecture decisions. `proposed` until the user accepts; accepted ones are superseded, never edited | user |
29
39
  | `docs/stories/` | The backlog. One Markdown file per story; `BACKLOG.md` is generated. With a tracker, this is the local mirror | main session as spec engineer; spec linter; `/backlog-sync` with a tracker |
30
40
  | `docs/product/vision.md` | Problem, user groups, goals, non-goals, success criteria | user |
31
41
  | `docs/product/ux-guidelines.md` | The interface yardstick, as numbered rules with date and reason | ux-designer, approved by user |
42
+ | `docs/research/<topic>.md` | Evidence for a cross-cutting question: findings with source, tier, confidence and an expiry. Evidence, not a specification | researcher (read-only); main session writes; user confirms findings |
32
43
  | `docs/prototypes/<slug>/` | Throwaway prototypes on their own branch; only the README stays on the default branch | ux-designer; verdict in the story and the guidelines |
33
44
 
34
45
  ## Events, commands, aggregates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kataster",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Agent workflow for domain-driven discovery and delivery – drop it into a greenfield or brownfield repository and build the domain by grilling.",
5
5
  "keywords": [
6
6
  "agents",
package/src/detect.mjs CHANGED
@@ -224,6 +224,15 @@ export function buildProfile(root, { name, kind, uiLanguage, artifactLanguage, d
224
224
  },
225
225
  },
226
226
  },
227
+ // Research files expire. The gate asks for `sources:` on risky work before it reaches ready-for-human-review.
228
+ // Keep the defaults in sync with .claude/lib/config.ts (DEFAULT_RESEARCH_CONFIG).
229
+ research: {
230
+ expiryMonths: 12,
231
+ gate: {
232
+ risk: ["high"],
233
+ labels: ["security", "auth", "payment", "privacy"],
234
+ },
235
+ },
227
236
  setup: {
228
237
  envFile: "",
229
238
  envHint: "",
package/src/install.mjs CHANGED
@@ -59,6 +59,7 @@ const ALLOW = [
59
59
  "Bash(node .claude/skills/event-storming/scripts/validate-events.ts)",
60
60
  "Bash(node .claude/skills/event-storming/scripts/render-event-storming.ts)",
61
61
  "Bash(node .claude/skills/user-stories/scripts/*)",
62
+ "Bash(node .claude/skills/research/scripts/*)",
62
63
  "Bash(node .claude/skills/adopt/scripts/*)",
63
64
  "Bash(node .claude/skills/backlog/scripts/*)",
64
65
  "Bash(node .claude/skills/implement/scripts/*)",
@@ -433,6 +434,9 @@ This repository runs the Kataster workflow. Discovery lives in \`.claude/skills/
433
434
  local mirror and the tracker is the source of truth – sync with \`/backlog-sync\`; \`BACKLOG.md\` stays generated.
434
435
  - **Map:** \`docs/workflow.md\` – sources of truth, commands, phases, rules. Read it when unsure where something
435
436
  belongs.
437
+ - **Research:** cross-cutting questions go through the read-only \`researcher\` (\`/research\`,
438
+ \`/feature --research\`); findings live in \`docs/research/<topic>.md\` with an expiry and are cited from a story's
439
+ \`sources:\`. Evidence, not a specification.
436
440
  - **Repo map (brownfield):** \`docs/architecture/repo-map.md\` – packages, dependency edges, consumers, adopted
437
441
  slices. A package's \`AGENTS.md\` is loaded only when you work there; the root file stays free of domain.
438
442
  - **Start:** greenfield → \`/bootstrap\`; existing code → \`/adopt\` (in slices, not the whole monorepo).
@@ -1,4 +1,8 @@
1
- # Context map – {{project}}
1
+ # Target context map – {{project}}
2
+
3
+ > Where the domain is going, not what the code does today. The as-is map is `context-map.as-is.md` and is
4
+ > written only by `/adopt`; the gap register links both sides. `/bootstrap`, `/shape`, `/feature`,
5
+ > `/event-storming`, `domain-architect` and `/improve-codebase-architecture` write this file.
2
6
 
3
7
  <!--
4
8
  One section per bounded context: purpose, core terms, owner role. Relationships as a Mermaid flowchart
@@ -6,6 +6,9 @@
6
6
  #
7
7
  # Written by /bootstrap, /adopt, /event-storming and /feature. Validated and rendered to
8
8
  # docs/domain/event-storming.md after every write. An empty model is valid: it grows during discovery.
9
+ #
10
+ # Aggregates carry an optional status: target (default when missing), as-is (factual boundary, needs evidence),
11
+ # or unresolved (boundary unclear, needs an open hotspot and grilling; no story on it can be ready).
9
12
 
10
13
  version: 1
11
14
  domain: {{project}}
@@ -0,0 +1,70 @@
1
+ # Research
2
+
3
+ Evidence for cross-cutting questions: authentication and identity, security, payment, privacy and data protection,
4
+ accessibility, performance, or a new library or technology. The files here are **evidence, not a specification** –
5
+ the user decides in grilling, and only a confirmed finding becomes a testable acceptance criterion.
6
+
7
+ One file per topic: `docs/research/<topic>.md`, lowercase kebab-case, no date in the file name. Refresh in place;
8
+ if the question changes materially, use a new topic.
9
+
10
+ ## Format
11
+
12
+ ```markdown
13
+ ---
14
+ topic: oauth-pkce-spa
15
+ question: "Which PKCE configuration is required for a public SPA client?"
16
+ created: 2026-11-07
17
+ retrieved: 2026-11-07
18
+ expires: 2027-11-07
19
+ stack: ["next@15.2.0", "node@22.18.0"]
20
+ ---
21
+
22
+ # Research: …
23
+ ## Question
24
+ ## Findings
25
+ ### F1 – …
26
+ - **Statement:** …
27
+ - **Source:** <title> – <url> (<section>)
28
+ - **Retrieved:** YYYY-MM-DD
29
+ - **Tier:** 1 | 2 | 3
30
+ - **Confidence:** high | medium | low
31
+ - **Domain-model impact:** none | conflicts with `<term>` / `AGG-…` / ADR-… – what
32
+ ## Conflicts, caveats and gaps
33
+ ## Open questions for the grilling
34
+ ```
35
+
36
+ Source tiers: **1** specs, standards and authorities (RFC, W3C/WAI, OWASP, NIST, ISO, GDPR/EDPB, PCI SSC, vendor
37
+ security advisories); **2** official documentation or source of the exact version in the lockfile; **3** everything
38
+ else – a hint, never the sole basis for a criterion.
39
+
40
+ ## Expiry
41
+
42
+ `expires` is computed when the file is written or refreshed: `retrieved + research.expiryMonths` from
43
+ `.claude/project.json` (default 12 months, month-end clamped). It is not recomputed on every read. A stale file is
44
+ refreshed, not silently reused.
45
+
46
+ Check a file, every referenced file, or the whole directory:
47
+
48
+ ```bash
49
+ node .claude/skills/research/scripts/research-check.ts --topic <slug>
50
+ node .claude/skills/research/scripts/research-check.ts --referenced
51
+ node .claude/skills/research/scripts/research-check.ts --all
52
+ ```
53
+
54
+ Statuses are `VALID`, `EXPIRED`, `MISSING` and `INVALID`; exit 1 means at least one file needs a refresh or repair.
55
+ `verify.ts` runs the referenced check as an advisory step – an expired file warns, it never fails the gate.
56
+
57
+ ## How research enters the work
58
+
59
+ - `/research <question>` runs the read-only `researcher`; the main session writes its findings verbatim into this
60
+ directory. `/feature --research` forces the step for a topic the trigger list does not catch.
61
+ - The story lists the file under `sources:`. A story with `risk: high` or one of the configured gate labels in
62
+ `.claude/project.json` (default `security`, `auth`, `payment`, `privacy`) needs at least one resolvable source
63
+ before `ready-for-human-review`; the linter warns, `story-status.ts` blocks.
64
+ - Adopted findings become testable criteria with an observable threshold. Rejected options are recorded with the
65
+ reason in the story, ADR or initiative – this file stays evidence, not a decision log.
66
+ - A finding that contradicts `CONTEXT.md`, `events.yaml`, an accepted ADR or the UX guidelines is a grilling
67
+ question, never a silent rewrite of the model.
68
+
69
+ The full workflow is `.claude/skills/research/SKILL.md`; the format template is
70
+ `.claude/skills/research/templates/research.md`.
@@ -11,6 +11,8 @@ Workflow: `draft → review → ready-for-human-review → ready → in-progress
11
11
  Gherkin `Given/When/Then` and the scenario limit – deterministically, no LLM.
12
12
  - Green → `ready-for-human-review`; **the user approves `ready`**. `/implement` only picks up `ready`.
13
13
  - **1-command rule:** one `type: domain` story = one command + its events on one aggregate, at most 4 scenarios.
14
+ - An aggregate with `status: unresolved` blocks the story at `ready-for-human-review`; settle the boundary and
15
+ its hotspot in grilling first. A missing status means `target`; `as-is` is allowed.
14
16
  - **Fast lane:** `type: chore` or `type: task` for UI tweaks, refactorings, dependencies and setup – no Gherkin
15
17
  matching, no formal reviews. `type: tech-task` keeps its checklist; `type: spike` is a timeboxed question.
16
18
  - `ui: true` marks a story with a frontend part; it triggers the on-demand UX utility, never a gate.