@justyork/repo-mind 0.7.2 → 0.8.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 (76) hide show
  1. package/.cursor/skills/repomind-docs/SKILL.md +160 -0
  2. package/.cursor/skills/repomind-docs/examples.md +429 -0
  3. package/.cursor/skills/repomind-docs/reference.md +209 -0
  4. package/.cursor/skills/repomind-docs/structure.md +132 -0
  5. package/dist/cli.js +17 -0
  6. package/dist/commands/install-skill.d.ts +9 -0
  7. package/dist/commands/install-skill.js +35 -0
  8. package/dist/commands/ui.js +5 -1
  9. package/dist/ui/auth.d.ts +21 -0
  10. package/dist/ui/auth.js +117 -0
  11. package/dist/ui/fs-operations.d.ts +1 -1
  12. package/dist/ui/fs-operations.js +36 -4
  13. package/dist/ui/fs-tree.d.ts +5 -4
  14. package/dist/ui/fs-tree.js +60 -56
  15. package/dist/ui/server.d.ts +2 -0
  16. package/dist/ui/server.js +63 -2
  17. package/package.json +3 -2
  18. package/ui/dist/assets/{arc-C1R_fyW-.js → arc-BitGDA7a.js} +1 -1
  19. package/ui/dist/assets/{architectureDiagram-3BPJPVTR-CcuF3FYc.js → architectureDiagram-3BPJPVTR-BmY2OtU5.js} +1 -1
  20. package/ui/dist/assets/{blockDiagram-GPEHLZMM-BZJKe3Ei.js → blockDiagram-GPEHLZMM-btai_20t.js} +1 -1
  21. package/ui/dist/assets/{c4Diagram-AAUBKEIU-D_0fRNGS.js → c4Diagram-AAUBKEIU-CN-pXtFe.js} +1 -1
  22. package/ui/dist/assets/channel-D1plp4bS.js +1 -0
  23. package/ui/dist/assets/{chunk-2J33WTMH-0LcH4vIA.js → chunk-2J33WTMH-CUgwCKKP.js} +1 -1
  24. package/ui/dist/assets/{chunk-4BX2VUAB-DU1h67AD.js → chunk-4BX2VUAB-ZXi72_qf.js} +1 -1
  25. package/ui/dist/assets/{chunk-55IACEB6-BCx7dyYV.js → chunk-55IACEB6-BvoNhEcH.js} +1 -1
  26. package/ui/dist/assets/{chunk-727SXJPM-CRO5yOrB.js → chunk-727SXJPM-CPVv-LcP.js} +1 -1
  27. package/ui/dist/assets/{chunk-AQP2D5EJ-BayHdr67.js → chunk-AQP2D5EJ-15HMmfKe.js} +1 -1
  28. package/ui/dist/assets/{chunk-FMBD7UC4-CJemLien.js → chunk-FMBD7UC4-CTJu1mOO.js} +1 -1
  29. package/ui/dist/assets/{chunk-ND2GUHAM-B_z-tw44.js → chunk-ND2GUHAM-BaS37xMv.js} +1 -1
  30. package/ui/dist/assets/{chunk-QZHKN3VN-D40rAh_S.js → chunk-QZHKN3VN-CzNZR3D9.js} +1 -1
  31. package/ui/dist/assets/classDiagram-4FO5ZUOK-BsDWU9KJ.js +1 -0
  32. package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-BsDWU9KJ.js +1 -0
  33. package/ui/dist/assets/{cose-bilkent-S5V4N54A-Cqthtrrq.js → cose-bilkent-S5V4N54A-mpC0WnuY.js} +1 -1
  34. package/ui/dist/assets/{dagre-BM42HDAG-CrxsyXEa.js → dagre-BM42HDAG-DVbZMTFr.js} +1 -1
  35. package/ui/dist/assets/{diagram-2AECGRRQ-DTYA7Rrn.js → diagram-2AECGRRQ-BvYsIaeC.js} +1 -1
  36. package/ui/dist/assets/{diagram-5GNKFQAL-CXs7RV13.js → diagram-5GNKFQAL-mM_4t2hT.js} +1 -1
  37. package/ui/dist/assets/{diagram-KO2AKTUF-Bf8EeV_S.js → diagram-KO2AKTUF-u5t0FZtc.js} +1 -1
  38. package/ui/dist/assets/{diagram-LMA3HP47-C2XWB0cI.js → diagram-LMA3HP47-Ngnghl4t.js} +1 -1
  39. package/ui/dist/assets/{diagram-OG6HWLK6-BNMXob9h.js → diagram-OG6HWLK6-DxCLvBmh.js} +1 -1
  40. package/ui/dist/assets/{erDiagram-TEJ5UH35-CnSczOvb.js → erDiagram-TEJ5UH35-Cblf5xKO.js} +1 -1
  41. package/ui/dist/assets/{flowDiagram-I6XJVG4X-DwZBJ2fr.js → flowDiagram-I6XJVG4X-3aVBpTd3.js} +1 -1
  42. package/ui/dist/assets/{ganttDiagram-6RSMTGT7-rxwvD8xn.js → ganttDiagram-6RSMTGT7-CCk8vbkn.js} +1 -1
  43. package/ui/dist/assets/{gitGraphDiagram-PVQCEYII-DjJPiQ2K.js → gitGraphDiagram-PVQCEYII-DLUIS6md.js} +1 -1
  44. package/ui/dist/assets/graph-DwaX8nDF.js +2 -0
  45. package/ui/dist/assets/{infoDiagram-5YYISTIA-BHgtQpBK.js → infoDiagram-5YYISTIA-C6umdFYV.js} +1 -1
  46. package/ui/dist/assets/{ishikawaDiagram-YF4QCWOH-BZ38ndde.js → ishikawaDiagram-YF4QCWOH-H8wERm8_.js} +1 -1
  47. package/ui/dist/assets/{journeyDiagram-JHISSGLW-CNaZf5VN.js → journeyDiagram-JHISSGLW-DnTRQ6Mn.js} +1 -1
  48. package/ui/dist/assets/{kanban-definition-UN3LZRKU-4h1Jmt_X.js → kanban-definition-UN3LZRKU-DrVEsZSL.js} +1 -1
  49. package/ui/dist/assets/main-C0Y60ppL.js +269 -0
  50. package/ui/dist/assets/{mermaid.core-BZZdRyuR.js → mermaid.core-BtOBEW5j.js} +4 -4
  51. package/ui/dist/assets/{mindmap-definition-RKZ34NQL-DG_NciLX.js → mindmap-definition-RKZ34NQL-BFEjh9BY.js} +1 -1
  52. package/ui/dist/assets/{pieDiagram-4H26LBE5-B9FyPCLY.js → pieDiagram-4H26LBE5-KLu3myN4.js} +1 -1
  53. package/ui/dist/assets/{quadrantDiagram-W4KKPZXB-B89WFfQu.js → quadrantDiagram-W4KKPZXB-DwSP1nfl.js} +1 -1
  54. package/ui/dist/assets/{requirementDiagram-4Y6WPE33-BYHKePgB.js → requirementDiagram-4Y6WPE33-CTe59AWj.js} +1 -1
  55. package/ui/dist/assets/{sankeyDiagram-5OEKKPKP-BW6YuBQM.js → sankeyDiagram-5OEKKPKP-DBG4pZal.js} +1 -1
  56. package/ui/dist/assets/{sequenceDiagram-3UESZ5HK-BnojqKwf.js → sequenceDiagram-3UESZ5HK-BV9E_Iae.js} +1 -1
  57. package/ui/dist/assets/{stateDiagram-AJRCARHV-C_x9t2o6.js → stateDiagram-AJRCARHV-BWnAUqpA.js} +1 -1
  58. package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-yNXWOXzs.js +1 -0
  59. package/ui/dist/assets/theme-CfkLgQFf.js +21 -0
  60. package/ui/dist/assets/theme-DJYNWCef.css +1 -0
  61. package/ui/dist/assets/{timeline-definition-PNZ67QCA-CI8Ngfzv.js → timeline-definition-PNZ67QCA-DDoGl25M.js} +1 -1
  62. package/ui/dist/assets/{vennDiagram-CIIHVFJN-CPRKGc2P.js → vennDiagram-CIIHVFJN-DVRPIJjW.js} +1 -1
  63. package/ui/dist/assets/{visual-editor-DUVPRlY3.js → visual-editor-COzWiJ8C.js} +12 -12
  64. package/ui/dist/assets/{wardley-L42UT6IY-CZY42jFT.js → wardley-L42UT6IY-Li08EMdE.js} +1 -1
  65. package/ui/dist/assets/{wardleyDiagram-YWT4CUSO-CGKyWKnw.js → wardleyDiagram-YWT4CUSO-BA2E9m1f.js} +1 -1
  66. package/ui/dist/assets/{xychartDiagram-2RQKCTM6-Cq-onAc3.js → xychartDiagram-2RQKCTM6-DKHRvUg5.js} +1 -1
  67. package/ui/dist/graph.html +3 -3
  68. package/ui/dist/index.html +3 -3
  69. package/ui/dist/assets/channel-BlWnHbuq.js +0 -1
  70. package/ui/dist/assets/classDiagram-4FO5ZUOK-D3OdfPqd.js +0 -1
  71. package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-D3OdfPqd.js +0 -1
  72. package/ui/dist/assets/graph-D_tIaE_I.js +0 -2
  73. package/ui/dist/assets/main-BeHrzLrS.js +0 -273
  74. package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-CBZx8gKT.js +0 -1
  75. package/ui/dist/assets/theme-CFHWfrn1.css +0 -1
  76. package/ui/dist/assets/theme-DS1fBM5L.js +0 -1
