@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.
- package/.cursor/skills/repomind-docs/SKILL.md +160 -0
- package/.cursor/skills/repomind-docs/examples.md +429 -0
- package/.cursor/skills/repomind-docs/reference.md +209 -0
- package/.cursor/skills/repomind-docs/structure.md +132 -0
- package/dist/cli.js +17 -0
- package/dist/commands/install-skill.d.ts +9 -0
- package/dist/commands/install-skill.js +35 -0
- package/dist/commands/ui.js +5 -1
- package/dist/ui/auth.d.ts +21 -0
- package/dist/ui/auth.js +117 -0
- package/dist/ui/fs-operations.d.ts +1 -1
- package/dist/ui/fs-operations.js +36 -4
- package/dist/ui/fs-tree.d.ts +5 -4
- package/dist/ui/fs-tree.js +60 -56
- package/dist/ui/server.d.ts +2 -0
- package/dist/ui/server.js +63 -2
- package/package.json +3 -2
- package/ui/dist/assets/{arc-C1R_fyW-.js → arc-BitGDA7a.js} +1 -1
- package/ui/dist/assets/{architectureDiagram-3BPJPVTR-CcuF3FYc.js → architectureDiagram-3BPJPVTR-BmY2OtU5.js} +1 -1
- package/ui/dist/assets/{blockDiagram-GPEHLZMM-BZJKe3Ei.js → blockDiagram-GPEHLZMM-btai_20t.js} +1 -1
- package/ui/dist/assets/{c4Diagram-AAUBKEIU-D_0fRNGS.js → c4Diagram-AAUBKEIU-CN-pXtFe.js} +1 -1
- package/ui/dist/assets/channel-D1plp4bS.js +1 -0
- package/ui/dist/assets/{chunk-2J33WTMH-0LcH4vIA.js → chunk-2J33WTMH-CUgwCKKP.js} +1 -1
- package/ui/dist/assets/{chunk-4BX2VUAB-DU1h67AD.js → chunk-4BX2VUAB-ZXi72_qf.js} +1 -1
- package/ui/dist/assets/{chunk-55IACEB6-BCx7dyYV.js → chunk-55IACEB6-BvoNhEcH.js} +1 -1
- package/ui/dist/assets/{chunk-727SXJPM-CRO5yOrB.js → chunk-727SXJPM-CPVv-LcP.js} +1 -1
- package/ui/dist/assets/{chunk-AQP2D5EJ-BayHdr67.js → chunk-AQP2D5EJ-15HMmfKe.js} +1 -1
- package/ui/dist/assets/{chunk-FMBD7UC4-CJemLien.js → chunk-FMBD7UC4-CTJu1mOO.js} +1 -1
- package/ui/dist/assets/{chunk-ND2GUHAM-B_z-tw44.js → chunk-ND2GUHAM-BaS37xMv.js} +1 -1
- package/ui/dist/assets/{chunk-QZHKN3VN-D40rAh_S.js → chunk-QZHKN3VN-CzNZR3D9.js} +1 -1
- package/ui/dist/assets/classDiagram-4FO5ZUOK-BsDWU9KJ.js +1 -0
- package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-BsDWU9KJ.js +1 -0
- package/ui/dist/assets/{cose-bilkent-S5V4N54A-Cqthtrrq.js → cose-bilkent-S5V4N54A-mpC0WnuY.js} +1 -1
- package/ui/dist/assets/{dagre-BM42HDAG-CrxsyXEa.js → dagre-BM42HDAG-DVbZMTFr.js} +1 -1
- package/ui/dist/assets/{diagram-2AECGRRQ-DTYA7Rrn.js → diagram-2AECGRRQ-BvYsIaeC.js} +1 -1
- package/ui/dist/assets/{diagram-5GNKFQAL-CXs7RV13.js → diagram-5GNKFQAL-mM_4t2hT.js} +1 -1
- package/ui/dist/assets/{diagram-KO2AKTUF-Bf8EeV_S.js → diagram-KO2AKTUF-u5t0FZtc.js} +1 -1
- package/ui/dist/assets/{diagram-LMA3HP47-C2XWB0cI.js → diagram-LMA3HP47-Ngnghl4t.js} +1 -1
- package/ui/dist/assets/{diagram-OG6HWLK6-BNMXob9h.js → diagram-OG6HWLK6-DxCLvBmh.js} +1 -1
- package/ui/dist/assets/{erDiagram-TEJ5UH35-CnSczOvb.js → erDiagram-TEJ5UH35-Cblf5xKO.js} +1 -1
- package/ui/dist/assets/{flowDiagram-I6XJVG4X-DwZBJ2fr.js → flowDiagram-I6XJVG4X-3aVBpTd3.js} +1 -1
- package/ui/dist/assets/{ganttDiagram-6RSMTGT7-rxwvD8xn.js → ganttDiagram-6RSMTGT7-CCk8vbkn.js} +1 -1
- package/ui/dist/assets/{gitGraphDiagram-PVQCEYII-DjJPiQ2K.js → gitGraphDiagram-PVQCEYII-DLUIS6md.js} +1 -1
- package/ui/dist/assets/graph-DwaX8nDF.js +2 -0
- package/ui/dist/assets/{infoDiagram-5YYISTIA-BHgtQpBK.js → infoDiagram-5YYISTIA-C6umdFYV.js} +1 -1
- package/ui/dist/assets/{ishikawaDiagram-YF4QCWOH-BZ38ndde.js → ishikawaDiagram-YF4QCWOH-H8wERm8_.js} +1 -1
- package/ui/dist/assets/{journeyDiagram-JHISSGLW-CNaZf5VN.js → journeyDiagram-JHISSGLW-DnTRQ6Mn.js} +1 -1
- package/ui/dist/assets/{kanban-definition-UN3LZRKU-4h1Jmt_X.js → kanban-definition-UN3LZRKU-DrVEsZSL.js} +1 -1
- package/ui/dist/assets/main-C0Y60ppL.js +269 -0
- package/ui/dist/assets/{mermaid.core-BZZdRyuR.js → mermaid.core-BtOBEW5j.js} +4 -4
- package/ui/dist/assets/{mindmap-definition-RKZ34NQL-DG_NciLX.js → mindmap-definition-RKZ34NQL-BFEjh9BY.js} +1 -1
- package/ui/dist/assets/{pieDiagram-4H26LBE5-B9FyPCLY.js → pieDiagram-4H26LBE5-KLu3myN4.js} +1 -1
- package/ui/dist/assets/{quadrantDiagram-W4KKPZXB-B89WFfQu.js → quadrantDiagram-W4KKPZXB-DwSP1nfl.js} +1 -1
- package/ui/dist/assets/{requirementDiagram-4Y6WPE33-BYHKePgB.js → requirementDiagram-4Y6WPE33-CTe59AWj.js} +1 -1
- package/ui/dist/assets/{sankeyDiagram-5OEKKPKP-BW6YuBQM.js → sankeyDiagram-5OEKKPKP-DBG4pZal.js} +1 -1
- package/ui/dist/assets/{sequenceDiagram-3UESZ5HK-BnojqKwf.js → sequenceDiagram-3UESZ5HK-BV9E_Iae.js} +1 -1
- package/ui/dist/assets/{stateDiagram-AJRCARHV-C_x9t2o6.js → stateDiagram-AJRCARHV-BWnAUqpA.js} +1 -1
- package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-yNXWOXzs.js +1 -0
- package/ui/dist/assets/theme-CfkLgQFf.js +21 -0
- package/ui/dist/assets/theme-DJYNWCef.css +1 -0
- package/ui/dist/assets/{timeline-definition-PNZ67QCA-CI8Ngfzv.js → timeline-definition-PNZ67QCA-DDoGl25M.js} +1 -1
- package/ui/dist/assets/{vennDiagram-CIIHVFJN-CPRKGc2P.js → vennDiagram-CIIHVFJN-DVRPIJjW.js} +1 -1
- package/ui/dist/assets/{visual-editor-DUVPRlY3.js → visual-editor-COzWiJ8C.js} +12 -12
- package/ui/dist/assets/{wardley-L42UT6IY-CZY42jFT.js → wardley-L42UT6IY-Li08EMdE.js} +1 -1
- package/ui/dist/assets/{wardleyDiagram-YWT4CUSO-CGKyWKnw.js → wardleyDiagram-YWT4CUSO-BA2E9m1f.js} +1 -1
- package/ui/dist/assets/{xychartDiagram-2RQKCTM6-Cq-onAc3.js → xychartDiagram-2RQKCTM6-DKHRvUg5.js} +1 -1
- package/ui/dist/graph.html +3 -3
- package/ui/dist/index.html +3 -3
- package/ui/dist/assets/channel-BlWnHbuq.js +0 -1
- package/ui/dist/assets/classDiagram-4FO5ZUOK-D3OdfPqd.js +0 -1
- package/ui/dist/assets/classDiagram-v2-Q7XG4LA2-D3OdfPqd.js +0 -1
- package/ui/dist/assets/graph-D_tIaE_I.js +0 -2
- package/ui/dist/assets/main-BeHrzLrS.js +0 -273
- package/ui/dist/assets/stateDiagram-v2-BHNVJYJU-CBZx8gKT.js +0 -1
- package/ui/dist/assets/theme-CFHWfrn1.css +0 -1
- 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
|
+
}
|
package/dist/commands/ui.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/ui/auth.js
ADDED
|
@@ -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
|
|
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;
|