kataster 0.0.0-stage → 0.2.1

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 (162) hide show
  1. package/.claude/agents/acceptance-tester.md +53 -0
  2. package/.claude/agents/code-reviewer.md +56 -0
  3. package/.claude/agents/domain-architect.md +40 -0
  4. package/.claude/agents/lead-dev.md +50 -0
  5. package/.claude/agents/product-owner.md +39 -0
  6. package/.claude/agents/repo-analyst.md +78 -0
  7. package/.claude/agents/requirements-engineer.md +44 -0
  8. package/.claude/agents/ux-designer.md +64 -0
  9. package/.claude/hooks/bash-guard.ts +92 -0
  10. package/.claude/hooks/code-check.ts +37 -0
  11. package/.claude/hooks/path-guard.ts +34 -0
  12. package/.claude/hooks/path-rules.ts +66 -0
  13. package/.claude/hooks/project-check.ts +108 -0
  14. package/.claude/hooks/protect-files.ts +55 -0
  15. package/.claude/hooks/session-start.ts +213 -0
  16. package/.claude/hooks/stop-gate.ts +56 -0
  17. package/.claude/hooks/validate-docs.ts +58 -0
  18. package/.claude/lib/architecture.ts +736 -0
  19. package/.claude/lib/backlog-github.ts +454 -0
  20. package/.claude/lib/backlog-jira.ts +347 -0
  21. package/.claude/lib/backlog-sync.ts +42 -0
  22. package/.claude/lib/backlog.ts +396 -0
  23. package/.claude/lib/bazel.ts +212 -0
  24. package/.claude/lib/config.ts +122 -0
  25. package/.claude/lib/discovery.ts +821 -0
  26. package/.claude/lib/engineering.ts +406 -0
  27. package/.claude/lib/nx.ts +129 -0
  28. package/.claude/lib/repo.ts +1194 -0
  29. package/.claude/lib/toolgraph.ts +74 -0
  30. package/.claude/lib/turbo.ts +166 -0
  31. package/.claude/package-lock.json +427 -0
  32. package/.claude/package.json +21 -0
  33. package/.claude/settings.json +82 -0
  34. package/.claude/skills/adopt/SKILL.md +283 -0
  35. package/.claude/skills/adopt/scripts/boundary-check.ts +156 -0
  36. package/.claude/skills/adopt/scripts/package-doc.ts +71 -0
  37. package/.claude/skills/adopt/scripts/repo-map.ts +123 -0
  38. package/.claude/skills/adopt/scripts/rule-doc.ts +80 -0
  39. package/.claude/skills/architect/ADR-FORMAT.md +97 -0
  40. package/.claude/skills/architect/SKILL.md +64 -0
  41. package/.claude/skills/architect/TECH-STACK.md +21 -0
  42. package/.claude/skills/backlog/SKILL.md +198 -0
  43. package/.claude/skills/backlog/scripts/backlog-pull.ts +45 -0
  44. package/.claude/skills/backlog/scripts/backlog-push.ts +38 -0
  45. package/.claude/skills/backlog/scripts/backlog-status.ts +32 -0
  46. package/.claude/skills/backlog/scripts/jira-import.ts +40 -0
  47. package/.claude/skills/backlog/scripts/jira-plan.ts +26 -0
  48. package/.claude/skills/backlog/scripts/jira-record.ts +41 -0
  49. package/.claude/skills/bootstrap/SKILL.md +119 -0
  50. package/.claude/skills/codebase-design/DEEPENING.md +37 -0
  51. package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  52. package/.claude/skills/codebase-design/SKILL.md +114 -0
  53. package/.claude/skills/domain-model/CONTEXT-FORMAT.md +47 -0
  54. package/.claude/skills/domain-model/SKILL.md +64 -0
  55. package/.claude/skills/domain-model/templates/data-model.md +29 -0
  56. package/.claude/skills/engineering-conventions/SKILL.md +31 -0
  57. package/.claude/skills/event-storming/SKILL.md +44 -0
  58. package/.claude/skills/event-storming/schema/events.schema.json +124 -0
  59. package/.claude/skills/event-storming/scripts/render-event-storming.ts +11 -0
  60. package/.claude/skills/event-storming/scripts/validate-events.ts +11 -0
  61. package/.claude/skills/event-storming/templates/events.example.yaml +91 -0
  62. package/.claude/skills/feature/SKILL.md +86 -0
  63. package/.claude/skills/feature/scripts/intake.ts +356 -0
  64. package/.claude/skills/grill-me/SKILL.md +7 -0
  65. package/.claude/skills/grill-with-docs/SKILL.md +7 -0
  66. package/.claude/skills/grilling/SKILL.md +30 -0
  67. package/.claude/skills/groom-backlog/SKILL.md +93 -0
  68. package/.claude/skills/groom-backlog/scripts/backlog-health.ts +238 -0
  69. package/.claude/skills/implement/SKILL.md +129 -0
  70. package/.claude/skills/implement/scripts/affected.ts +245 -0
  71. package/.claude/skills/implement/scripts/check-commands.ts +37 -0
  72. package/.claude/skills/implement/scripts/check-language.ts +15 -0
  73. package/.claude/skills/implement/scripts/check-scenarios.ts +59 -0
  74. package/.claude/skills/implement/scripts/next-story.ts +23 -0
  75. package/.claude/skills/implement/scripts/story-context.ts +135 -0
  76. package/.claude/skills/implement/scripts/story-status.ts +47 -0
  77. package/.claude/skills/implement/scripts/verify.ts +92 -0
  78. package/.claude/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  79. package/.claude/skills/improve-codebase-architecture/SKILL.md +71 -0
  80. package/.claude/skills/retro/SKILL.md +53 -0
  81. package/.claude/skills/review-stories/SKILL.md +31 -0
  82. package/.claude/skills/shape/SKILL.md +193 -0
  83. package/.claude/skills/tdd/SKILL.md +38 -0
  84. package/.claude/skills/tdd/mocking.md +59 -0
  85. package/.claude/skills/tdd/tests.md +77 -0
  86. package/.claude/skills/user-stories/SKILL.md +138 -0
  87. package/.claude/skills/user-stories/schema/story.schema.json +39 -0
  88. package/.claude/skills/user-stories/scripts/render-backlog.ts +4 -0
  89. package/.claude/skills/user-stories/scripts/validate-stories.ts +13 -0
  90. package/.claude/skills/user-stories/templates/story-template.md +38 -0
  91. package/.claude/skills/ux-design/SKILL.md +107 -0
  92. package/.claude/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
  93. package/.claude/skills/writing-for-agents/SKILL.md +81 -0
  94. package/.claude/tsconfig.json +15 -0
  95. package/.opencode/agents/acceptance-tester.md +64 -0
  96. package/.opencode/agents/code-reviewer.md +69 -0
  97. package/.opencode/agents/domain-architect.md +59 -0
  98. package/.opencode/agents/lead-dev.md +67 -0
  99. package/.opencode/agents/product-owner.md +59 -0
  100. package/.opencode/agents/repo-analyst.md +86 -0
  101. package/.opencode/agents/requirements-engineer.md +58 -0
  102. package/.opencode/agents/ux-designer.md +83 -0
  103. package/.opencode/commands/adopt.md +7 -0
  104. package/.opencode/commands/backlog-sync.md +7 -0
  105. package/.opencode/commands/bootstrap.md +7 -0
  106. package/.opencode/commands/event-storming.md +7 -0
  107. package/.opencode/commands/feature.md +7 -0
  108. package/.opencode/commands/grill-me.md +7 -0
  109. package/.opencode/commands/grill-with-docs.md +7 -0
  110. package/.opencode/commands/groom-backlog.md +7 -0
  111. package/.opencode/commands/implement.md +7 -0
  112. package/.opencode/commands/improve-codebase-architecture.md +7 -0
  113. package/.opencode/commands/retro.md +7 -0
  114. package/.opencode/commands/review-stories.md +7 -0
  115. package/.opencode/commands/shape.md +7 -0
  116. package/.opencode/opencode.jsonc +15 -0
  117. package/.opencode/plugins/kataster/index.ts +283 -0
  118. package/AGENTS.md +21 -0
  119. package/CHANGELOG.md +91 -0
  120. package/LICENSE +21 -0
  121. package/README.md +331 -2
  122. package/bin/kataster.mjs +7 -0
  123. package/extras/skills/shadcn/SKILL.md +277 -0
  124. package/extras/skills/shadcn/agents/openai.yml +5 -0
  125. package/extras/skills/shadcn/assets/shadcn-small.png +0 -0
  126. package/extras/skills/shadcn/assets/shadcn.png +0 -0
  127. package/extras/skills/shadcn/cli.md +290 -0
  128. package/extras/skills/shadcn/customization.md +209 -0
  129. package/extras/skills/shadcn/evals/evals.json +77 -0
  130. package/extras/skills/shadcn/mcp.md +105 -0
  131. package/extras/skills/shadcn/registry.md +277 -0
  132. package/extras/skills/shadcn/rules/base-vs-radix.md +306 -0
  133. package/extras/skills/shadcn/rules/chat.md +250 -0
  134. package/extras/skills/shadcn/rules/composition.md +213 -0
  135. package/extras/skills/shadcn/rules/forms.md +192 -0
  136. package/extras/skills/shadcn/rules/icons.md +101 -0
  137. package/extras/skills/shadcn/rules/styling.md +185 -0
  138. package/guide/README.md +9 -0
  139. package/guide/brownfield.md +242 -0
  140. package/guide/concepts.md +109 -0
  141. package/guide/greenfield.md +84 -0
  142. package/guide/install.md +144 -0
  143. package/guide/release.md +92 -0
  144. package/package.json +56 -4
  145. package/src/cli.mjs +302 -0
  146. package/src/detect.mjs +237 -0
  147. package/src/install.mjs +427 -0
  148. package/src/repo.mjs +129 -0
  149. package/src/util.mjs +110 -0
  150. package/templates/CONTEXT.md +21 -0
  151. package/templates/conventions.md +63 -0
  152. package/templates/docs/adr/README.md +7 -0
  153. package/templates/docs/architecture/data-model.md +31 -0
  154. package/templates/docs/domain/context-map.md +21 -0
  155. package/templates/docs/domain/events.yaml +23 -0
  156. package/templates/docs/product/initiatives/README.md +41 -0
  157. package/templates/docs/product/ux-guidelines.md +27 -0
  158. package/templates/docs/product/vision.md +36 -0
  159. package/templates/docs/reviews/.gitkeep +0 -0
  160. package/templates/docs/stories/OPEN_QUESTIONS.md +8 -0
  161. package/templates/docs/stories/README.md +26 -0
  162. package/templates/docs/workflow.md +135 -0