@@ -0,0 +1,209 @@
1
+ # RepoMind docs — reference
2
+
3
+ See **[structure.md](structure.md)** for the full documentation tree and domain×type matrix.
4
+
5
+ ## Frontmatter schema
6
+
7
+ | Field | Type | Required | Validation |
8
+ |-------|------|----------|------------|
9
+ | `type` | string | yes (structured) | Must be one of `DOC_TYPES` below |
10
+ | `domain` | string | yes (structured) | Must be one of `DOC_DOMAINS` below; should match path prefix |
11
+ | `slug` | string | yes | `^[a-z0-9][a-z0-9-]*$`; unique across all indexed files |
12
+ | `status` | string | yes | Must be one of `DOC_STATUSES` below |
13
+ | `title` | string | recommended | Used in UI, search snippets, wikilink fallback |
14
+ | `tags` | string[] | optional | Flat list; used for MCP `list_docs` filter |
15
+ | `related` | string[] | optional | Each entry must match an existing doc slug |
16
+ | `owner` | string | optional | Free text |
17
+ | `updated` | string | optional | ISO date `YYYY-MM-DD` |
18
+
19
+ ### Documentation domains (`domain`)
20
+
21
+ | Value | Label | Primary content |
22
+ |-------|-------|-----------------|
23
+ | `product` | Product | PRD, roadmap, user value, product open questions |
24
+ | `technical` | Technical | ADR, API, architecture, infra |
25
+ | `game-design` | Game design | Mechanics, balance, system specs |
26
+ | `analytics` | Analytics | Events, metrics, experiments |
27
+ | `art` | Art | Visual style, asset guidelines |
28
+ | `narrative` | Narrative | Lore, story, dialogue |
29
+ | `ops` | Operations | Runbooks, release, liveops |
30
+ | `shared` | Shared | Global glossary, agent rules, cross-domain wiki |
31
+
32
+ Path inference: first segment under `docs/` if it matches a domain id; otherwise `shared`.
33
+
34
+ MCP filters: `list_docs({ domain: "game-design" })`, `search_docs({ query, domain: "technical" })`.
35
+
36
+ ### Doc types (`type`)
37
+
38
+ | Value | Folder | Typical use |
39
+ |-------|--------|-------------|
40
+ | `adr` | `adr/` | Architecture Decision Records |
41
+ | `feature-spec` | `specs/` | Feature/product specifications |
42
+ | `glossary-term` | `glossary/` | Domain vocabulary definitions |
43
+ | `open-question` | `open-questions/` | Unresolved decisions |
44
+ | `agent-instruction` | `agents/` | Rules/workflows for AI agents |
45
+ | `wiki-page` | `wiki/` | General wiki content |
46
+
47
+ ### Status values (`status`)
48
+
49
+ | Value | Meaning |
50
+ |-------|---------|
51
+ | `draft` | Work in progress; may be incomplete |
52
+ | `proposed` | Ready for review; not yet accepted |
53
+ | `accepted` | Current, authoritative content |
54
+ | `superseded` | Replaced by another doc; keep for history, link to successor |
55
+
56
+ ## Slug rules
57
+
58
+ - Pattern: `^[a-z0-9][a-z0-9-]*$`
59
+ - Lowercase only; hyphens allowed; no underscores or spaces
60
+ - Globally unique across markdown + yaml/json under `docs/`
61
+ - Explicit `slug:` in frontmatter survives file renames better than path inference
62
+ - Path inference (`slugFromRelativePath`): tries `segments.join('-')` then last segment basename
63
+
64
+ ## Link kinds (LinkIndex)
65
+
66
+ | Kind | Source | Notes |
67
+ |------|--------|-------|
68
+ | `related` | frontmatter `related:` array | Directed edge; used by `explore_graph` |
69
+ | `wikilink` | body `[[target]]` or `[[label\|target]]` | Backlinks computed automatically |
70
+ | `parent_of` | folder README → child pages | Tree navigation edges |
71
+
72
+ ### Wikilink syntax
73
+
74
+ ```
75
+ [[slug]]
76
+ [[Human Title|slug]]
77
+ ```
78
+
79
+ Regex: `\[\[([^\]|]+)(?:\|([^\]]+))?\]\]`
80
+
81
+ Resolution order:
82
+
83
+ 1. `target` matches a known slug exactly
84
+ 2. Case-insensitive match on slug or document `title`
85
+
86
+ Broken wikilinks: warning in `repo-mind check` (not a hard violation for `related:`-level errors on wikilinks — check implementation warns on broken wikilink targets).
87
+
88
+ ## Structured files
89
+
90
+ | Extension | contentKind | Frontmatter | Slug example |
91
+ |-----------|-------------|-------------|--------------|
92
+ | `.md` | markdown | yes (structured) | from frontmatter or path |
93
+ | `.yaml`, `.yml` | yaml | no | `config-app` from `config/app.yaml` |
94
+ | `.json` | json | no | path-derived |
95
+
96
+ ## CLI commands
97
+
98
+ ```bash
99
+ repo-mind init [--cwd <dir>]
100
+ repo-mind setup [--cursor] [--claude] [--force]
101
+ repo-mind check [--cwd <dir>]
102
+ repo-mind export [--force] [--cwd <dir>]
103
+ repo-mind prepare [--all] [--dry-run] [--cwd <dir>] [relative-path]
104
+ repo-mind sync-links [--dry-run] [--no-convert-body] [--no-sync-related] [--cwd <dir>]
105
+ repo-mind mcp
106
+ repo-mind ui [--port <n>] [--cwd <dir>]
107
+ ```
108
+
109
+ ### prepare
110
+
111
+ - Single file: `repo-mind prepare wiki/legacy-page.md`
112
+ - Batch: `repo-mind prepare --all`
113
+ - Skips files that already have valid `type:`
114
+ - Infers `type` from folder; generates `slug`, `title`, empty `tags`/`related`, sets `updated` to today
115
+
116
+ ### sync-links
117
+
118
+ Default behavior:
119
+
120
+ 1. Convert resolvable `[text](relative.md)` in body to `[[slug]]`
121
+ 2. Merge outbound wikilink + markdown-link slugs into frontmatter `related` (deduplicated)
122
+
123
+ Flags:
124
+
125
+ - `--dry-run` — report changes without writing
126
+ - `--no-convert-body` — only sync `related`, leave markdown links
127
+ - `--no-sync-related` — only convert body links to wikilinks
128
+
129
+ ## MCP tools
130
+
131
+ | Tool | Input | Output highlights |
132
+ |------|-------|-------------------|
133
+ | `list_docs` | optional type, status, tag filters | slug list |
134
+ | `search_docs` | query string | ranked matches |
135
+ | `get_doc` | slug | frontmatter + body |
136
+ | `get_glossary_term` | name | definition + related |
137
+ | `explore_graph` | slug, depth | nodes, edges, broken_links |
138
+
139
+ ## repo-mind check
140
+
141
+ **Violations (fail):**
142
+
143
+ - Invalid `type` or `status`
144
+ - Missing slug
145
+ - Broken `related:` slug (target doc not found)
146
+ - Duplicate slug across docs
147
+
148
+ **Warnings (pass with notice):**
149
+
150
+ - Broken wikilink targets in body
151
+ - Missing image assets referenced in markdown
152
+ - Orphaned `.worktrees/` older than 7 days
153
+
154
+ ## Anti-patterns
155
+
156
+ | Problem | Why it hurts | Fix |
157
+ |---------|--------------|-----|
158
+ | Duplicate slug | MCP/UI cannot disambiguate | Rename slug or merge docs |
159
+ | Uppercase or spaced slug | Fails validation | Use kebab-case |
160
+ | `related: [missing-slug]` | CI check fails | Create target doc or remove entry |
161
+ | Wikilink by title only | Breaks if title changes | Use `[[slug]]` or `[[Label\|slug]]` |
162
+ | Wrong folder for type | Confusing tree | Use `docs/{domain}/{type-folder}/` |
163
+ | `domain` mismatch with path | check warning | Align frontmatter and path |
164
+ | Product content under wrong domain | Wrong ownership in tree | Move to `product/`, `game-design/`, etc. |
165
+ | Every inline link in `related:` | Noisy graph | Keep 2–5 strategic `related:` entries |
166
+ | Editing only drafts forever | MCP never sees content | Publish to `docs/` |
167
+ | Hard-coded absolute paths in links | Break on move | Use wikilinks or relative md paths |
168
+ | Skipping `updated` | Stale metadata in exports/search | Set date on substantive edits |
169
+
170
+ ## File layout convention
171
+
172
+ ```
173
+ docs/
174
+ ├── README.md
175
+ ├── assets/
176
+ ├── product/
177
+ │ ├── README.md
178
+ │ ├── specs/
179
+ │ ├── wiki/
180
+ │ └── open-questions/
181
+ ├── technical/
182
+ │ ├── README.md
183
+ │ ├── adr/
184
+ │ ├── specs/
185
+ │ ├── glossary/
186
+ │ └── wiki/
187
+ ├── game-design/
188
+ │ ├── README.md
189
+ │ ├── specs/
190
+ │ ├── glossary/
191
+ │ └── wiki/
192
+ ├── analytics/
193
+ │ ├── README.md
194
+ │ ├── specs/
195
+ │ └── wiki/
196
+ ├── art/ … narrative/ … ops/ …
197
+ └── shared/
198
+ ├── README.md
199
+ ├── agents/
200
+ ├── glossary/
201
+ └── wiki/
202
+ ```
203
+
204
+ Legacy flat layout (`docs/adr/`, `docs/specs/` without domain prefix) → `domain: shared`.
205
+
206
+ ## Skill installation
207
+
208
+ - **Project copy:** `.cursor/skills/repomind-docs/` (versioned in repo)
209
+ - **Personal copy:** copy to `~/.cursor/skills/repomind-docs/` for use in any RepoMind project
@@ -0,0 +1,132 @@
1
+ # RepoMind documentation structure
2
+
3
+ Two orthogonal axes organize every page under `docs/`:
4
+
5
+ | Axis | Field / path | Question it answers |
6
+ |------|----------------|---------------------|
7
+ | **Domain** | `domain:` + first path segment | *Which area of the project?* |
8
+ | **Type** | `type:` + type subfolder | *What kind of document?* |
9
+
10
+ ```mermaid
11
+ flowchart TB
12
+ subgraph docsRoot [docs/]
13
+ README[README.md]
14
+ assets[assets/]
15
+ subgraph domains [Domains]
16
+ product[product/]
17
+ technical[technical/]
18
+ gameDesign[game-design/]
19
+ analytics[analytics/]
20
+ art[art/]
21
+ narrative[narrative/]
22
+ ops[ops/]
23
+ shared[shared/]
24
+ end
25
+ end
26
+ product --> productSpecs[specs/]
27
+ product --> productWiki[wiki/]
28
+ technical --> techAdr[adr/]
29
+ technical --> techSpecs[specs/]
30
+ gameDesign --> gdSpecs[specs/]
31
+ shared --> sharedAgents[agents/]
32
+ shared --> sharedGlossary[glossary/]
33
+ ```
34
+
35
+ ## Domain catalog
36
+
37
+ | Domain id | Label | Scope | Typical content |
38
+ |-----------|-------|-------|-----------------|
39
+ | `product` | Product | User value, roadmap, prioritization | PRD, user stories, product specs, product open questions |
40
+ | `technical` | Technical | Engineering, architecture, delivery | ADR, API specs, infra, technical glossary |
41
+ | `game-design` | Game design | Mechanics, balance, player loops | System specs, tuning docs, design glossary |
42
+ | `analytics` | Analytics | Measurement, experiments | Event dictionaries, metric definitions, dashboard specs |
43
+ | `art` | Art | Visual identity, assets | Style guides, asset specs, UI art wiki |
44
+ | `narrative` | Narrative | Story and text | Lore bible, quest outlines, character wiki |
45
+ | `ops` | Operations | Live service, releases | Runbooks, release checklists, incident playbooks |
46
+ | `shared` | Shared | Cross-domain | Global glossary, agent instructions, project-wide wiki |
47
+
48
+ ## Type subfolders per domain
49
+
50
+ Created by `repo-mind init`:
51
+
52
+ | Domain | Subfolders |
53
+ |--------|------------|
54
+ | `product` | `specs/`, `wiki/`, `open-questions/` |
55
+ | `technical` | `adr/`, `specs/`, `glossary/`, `wiki/` |
56
+ | `game-design` | `specs/`, `glossary/`, `wiki/` |
57
+ | `analytics` | `specs/`, `wiki/` |
58
+ | `art` | `wiki/`, `specs/` |
59
+ | `narrative` | `wiki/`, `specs/` |
60
+ | `ops` | `specs/`, `wiki/` |
61
+ | `shared` | `agents/`, `glossary/`, `wiki/` |
62
+
63
+ You may add subfolders (e.g. `technical/specs/api/`) — type is inferred from the nearest matching segment (`specs` → `feature-spec`).
64
+
65
+ ## Canonical path pattern
66
+
67
+ ```
68
+ docs/{domain}/{type-folder}/{slug}.md
69
+ ```
70
+
71
+ Examples:
72
+
73
+ ```
74
+ docs/product/specs/onboarding-flow.md
75
+ docs/technical/adr/use-postgres.md
76
+ docs/game-design/specs/combat-system.md
77
+ docs/analytics/specs/event-dictionary.md
78
+ docs/shared/glossary/mcp.md
79
+ docs/shared/agents/query-first.md
80
+ docs/narrative/wiki/world-lore.md
81
+ ```
82
+
83
+ Each `{domain}/README.md` is the domain index. Each `{domain}/{type-folder}/README.md` optional when the section grows.
84
+
85
+ ## Domain × type matrix (what goes where)
86
+
87
+ | | `feature-spec` | `adr` | `glossary-term` | `open-question` | `wiki-page` | `agent-instruction` |
88
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
89
+ | **product** | PRD, feature brief | rare | product terms | prioritization | product wiki | — |
90
+ | **technical** | API, modules | **primary** | tech terms | arch tradeoffs | tech notes | — |
91
+ | **game-design** | systems, balance | design decisions | mechanic terms | tuning open Q | design wiki | — |
92
+ | **analytics** | events, metrics | — | metric defs | experiment Q | analytics wiki | — |
93
+ | **art** | asset specs | style decisions | art terms | — | moodboards wiki | — |
94
+ | **narrative** | quest specs | story decisions | lore terms | plot open Q | lore wiki | — |
95
+ | **ops** | runbook specs | ops decisions | ops terms | process Q | ops wiki | — |
96
+ | **shared** | — | — | **global glossary** | — | general wiki | **agent rules** |
97
+
98
+ Empty cells mean “unusual — prefer another domain or `wiki-page`”.
99
+
100
+ ## Legacy flat layout (supported)
101
+
102
+ Pre-domain repos may still use:
103
+
104
+ ```
105
+ docs/adr/foo.md
106
+ docs/specs/bar.md
107
+ ```
108
+
109
+ These infer `domain: shared`. Migrate gradually:
110
+
111
+ 1. Move files under `docs/{domain}/{type-folder}/`
112
+ 2. Add `domain:` to frontmatter
113
+ 3. Run `repo-mind check`
114
+
115
+ ## Structured files and assets
116
+
117
+ ```
118
+ docs/assets/ # images referenced from markdown
119
+ docs/analytics/specs/events.yaml # yaml/json — no frontmatter, domain from path
120
+ ```
121
+
122
+ ## Agent routing rules
123
+
124
+ When creating a page, decide in order:
125
+
126
+ 1. **Domain** — who owns this knowledge? (product vs technical vs game-design …)
127
+ 2. **Type** — ADR vs spec vs glossary vs wiki?
128
+ 3. **Path** — `docs/{domain}/{type-folder}/{kebab-name}.md`
129
+ 4. **Frontmatter** — `type`, `domain`, `slug`, `status`, `title`, `related`, `updated`
130
+ 5. **Links** — wikilinks in body; 2–5 strategic slugs in `related:`
131
+
132
+ Cross-domain links are normal: a game-design spec may `related: [combat-events]` pointing to an analytics spec.
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { runAbEval } from './commands/ab-eval.js';
3
3
  import { runCheck } from './commands/check.js';
4
4
  import { runExport } from './commands/export.js';
5
5
  import { runInit } from './commands/init.js';
6
+ import { runInstallSkill } from './commands/install-skill.js';
6
7
  import { runPrepare } from './commands/prepare.js';
7
8
  import { runPublish } from './commands/publish.js';
8
9
  import { runSetup } from './commands/setup.js';
@@ -15,6 +16,7 @@ function printHelp() {
15
16
  Usage:
16
17
  repo-mind init [--cwd <dir>]
17
18
  repo-mind setup [--cursor] [--claude] [--force]
19
+ repo-mind install-skill [--cwd <dir>] [--global] [--force]
18
20
  repo-mind check [--cwd <dir>]
19
21
  repo-mind export [--force] [--cwd <dir>]
20
22
  repo-mind prepare [--all] [--dry-run] [--cwd <dir>] [relative-path]
@@ -24,9 +26,13 @@ Usage:
24
26
  repo-mind mcp
25
27
  repo-mind ui [--port <n>] [--cwd <dir>]
26
28
 
29
+ Environment:
30
+ REPOMIND_UI_PASSWORD When set, protects the UI and API with password login
31
+
27
32
  Commands:
28
33
  init Scaffold docs/ with example structured pages
29
34
  setup Configure Cursor/Claude MCP and CLAUDE.md snippet
35
+ install-skill Copy repomind-docs Cursor skill into .cursor/skills/
30
36
  check Validate frontmatter schema and related links
31
37
  export Write agents.md export to repo root
32
38
  prepare Add RepoMind frontmatter to markdown files (--all for batch)
@@ -74,6 +80,10 @@ function parseArgs(argv) {
74
80
  flags.claude = true;
75
81
  continue;
76
82
  }
83
+ if (arg === '--global') {
84
+ flags.global = true;
85
+ continue;
86
+ }
77
87
  if (arg === '--cwd' && rest[i + 1]) {
78
88
  flags.cwd = rest[i + 1];
79
89
  i += 1;
@@ -176,6 +186,13 @@ async function main() {
176
186
  force: flags.force === true,
177
187
  }));