@@ -0,0 +1,58 @@
1
+ ---
2
+ description: Derives user stories with Gherkin acceptance criteria from docs/domain/events.yaml and the data model, and revises stories after reviews. Use after event storming or domain modeling.
3
+ mode: subagent
4
+ color: blue
5
+ permissions:
6
+ - action: edit
7
+ resource: "*"
8
+ effect: deny
9
+ - action: edit
10
+ resource: docs/stories/*
11
+ effect: allow
12
+ - action: shell
13
+ resource: "*"
14
+ effect: deny
15
+ - action: webfetch
16
+ resource: "*"
17
+ effect: deny
18
+ - action: websearch
19
+ resource: "*"
20
+ effect: deny
21
+ - action: skill
22
+ resource: "*"
23
+ effect: deny
24
+ - action: skill
25
+ resource: user-stories
26
+ effect: allow
27
+ ---
28
+
29
+ Before you start, load the skill `user-stories` with the `skill` tool.
30
+
31
+ You are the requirements engineer for this project. You have NO context from the main conversation. Write in the artifact language (`languages.artifacts` in `.claude/project.json`, default English).
32
+
33
+ ## Sources of Truth (read before every task)
34
+ - `docs/product/vision.md` – user groups and scope
35
+ - `CONTEXT.md` – glossary; use the terms exactly as written, never an `_Avoid_` synonym
36
+ - `docs/domain/events.yaml` – events, commands, actors, policies, read models, contexts. A command's `rules` are
37
+ **not** the whole truth: an invariant is stated once, on its `aggregate` (user, 2026-10-01), so always read the
38
+ aggregate's `invariants` too and derive a rejection scenario for every one the command can violate.
39
+ - `docs/architecture/data-model.md` – if present
40
+ - `docs/reviews/` – for revision tasks
41
+
42
+ ## Approach
43
+ - Method, format, splitting patterns and numbering: see the preloaded skill `user-stories`.
44
+ - Next free ID: check existing `docs/stories/ST-*.md` via Glob.
45
+ - New stories get `status: draft`; once you're done with a story, `status: review`.
46
+ - `priority` initially `should` (the PO decides), leave `size` empty (lead-dev estimates).
47
+ - Never edit `docs/stories/BACKLOG.md`; it is regenerated automatically from the story files.
48
+ - With a tracker (`backlog.provider` ≠ `local`), write the local mirror only. Never set `external` by hand and never
49
+ call Jira/GitHub yourself – the main session pushes with `/backlog-sync push` after you are done.
50
+
51
+ ## Validation
52
+ After every write, a hook checks the file automatically. If it reports errors, fix them immediately in the same file. Never work around rules (e.g. padding scenarios just to reach the minimum count).
53
+
54
+ ## Uncertainties
55
+ Don't guess. Mark open points in the story with `[OPEN]` AND collect them with the story ID in `docs/stories/OPEN_QUESTIONS.md`. Don't invent missing domain terms or events – report them as open questions.
56
+
57
+ ## Output (reply to the main session)
58
+ List of created/changed stories (ID, title, context, linked events), coverage: which commands don't have a story yet, open questions.
@@ -0,0 +1,83 @@
1
+ ---
2
+ description: UX/UI designer. Reviews stories with a frontend part and existing screens against docs/product/ux-guidelines.md, and maintains those guidelines. Use in backlog grooming for stories labelled `ui`, and when a feature's grilling changed a UX guideline.
3
+ mode: subagent
4
+ color: pink
5
+ permissions:
6
+ - action: edit
7
+ resource: "*"
8
+ effect: deny
9
+ - action: edit
10
+ resource: docs/product/ux-guidelines.md
11
+ effect: allow
12
+ - action: edit
13
+ resource: docs/reviews/*
14
+ effect: allow
15
+ - action: shell
16
+ resource: "*"
17
+ effect: deny
18
+ - action: webfetch
19
+ resource: "*"
20
+ effect: deny
21
+ - action: websearch
22
+ resource: "*"
23
+ effect: deny
24
+ - action: skill
25
+ resource: "*"
26
+ effect: deny
27
+ - action: skill
28
+ resource: ux-design
29
+ effect: allow
30
+ - action: skill
31
+ resource: user-stories
32
+ effect: allow
33
+ ---
34
+
35
+ Before you start, load the skills `ux-design` and `user-stories` with the `skill` tool.
36
+
37
+ You are the UX/UI designer of this project, whose interface its users meet in the situations
38
+ `docs/product/vision.md` describes. You have NO context from the main conversation – everything relevant is in `docs/` and in the code.
39
+ Write in the artifact language (`languages.artifacts` in `.claude/project.json`, default English); the UI texts you propose are in the project UI language (`languages.ui` in `.claude/project.json`).
40
+
41
+ ## Yardstick
42
+ - `docs/product/ux-guidelines.md` – the decisions already made. **Your file.** If it does not exist yet, say so
43
+ and propose its first content instead of judging against rules nobody agreed to.
44
+ - `docs/product/vision.md` – user groups, devices, constraints, non-goals
45
+ - `CONTEXT.md` – the glossary; UI terms use the `_UI (<lang>)_` wording exactly
46
+ - `docs/engineering/conventions.md` – the page frame, the design size, component rules, where the
47
+ message catalogs live. What is decided there is not yours to re-decide.
48
+ - The method, the question set and the review order: the preloaded skill `ux-design`.
49
+
50
+ ## Tasks
51
+ 1. **Story review** – per story with a frontend part: is the screen's job clear, is the entry point named, do the
52
+ empty, rejected and no-permission cases have a scenario, is the UI wording decided and does it match the
53
+ glossary? Name what is missing as concrete scenarios or Context lines the requirements-engineer can apply.
54
+ 2. **Screen review** – read the page, the form and the message catalog in the code (`repo.codeRoots` in `.claude/project.json`), and judge in the order the
55
+ skill gives. Findings only; you never change application code.
56
+ 3. **Guidelines** – after the user approves a decision, write it into `docs/product/ux-guidelines.md` as a rule,
57
+ with the date and the reason. Keep it short enough that someone reads it before writing a page.
58
+ 4. **Gaps** – which recurring interface question has no guideline yet, and which existing screens contradict each
59
+ other?
60
+
61
+ ## Boundaries
62
+ - **You never write stories.** Everything that belongs in a story comes back as a recommendation – the
63
+ `requirements-engineer` applies it. The same rule the product-owner and the lead-dev follow.
64
+ - You never write application code, tests or the engineering conventions.
65
+ - No implementation detail in what you propose: no component names, no classes, no pixel values. Observable
66
+ behaviour and words, not markup.
67
+ - Make no assumptions where the vision or the guidelines are silent: return them as questions, each with a
68
+ recommended answer, as the `grilling` skill requires.
69
+ - Don't invent domain terms. A term that is missing belongs in `CONTEXT.md` first, through the domain-model skill
70
+ – say so, don't use a synonym.
71
+ - Never edit `docs/stories/BACKLOG.md`; it is generated.
72
+
73
+ ## Output (reply to the main session)
74
+ A table of findings:
75
+
76
+ | Story / screen | Level (job / understanding / states / language / consistency / polish) | Severity (blocker, major, minor) | Finding | What to do instead |
77
+
78
+ Below it: the scenarios or Context lines you propose per story (ready for the requirements-engineer to apply), the
79
+ guidelines you would add, and at most 3 questions for the user, each with a recommended answer.
80
+
81
+ Severity means: **blocker** – the person cannot do the job, or cannot on a phone; **major** – they can, but will
82
+ get it wrong, misread it or not find it; **minor** – it works and is understood, but is inconsistent or clumsy.
83
+ Be honest about the difference; calling polish a blocker costs you the next argument.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Brownfield entry point – maps the repository, adopts it in slices (one domain plus at most one shared library), drafts the domain model with evidence, documents each package where it lives, then grills the user and records the gap; stories only with --stories.
3
+ ---
4
+
5
+ Load the `adopt` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Syncs the local story mirror with the configured backlog backend – local (default), GitHub Issues, or Jira via MCP.
3
+ ---
4
+
5
+ Load the `backlog` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Greenfield entry point – grills the vision, runs a big-picture event storming, builds the domain model and architecture with ADRs, sets the UX yardstick and derives the foundation stories and first vertical slice.
3
+ ---
4
+
5
+ Load the `bootstrap` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Facilitates an interactive event storming with the user (big picture or a single process) and maintains docs/domain/events.yaml and the glossary in CONTEXT.md.
3
+ ---
4
+
5
+ Load the `event-storming` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Entry point for a new feature or change once the initial discovery is done – accepts a free-text idea, an existing story (`ST-NNN`) or a ticket (`#123`, `PROJ-123`, URL); grills the idea, runs a focused event storming, updates domain model and ADRs, derives stories into the backlog (local, or the tracker mirror) and runs the story review; bug tickets go to a regression story.
3
+ ---
4
+
5
+ Load the `feature` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: A relentless interview to sharpen a plan or design.
3
+ ---
4
+
5
+ Load the `grill-me` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: A relentless interview to sharpen a plan or design, which also creates docs (glossary in CONTEXT.md and ADRs in docs/adr/) as we go.
3
+ ---
4
+
5
+ Load the `grill-with-docs` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Periodic grooming of the open backlog – challenges whether stories are still worth building, whether dependencies still hold and whether the priorities still match what the product needs now.
3
+ ---
4
+
5
+ Load the `groom-backlog` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Engineering workflow – implements one story from docs/stories/ test-first on its own branch, verifies, has it reviewed by code-reviewer and acceptance-tester, refactors, and sets it done on the acceptance tester's verdict and hands the pull request to the user to merge.
3
+ ---
4
+
5
+ Load the `implement` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
3
+ ---
4
+
5
+ Load the `improve-codebase-architecture` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Conduct a retrospective on a coding session.
3
+ ---
4
+
5
+ Load the `retro` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Has stories checked in one lead-dev pass (value, build, UX), applies the small fixes and guides the user to approval (status ready).
3
+ ---
4
+
5
+ Load the `review-stories` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,7 @@
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.
3
+ ---
4
+
5
+ Load the `shape` skill with the `skill` tool and follow it exactly. Do not restate it.
6
+
7
+ Arguments: $ARGUMENTS
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+
4
+ // The workflow itself lives in .claude/ and is shared with Claude Code.
5
+ // .opencode/ holds the OpenCode wiring: agents, commands, the hook adapter plugin.
6
+ //
7
+ // Permissions: the hard blocks (force push, production deploy, destructive database
8
+ // commands, generated files, accepted ADRs) are enforced by
9
+ // .opencode/plugins/kataster/index.ts, which runs the .claude/hooks/*.ts scripts.
10
+ // Per-agent write scopes are expressed as `edit` permissions in .opencode/agents/*.md.
11
+
12
+ "watcher": {
13
+ "ignore": ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/coverage/**"]
14
+ }
15
+ }
@@ -0,0 +1,283 @@
1
+ /**
2
+ * OpenCode adapter for the Kataster workflow.
3
+ *
4
+ * The actual logic lives in `.claude/hooks/*.ts` (plain Node, shared with Claude Code).
5
+ * This plugin only translates OpenCode's hook events into the JSON those scripts read
6
+ * on stdin and their exit codes back into OpenCode behaviour:
7
+ *
8
+ * SessionStart -> event `session.created` -> instructions entry
9
+ * PreToolUse -> tool hook `execute.before` -> throw = block
10
+ * PostToolUse -> tool hook `execute.after` -> append text to the result
11
+ * Stop -> session hook `experimental.idle` -> synthetic message once
12
+ *
13
+ * OpenCode also has no path-scoped rules: on every read or write, matching `.claude/rules/*.md` files are
14
+ * attached as keyed instruction entries (`path-rules.ts`), so both harnesses see the same rules.
15
+ *
16
+ * The per-agent path guard is not wired here: it is expressed as `edit` permissions in
17
+ * `.opencode/agents/*.md`, which is the native OpenCode equivalent.
18
+ */
19
+ import { Plugin } from "@opencode/plugin";
20
+ import { spawn } from "node:child_process";
21
+ import { join } from "node:path";
22
+
23
+ type HookResult = { code: number; stdout: string; stderr: string };
24
+
25
+ /** Claude tool names the hook scripts switch on. */
26
+ const TOOL_NAME: Record<string, string> = {
27
+ write: "Write",
28
+ edit: "Edit",
29
+ patch: "MultiEdit",
30
+ shell: "Bash",
31
+ };
32
+
33
+ function pick(input: unknown, ...names: string[]): unknown {
34
+ const obj = input as Record<string, unknown> | undefined;
35
+ for (const name of names) if (obj?.[name] !== undefined) return obj[name];
36
+ return undefined;
37
+ }
38
+
39
+ /** Map an OpenCode tool input onto the `tool_input` shape the Claude hooks expect. */
40
+ function claudeInput(tool: string, input: unknown): Record<string, unknown> {
41
+ if (tool === "shell") return { command: pick(input, "command") ?? "" };
42
+
43
+ const file_path = pick(input, "filePath", "file_path", "path", "file");
44
+ if (tool === "write") return { file_path, content: pick(input, "content") };
45
+ if (tool === "edit")
46
+ return {
47
+ file_path,
48
+ old_string: pick(input, "oldString", "old_string"),
49
+ new_string: pick(input, "newString", "new_string"),
50
+ replace_all: pick(input, "replaceAll", "replace_all"),
51
+ };
52
+ // `patch` carries no old/new strings; the path alone is enough for the guards.
53
+ return { file_path };
54
+ }
55
+
56
+ function additionalContext(stdout: string): string | undefined {
57
+ try {
58
+ return JSON.parse(stdout)?.hookSpecificOutput?.additionalContext;
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ }
63
+
64
+ /** Files a tool call touches. `patch` carries a patch text instead of one path. */
65
+ function toolPaths(tool: string, input: unknown): string[] {
66
+ if (tool === "patch") {
67
+ const text = String(pick(input, "patchText", "patch_text", "patch") ?? "");
68
+ const out: string[] = [];
69
+ for (const line of text.split("\n")) {
70
+ const match = /^\*\*\* (?:Add|Update|Delete) File: (.+)$/.exec(line) ?? /^\*\*\* Move to: (.+)$/.exec(line);
71
+ if (match) out.push(match[1].trim());
72
+ }
73
+ return [...new Set(out)];
74
+ }
75
+ const path = pick(input, "filePath", "file_path", "path", "file");
76
+ return typeof path === "string" && path ? [path] : [];
77
+ }
78
+
79
+ /** Path-scoped rules returned by `.claude/hooks/path-rules.ts`. */
80
+ function parseRules(stdout: string): { key: string; value: string }[] {
81
+ try {
82
+ const data = JSON.parse(stdout.trim()) as { rules?: unknown };
83
+ if (!Array.isArray(data.rules)) return [];
84
+ return data.rules.filter(
85
+ (rule): rule is { key: string; value: string } =>
86
+ rule !== null &&
87
+ typeof rule === "object" &&
88
+ typeof (rule as { key?: unknown }).key === "string" &&
89
+ typeof (rule as { value?: unknown }).value === "string",
90
+ );
91
+ } catch {
92
+ return [];
93
+ }
94
+ }
95
+
96
+ /** Append feedback to a successful tool result so the model sees it. */
97
+ function appendText(result: unknown, text: string): unknown {
98
+ const res = result as { content?: unknown } | undefined;
99
+ const content = res?.content;
100
+ if (typeof content === "string") return { ...res, content: `${content}\n\n${text}` };
101
+ const list = Array.isArray(content) ? content : [];
102
+ return { ...res, content: [...list, { type: "text", text }] };
103
+ }
104
+
105
+ export default Plugin.define({
106
+ id: "kataster",
107
+ async setup(ctx) {
108
+ const root = ctx.location.directory;
109
+
110
+ /** Run one hook script with a Claude-shaped payload on stdin. Never throws. */
111
+ function run(script: string, payload: unknown, timeout = 120_000): Promise<HookResult> {
112
+ return new Promise((resolve) => {
113
+ // The plugin itself runs inside OpenCode's bundled Bun; the hooks are plain
114
+ // Node scripts (type stripping), so run them with the project's Node.
115
+ const child = spawn(process.env.KATASTER_NODE ?? "node", [join(root, ".claude", "hooks", script)], {
116
+ cwd: root,
117
+ env: { ...process.env, CLAUDE_PROJECT_DIR: root },
118
+ stdio: ["pipe", "pipe", "pipe"],
119
+ });
120
+ let stdout = "";
121
+ let stderr = "";
122
+ child.stdout.setEncoding("utf8").on("data", (chunk) => (stdout += chunk));
123
+ child.stderr.setEncoding("utf8").on("data", (chunk) => (stderr += chunk));
124
+ const timer = setTimeout(() => child.kill("SIGKILL"), timeout);
125
+ child.on("error", (error) => {
126
+ clearTimeout(timer);
127
+ resolve({ code: 1, stdout, stderr: `${stderr}${String(error)}` });
128
+ });
129
+ child.on("close", (code) => {
130
+ clearTimeout(timer);
131
+ resolve({ code: code ?? 0, stdout, stderr });
132
+ });
133
+ child.stdin.end(JSON.stringify(payload));
134
+ });
135
+ }
136
+
137
+ // ---- Path-scoped rules ------------------------------------------------
138
+ // Claude Code reads `.claude/rules/` itself. OpenCode has no path-scoped rules, so the same files are
139
+ // attached to the session when a matching file is read or written. One entry per rule, so a rule is
140
+ // injected once per session and a later write to the same area does not repeat it.
141
+ const rulesSeen = new Map<string, Set<string>>();
142
+ async function injectRules(sessionID: string, tool: string, input: unknown): Promise<void> {
143
+ const paths = toolPaths(tool, input);
144
+ if (!paths.length) return;
145
+ try {
146
+ const res = await run("path-rules.ts", { paths, cwd: root }, 15_000);
147
+ const entries = parseRules(res.stdout);
148
+ if (!entries.length) return;
149
+ const seen = rulesSeen.get(sessionID) ?? new Set<string>();
150
+ rulesSeen.set(sessionID, seen);
151
+ for (const entry of entries) {
152
+ if (seen.has(entry.key)) continue;
153
+ seen.add(entry.key);
154
+ await ctx.session.instructions.entry.put({
155
+ sessionID: sessionID as never,
156
+ key: `kataster-rule:${entry.key}`,
157
+ value: entry.value,
158
+ });
159
+ }
160
+ } catch (error) {
161
+ // rules are guidance – never let them break a tool result
162
+ console.error(`[kataster] path-rules failed: ${String(error)}`);
163
+ }
164
+ }
165
+
166
+ // ---- SessionStart ------------------------------------------------------
167
+ // The hook prints what is missing and where the engineering work stands.
168
+ // An instruction entry adds it as context without starting a model turn.
169
+ // Child sessions (subagents) get no status block and no idle gate.
170
+ const controller = new AbortController();
171
+ const childSessions = new Set<string>();
172
+ void (async () => {
173
+ for await (const event of ctx.event.subscribe({
174
+ types: ["session.created"],
175
+ signal: controller.signal,
176
+ })) {
177
+ const data = (event as {
178
+ data?: { sessionID?: string; parentID?: string; location?: { directory?: string } };
179
+ }).data;
180
+ if (!data?.sessionID || data.location?.directory !== root) continue;
181
+ if (data.parentID) {
182
+ childSessions.add(data.sessionID);
183
+ continue;
184
+ }
185
+ const res = await run("session-start.ts", {}, 15_000);
186
+ const text = res.stdout.trim();
187
+ if (!text) continue;
188
+ await ctx.session.instructions.entry.put({
189
+ sessionID: data.sessionID as never,
190
+ key: "kataster-status",
191
+ value: text,
192
+ });
193
+ }
194
+ })().catch((error) => {
195
+ // a broken status check never blocks a session
196
+ console.error(`[kataster] session-start failed: ${String(error)}`);
197
+ });
198
+
199
+ // ---- PreToolUse: protect-files + bash-guard ----------------------------
200
+ await ctx.tool.hook("execute.before", async (event) => {
201
+ const toolName = TOOL_NAME[event.tool];
202
+ if (!toolName) return;
203
+
204
+ const payload = {
205
+ tool_name: toolName,
206
+ tool_input: claudeInput(event.tool, event.input),
207
+ cwd: root,
208
+ session_id: event.sessionID,
209
+ agent: event.agent,
210
+ };
211
+
212
+ if (toolName === "Bash") {
213
+ const res = await run("bash-guard.ts", payload, 15_000);
214
+ if (res.code === 2) throw new Error(res.stderr.trim() || "Blocked by bash-guard.");
215
+ return;
216
+ }
217
+
218
+ const res = await run("protect-files.ts", payload, 15_000);
219
+ if (res.code === 2) throw new Error(res.stderr.trim() || "Blocked by protect-files.");
220
+ });
221
+
222
+ // ---- PostToolUse: validate-docs + code-check + project checks ----------
223
+ await ctx.tool.hook("execute.after", async (event) => {
224
+ if (event.status !== "completed") return;
225
+
226
+ // Path-scoped rules become session context, not tool output.
227
+ await injectRules(event.sessionID, event.tool, event.input);
228
+
229
+ const toolName = TOOL_NAME[event.tool];
230
+ if (toolName !== "Write" && toolName !== "Edit" && toolName !== "MultiEdit") return;
231
+
232
+ const payload = {
233
+ tool_name: toolName,
234
+ tool_input: claudeInput(event.tool, event.input),
235
+ cwd: root,
236
+ session_id: event.sessionID,
237
+ agent: event.agent,
238
+ };
239
+
240
+ const notes: string[] = [];
241
+
242
+ const docs = await run("validate-docs.ts", payload, 60_000);
243
+ if (docs.code === 2) notes.push(docs.stderr.trim());
244
+ else {
245
+ const extra = additionalContext(docs.stdout);
246
+ if (extra) notes.push(extra);
247
+ }
248
+
249
+ const code = await run("code-check.ts", payload, 120_000);
250
+ if (code.code === 2) notes.push(code.stderr.trim());
251
+
252
+ const project = await run("project-check.ts", payload, 120_000);
253
+ if (project.code === 2) notes.push(project.stderr.trim());
254
+ else {
255
+ const extra = additionalContext(project.stdout);
256
+ if (extra) notes.push(extra);
257
+ }
258
+
259
+ if (notes.length) event.result = appendText(event.result, notes.join("\n\n")) as never;
260
+ });
261
+
262
+ // ---- Stop: the quick gate, once per user turn -------------------------
263
+ // `experimental.idle` runs when the session has no pending input. The first
264
+ // red result is fed back as a synthetic message; a new user prompt re-arms it.
265
+ const sentBack = new Set<string>();
266
+ await ctx.session.hook("prompt", (event) => {
267
+ sentBack.delete(event.sessionID);
268
+ });
269
+ await ctx.session.hook("experimental.idle", async (event) => {
270
+ if (childSessions.has(event.sessionID)) return;
271
+ if (sentBack.has(event.sessionID)) return;
272
+ const res = await run("stop-gate.ts", { stop_hook_active: false, session_id: event.sessionID }, 600_000);
273
+ if (res.code !== 2) return;
274
+ sentBack.add(event.sessionID);
275
+ await ctx.session.synthetic({
276
+ sessionID: event.sessionID,
277
+ text: res.stderr.trim(),
278
+ });
279
+ });
280
+
281
+ return () => controller.abort();
282
+ },
283
+ });
package/AGENTS.md ADDED
@@ -0,0 +1,21 @@
1
+ # Kataster – development entry point
2
+
3
+ This repository is the **toolkit**, not an installation of it. `.claude/`, `.opencode/`, `templates/` and
4
+ `extras/` are the payload that `npx kataster init` copies into a target repository.
5
+
6
+ - **Installer:** `src/cli.mjs` (commands, prompts), `src/install.mjs` (copy, merge, manifest, upgrade),
7
+ `src/detect.mjs` (stack detection), `src/repo.mjs` (monorepo tooling and workspaces), `src/util.mjs`.
8
+ - **Payload:** `.claude/` (skills, agents, hooks, lib, settings), `.opencode/` (commands, agents, plugin),
9
+ `templates/` (docs skeleton, project profile defaults, conventions template), `extras/` (optional skills).
10
+ - **Tests:** `npm test` (installs into temp directories and asserts the result).
11
+ - **Docs for users:** `README.md` and `guide/`.
12
+
13
+ Rules:
14
+
15
+ - The payload must stay project-neutral. No product names, no hardcoded stack, no hardcoded language. Project facts
16
+ come from `.claude/project.json`; examples in skills use neutral domains.
17
+ - Every new workflow file goes into both harnesses (`.claude/agents` and `.opencode/agents`, command in
18
+ `.opencode/commands`) or is deliberately harness-specific and documented as such.
19
+ - A file the installer manages is tracked in `.claude/install.json`; project content (`CONTEXT.md`, `docs/`,
20
+ `project.json`, `AGENTS.md` block) is not. Never make an upgrade overwrite user content.
21
+ - Run `npm test` and `npm run typecheck --prefix .claude` before committing.