178
188
  break;
189
+ case 'install-skill':
190
+ process.exit(runInstallSkill({
191
+ cwd,
192
+ global: flags.global === true,
193
+ force: flags.force === true,
194
+ }));
195
+ break;
179
196
  case 'check':
180
197
  process.exit(runCheck({ cwd }));
181
198
  break;
@@ -0,0 +1,9 @@
1
+ export declare const SKILL_NAME = "repomind-docs";
2
+ export declare function resolveSkillSourceDir(): string;
3
+ export interface InstallSkillOptions {
4
+ cwd?: string;
5
+ global?: boolean;
6
+ force?: boolean;
7
+ }
8
+ export declare function resolveSkillDestDir(options?: InstallSkillOptions): string;
9
+ export declare function runInstallSkill(options?: InstallSkillOptions): number;
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ export const SKILL_NAME = 'repomind-docs';
6
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
7
+ export function resolveSkillSourceDir() {
8
+ return path.join(PACKAGE_ROOT, '.cursor', 'skills', SKILL_NAME);
9
+ }
10
+ export function resolveSkillDestDir(options = {}) {
11
+ const cwd = path.resolve(options.cwd ?? process.cwd());
12
+ if (options.global) {
13
+ return path.join(os.homedir(), '.cursor', 'skills', SKILL_NAME);
14
+ }
15
+ return path.join(cwd, '.cursor', 'skills', SKILL_NAME);
16
+ }
17
+ export function runInstallSkill(options = {}) {
18
+ const source = resolveSkillSourceDir();
19
+ const dest = resolveSkillDestDir(options);
20
+ if (!fs.existsSync(source)) {
21
+ console.error(`error: skill source not found at ${source}`);
22
+ return 1;
23
+ }
24
+ if (fs.existsSync(dest) && !options.force) {
25
+ console.warn(`warning: ${dest} already exists (use --force to replace)`);
26
+ return 0;
27
+ }
28
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
29
+ if (fs.existsSync(dest)) {
30
+ fs.rmSync(dest, { recursive: true, force: true });
31
+ }
32
+ fs.cpSync(source, dest, { recursive: true });
33
+ console.log(`Installed ${SKILL_NAME} skill: ${dest}`);
34
+ return 0;
35
+ }
@@ -5,6 +5,7 @@ import { closeAllDocsEventStreams } from '../ui/api-handlers.js';
5
5
  import { DocsWatcher } from '../ui/docs-watcher.js';
6
6
  import { openDraftsDb } from '../ui/db/drafts-db.js';
7
7
  import { destroyUiServerConnections, resolveUiStaticDir, startUiServer } from '../ui/server.js';
8
+ import { createOptionalUiAuth } from '../ui/auth.js';
8
9
  export async function runUi(options = {}) {
9
10
  const cwd = path.resolve(options.cwd ?? process.cwd());
10
11
  const port = options.port ?? 3847;
@@ -22,6 +23,7 @@ export async function runUi(options = {}) {
22
23
  const draftsDb = openDraftsDb(index.getKnowledgeRoot());
23
24
  const docsWatcher = new DocsWatcher(index);
24
25
  docsWatcher.start(index.getKnowledgeRoot());
26
+ const auth = createOptionalUiAuth();
25
27
  const server = await startUiServer({
26
28
  host: '127.0.0.1',
27
29
  port,
@@ -29,10 +31,12 @@ export async function runUi(options = {}) {
29
31
  staticDir,
30
32
  draftsDb,
31
33
  docsWatcher,
34
+ auth,
32
35
  });
33
36
  const docCount = index.refresh().length;
34
37
  const draftCount = draftsDb.listActive().length;
35
- console.log(`RepoMind UI at http://127.0.0.1:${port} (${docCount} docs, ${draftCount} drafts)`);
38
+ const authNote = auth ? ' · password auth enabled' : '';
39
+ console.log(`RepoMind UI at http://127.0.0.1:${port} (${docCount} docs, ${draftCount} drafts${authNote})`);
36
40
  console.log('Press Ctrl+C to stop');
37
41
  await new Promise((resolve) => {
38
42
  let shuttingDown = false;
@@ -0,0 +1,21 @@
1
+ import type { IncomingHttpHeaders } from 'node:http';
2
+ export interface UiAuthOptions {
3
+ password: string;
4
+ }
5
+ export interface UiAuth {
6
+ isEnabled: () => boolean;
7
+ isAuthenticated: (headers: IncomingHttpHeaders) => boolean;
8
+ login: (password: string) => string | null;
9
+ logout: (headers: IncomingHttpHeaders) => void;
10
+ createSessionCookie: (token: string) => string;
11
+ clearSessionCookie: () => string;
12
+ }
13
+ export interface AuthApiResponse {
14
+ status: number;
15
+ body: unknown;
16
+ setCookie?: string;
17
+ clearCookie?: boolean;
18
+ }
19
+ export declare function createUiAuth(options: UiAuthOptions): UiAuth;
20
+ export declare function createOptionalUiAuth(): UiAuth | null;
21
+ export declare function handleAuthApi(auth: UiAuth, method: string, urlPath: string, bodyRaw: string, headers: IncomingHttpHeaders): AuthApiResponse | null;
@@ -0,0 +1,117 @@
1
+ import crypto from 'node:crypto';
2
+ const SESSION_COOKIE = 'repomind_session';
3
+ const SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
4
+ function parseCookies(header) {
5
+ if (!header) {
6
+ return {};
7
+ }
8
+ const cookies = {};
9
+ for (const part of header.split(';')) {
10
+ const [key, ...rest] = part.split('=');
11
+ const name = key?.trim();
12
+ if (name) {
13
+ cookies[name] = decodeURIComponent(rest.join('=').trim());
14
+ }
15
+ }
16
+ return cookies;
17
+ }
18
+ function safeEqual(a, b) {
19
+ const bufA = Buffer.from(a);
20
+ const bufB = Buffer.from(b);
21
+ if (bufA.length !== bufB.length) {
22
+ crypto.timingSafeEqual(bufA, bufA);
23
+ return false;
24
+ }
25
+ return crypto.timingSafeEqual(bufA, bufB);
26
+ }
27
+ export function createUiAuth(options) {
28
+ const sessions = new Map();
29
+ function pruneSessions() {
30
+ const now = Date.now();
31
+ for (const [token, expiresAt] of sessions) {
32
+ if (expiresAt <= now) {
33
+ sessions.delete(token);
34
+ }
35
+ }
36
+ }
37
+ return {
38
+ isEnabled: () => true,
39
+ isAuthenticated(headers) {
40
+ pruneSessions();
41
+ const token = parseCookies(headers.cookie)[SESSION_COOKIE];
42
+ if (!token) {
43
+ return false;
44
+ }
45
+ const expiresAt = sessions.get(token);
46
+ if (!expiresAt || expiresAt <= Date.now()) {
47
+ sessions.delete(token);
48
+ return false;
49
+ }
50
+ return true;
51
+ },
52
+ login(password) {
53
+ if (!safeEqual(password, options.password)) {
54
+ return null;
55
+ }
56
+ const token = crypto.randomBytes(32).toString('base64url');
57
+ sessions.set(token, Date.now() + SESSION_MAX_AGE_MS);
58
+ return token;
59
+ },
60
+ logout(headers) {
61
+ const token = parseCookies(headers.cookie)[SESSION_COOKIE];
62
+ if (token) {
63
+ sessions.delete(token);
64
+ }
65
+ },
66
+ createSessionCookie(token) {
67
+ const maxAge = Math.floor(SESSION_MAX_AGE_MS / 1000);
68
+ return `${SESSION_COOKIE}=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`;
69
+ },
70
+ clearSessionCookie() {
71
+ return `${SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
72
+ },
73
+ };
74
+ }
75
+ export function createOptionalUiAuth() {
76
+ const password = process.env.REPOMIND_UI_PASSWORD?.trim();
77
+ if (!password) {
78
+ return null;
79
+ }
80
+ return createUiAuth({ password });
81
+ }
82
+ export function handleAuthApi(auth, method, urlPath, bodyRaw, headers) {
83
+ if (urlPath === '/api/auth/session' && method === 'GET') {
84
+ return {
85
+ status: 200,
86
+ body: { authenticated: auth.isAuthenticated(headers), required: true },
87
+ };
88
+ }
89
+ if (urlPath === '/api/auth/login' && method === 'POST') {
90
+ let password = '';
91
+ try {
92
+ const parsed = JSON.parse(bodyRaw);
93
+ password = typeof parsed.password === 'string' ? parsed.password : '';
94
+ }
95
+ catch {
96
+ return { status: 400, body: { error: 'invalid JSON body' } };
97
+ }
98
+ const token = auth.login(password);
99
+ if (!token) {
100
+ return { status: 401, body: { error: 'invalid password' } };
101
+ }
102
+ return {
103
+ status: 200,
104
+ body: { authenticated: true },
105
+ setCookie: auth.createSessionCookie(token),
106
+ };
107
+ }
108
+ if (urlPath === '/api/auth/logout' && method === 'POST') {
109
+ auth.logout(headers);
110
+ return {
111
+ status: 200,
112
+ body: { authenticated: false },
113
+ clearCookie: true,
114
+ };
115
+ }
116
+ return null;
117
+ }
@@ -49,7 +49,7 @@ export interface PromotePageResult extends FsPageMutationResult {
49
49
  }
50
50
  export declare function createFolder(index: DocIndex, parentPath: string, name: string): CreateFolderResult;
51
51
  export declare function createPageFile(index: DocIndex, parentPath: string, name: string, options?: CreatePageOptions): CreatePageResult;
52
- /** Creates sibling folder `{parent}/{basename}/` for a leaf page (Confluence-style); page file stays in place. */
52
+ /** Creates `{parent}/{basename}/README.md` from a leaf page file. */
53
53
  export declare function promotePageToFolder(index: DocIndex, pagePath: string): PromotePageResult;
54
54
  export declare function movePageFile(index: DocIndex, fromPath: string, toDir: string): FsPageMutationResult;
55
55
  export declare function renamePageFile(index: DocIndex, pagePath: string, newName: string): FsPageMutationResult;