@magic-spells/constellation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +147 -0
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.js +127 -0
  5. package/dist/cli/index.js.map +1 -0
  6. package/dist/core/extract.d.ts +9 -0
  7. package/dist/core/extract.js +56 -0
  8. package/dist/core/extract.js.map +1 -0
  9. package/dist/core/handles.d.ts +13 -0
  10. package/dist/core/handles.js +45 -0
  11. package/dist/core/handles.js.map +1 -0
  12. package/dist/core/index.d.ts +8 -0
  13. package/dist/core/index.js +9 -0
  14. package/dist/core/index.js.map +1 -0
  15. package/dist/core/indexer.d.ts +3 -0
  16. package/dist/core/indexer.js +209 -0
  17. package/dist/core/indexer.js.map +1 -0
  18. package/dist/core/lint.d.ts +9 -0
  19. package/dist/core/lint.js +19 -0
  20. package/dist/core/lint.js.map +1 -0
  21. package/dist/core/parse.d.ts +7 -0
  22. package/dist/core/parse.js +19 -0
  23. package/dist/core/parse.js.map +1 -0
  24. package/dist/core/resolve.d.ts +12 -0
  25. package/dist/core/resolve.js +57 -0
  26. package/dist/core/resolve.js.map +1 -0
  27. package/dist/core/scaffold.d.ts +3 -0
  28. package/dist/core/scaffold.js +36 -0
  29. package/dist/core/scaffold.js.map +1 -0
  30. package/dist/core/types.d.ts +50 -0
  31. package/dist/core/types.js +5 -0
  32. package/dist/core/types.js.map +1 -0
  33. package/dist/core/validate.d.ts +6 -0
  34. package/dist/core/validate.js +64 -0
  35. package/dist/core/validate.js.map +1 -0
  36. package/dist/core/writer.d.ts +34 -0
  37. package/dist/core/writer.js +170 -0
  38. package/dist/core/writer.js.map +1 -0
  39. package/dist/mcp/git.d.ts +34 -0
  40. package/dist/mcp/git.js +172 -0
  41. package/dist/mcp/git.js.map +1 -0
  42. package/dist/mcp/search.d.ts +11 -0
  43. package/dist/mcp/search.js +52 -0
  44. package/dist/mcp/search.js.map +1 -0
  45. package/dist/mcp/server.d.ts +7 -0
  46. package/dist/mcp/server.js +684 -0
  47. package/dist/mcp/server.js.map +1 -0
  48. package/dist/serve/server.d.ts +12 -0
  49. package/dist/serve/server.js +281 -0
  50. package/dist/serve/server.js.map +1 -0
  51. package/docs/001-file-format.md +222 -0
  52. package/docs/002-mcp.md +109 -0
  53. package/examples/constellation/agent/AGENT-CODE-STYLE.md +14 -0
  54. package/examples/constellation/api/API-TICKETS.md +24 -0
  55. package/examples/constellation/component/COMPONENT-TICKET-CARD.md +16 -0
  56. package/examples/constellation/datatype/DATATYPE-CREATE-TICKET-INPUT.md +15 -0
  57. package/examples/constellation/datatype/DATATYPE-TICKET.md +20 -0
  58. package/examples/constellation/db/DB-TICKETS.md +21 -0
  59. package/examples/constellation/diagram/DIAGRAM-SYSTEM-OVERVIEW.md +19 -0
  60. package/examples/constellation/doc/DOC-TICKET-LIFECYCLE.md +22 -0
  61. package/examples/constellation/event/EVENT-TICKET-CREATED.md +13 -0
  62. package/examples/constellation/external/EXTERNAL-EMAIL-PROVIDER.md +13 -0
  63. package/examples/constellation/file/FILE-TICKETS-ROUTE.md +12 -0
  64. package/examples/constellation/flow/FLOW-CREATE-TICKET.md +16 -0
  65. package/examples/constellation/job/JOB-AUTO-ASSIGN.md +15 -0
  66. package/examples/constellation/page/PAGE-INBOX.md +19 -0
  67. package/examples/constellation/plan.md +23 -0
  68. package/examples/constellation/role/ROLE-SUPPORT-AGENT.md +12 -0
  69. package/examples/constellation/state/STATE-TICKET.md +31 -0
  70. package/examples/constellation/test/TEST-CREATE-TICKET.md +17 -0
  71. package/package.json +80 -0
  72. package/schemas/agent.json +21 -0
  73. package/schemas/api.json +47 -0
  74. package/schemas/card.json +37 -0
  75. package/schemas/component.json +40 -0
  76. package/schemas/datatype.json +8 -0
  77. package/schemas/db.json +51 -0
  78. package/schemas/diagram.json +95 -0
  79. package/schemas/doc.json +8 -0
  80. package/schemas/event.json +28 -0
  81. package/schemas/external.json +24 -0
  82. package/schemas/file.json +22 -0
  83. package/schemas/flow.json +20 -0
  84. package/schemas/job.json +25 -0
  85. package/schemas/page.json +36 -0
  86. package/schemas/plan.json +13 -0
  87. package/schemas/role.json +18 -0
  88. package/schemas/state.json +35 -0
  89. package/schemas/test.json +13 -0
  90. package/skill/SKILL.md +105 -0
  91. package/skill/types/agent.md +26 -0
  92. package/skill/types/api.md +38 -0
  93. package/skill/types/component.md +30 -0
  94. package/skill/types/datatype.md +26 -0
  95. package/skill/types/db.md +33 -0
  96. package/skill/types/diagram.md +32 -0
  97. package/skill/types/doc.md +23 -0
  98. package/skill/types/event.md +31 -0
  99. package/skill/types/external.md +30 -0
  100. package/skill/types/file.md +28 -0
  101. package/skill/types/flow.md +29 -0
  102. package/skill/types/job.md +27 -0
  103. package/skill/types/page.md +28 -0
  104. package/skill/types/plan.md +37 -0
  105. package/skill/types/role.md +25 -0
  106. package/skill/types/state.md +38 -0
  107. package/skill/types/test.md +28 -0
  108. package/viewer/dist/assets/arc-Kj6pF3JI.js +1 -0
  109. package/viewer/dist/assets/architecture-7EHR7CIX-CGfWeim3.js +1 -0
  110. package/viewer/dist/assets/architectureDiagram-3BPJPVTR-C5bZdErB.js +36 -0
  111. package/viewer/dist/assets/array-BifhSqXX.js +1 -0
  112. package/viewer/dist/assets/blockDiagram-GPEHLZMM-C1Q6l6fE.js +132 -0
  113. package/viewer/dist/assets/c4Diagram-AAUBKEIU-BmM6Tmtq.js +10 -0
  114. package/viewer/dist/assets/channel-19IdUS_c.js +1 -0
  115. package/viewer/dist/assets/chunk-2J33WTMH-z09tLTpZ.js +1 -0
  116. package/viewer/dist/assets/chunk-3OPIFGDE-BynpXh1r.js +62 -0
  117. package/viewer/dist/assets/chunk-4BX2VUAB-CDOVuPyG.js +1 -0
  118. package/viewer/dist/assets/chunk-55IACEB6-nBwigOgn.js +1 -0
  119. package/viewer/dist/assets/chunk-5ZQYHXKU-Bxe5xIy_.js +2 -0
  120. package/viewer/dist/assets/chunk-727SXJPM-DZmTgL68.js +206 -0
  121. package/viewer/dist/assets/chunk-AQP2D5EJ-B7wr_Owx.js +231 -0
  122. package/viewer/dist/assets/chunk-BSJP7CBP-DbAKfVCK.js +1 -0
  123. package/viewer/dist/assets/chunk-CSCIHK7Q-C0rsBwqP.js +124 -0
  124. package/viewer/dist/assets/chunk-FMBD7UC4-BAtzt0wv.js +15 -0
  125. package/viewer/dist/assets/chunk-KSCS5N6A-CXXwf52I.js +10 -0
  126. package/viewer/dist/assets/chunk-L5ZTLDWV-DS4vRI1U.js +1 -0
  127. package/viewer/dist/assets/chunk-LZXEDZCA-CclT9MXr.js +2 -0
  128. package/viewer/dist/assets/chunk-ND2GUHAM-CUSnPl8t.js +1 -0
  129. package/viewer/dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
  130. package/viewer/dist/assets/chunk-NZK2D7GU-Dh986nJk.js +1 -0
  131. package/viewer/dist/assets/chunk-O5CBEL6O-JAEZ_pS6.js +70 -0
  132. package/viewer/dist/assets/chunk-QZHKN3VN-BKc_Kg2Z.js +1 -0
  133. package/viewer/dist/assets/chunk-WU5MYG2G-9ssTSMzt.js +1 -0
  134. package/viewer/dist/assets/chunk-XPW4576I-BwMZI0gv.js +32 -0
  135. package/viewer/dist/assets/classDiagram-4FO5ZUOK-DXv85WFd.js +1 -0
  136. package/viewer/dist/assets/classDiagram-v2-Q7XG4LA2-DXv85WFd.js +1 -0
  137. package/viewer/dist/assets/cose-bilkent-S5V4N54A-NGC7gYHM.js +1 -0
  138. package/viewer/dist/assets/cytoscape.esm-h6BdjjI9.js +321 -0
  139. package/viewer/dist/assets/dagre-BM42HDAG-RD63uyvd.js +4 -0
  140. package/viewer/dist/assets/dagre-Bx709z4p.js +1 -0
  141. package/viewer/dist/assets/defaultLocale-C8Fc0cco.js +1 -0
  142. package/viewer/dist/assets/diagram-2AECGRRQ-hwnqqCcb.js +43 -0
  143. package/viewer/dist/assets/diagram-5GNKFQAL-q8EaoZSG.js +10 -0
  144. package/viewer/dist/assets/diagram-KO2AKTUF-D4_5Qf-l.js +3 -0
  145. package/viewer/dist/assets/diagram-LMA3HP47-D8pwekFs.js +24 -0
  146. package/viewer/dist/assets/diagram-OG6HWLK6-D9KinIWZ.js +24 -0
  147. package/viewer/dist/assets/dist-CFOOgrqc.js +1 -0
  148. package/viewer/dist/assets/erDiagram-TEJ5UH35-D0Wfq250.js +85 -0
  149. package/viewer/dist/assets/eventmodeling-FCH6USID-D3KRSuC1.js +1 -0
  150. package/viewer/dist/assets/flowDiagram-I6XJVG4X-Y2DY-Ze2.js +162 -0
  151. package/viewer/dist/assets/ganttDiagram-6RSMTGT7-BnqkeLVw.js +292 -0
  152. package/viewer/dist/assets/gitGraph-WXDBUCRP-Cft7usRT.js +1 -0
  153. package/viewer/dist/assets/gitGraphDiagram-PVQCEYII-D-cYtraK.js +106 -0
  154. package/viewer/dist/assets/graphlib-B8gBHxth.js +1 -0
  155. package/viewer/dist/assets/index-CDR-riG2.css +2 -0
  156. package/viewer/dist/assets/index-DRPsTWe2.js +98 -0
  157. package/viewer/dist/assets/info-J43DQDTF-Djc8Bx3F.js +1 -0
  158. package/viewer/dist/assets/infoDiagram-5YYISTIA-D-ehtyyJ.js +2 -0
  159. package/viewer/dist/assets/init-D6jRqBbL.js +1 -0
  160. package/viewer/dist/assets/ishikawaDiagram-YF4QCWOH-Ct3f6bH-.js +70 -0
  161. package/viewer/dist/assets/journeyDiagram-JHISSGLW-DXlULEmi.js +139 -0
  162. package/viewer/dist/assets/kanban-definition-UN3LZRKU-3vE9h-R7.js +89 -0
  163. package/viewer/dist/assets/katex-Vhh-h91d.js +257 -0
  164. package/viewer/dist/assets/line-B8MygbLB.js +1 -0
  165. package/viewer/dist/assets/linear-CfMuM0B3.js +1 -0
  166. package/viewer/dist/assets/mermaid-parser.core-DzlZTbbh.js +4 -0
  167. package/viewer/dist/assets/mermaid.core-IM-sPiyq.js +9 -0
  168. package/viewer/dist/assets/mindmap-definition-RKZ34NQL-CMnpAq1T.js +96 -0
  169. package/viewer/dist/assets/ordinal-hYBb2elL.js +1 -0
  170. package/viewer/dist/assets/packet-YPE3B663-D44AzgHh.js +1 -0
  171. package/viewer/dist/assets/path-BWPyau1x.js +1 -0
  172. package/viewer/dist/assets/pie-LRSECV5Y-DL8AVJH_.js +1 -0
  173. package/viewer/dist/assets/pieDiagram-4H26LBE5-FvKK5jd7.js +30 -0
  174. package/viewer/dist/assets/quadrantDiagram-W4KKPZXB-CmjSkU8c.js +7 -0
  175. package/viewer/dist/assets/radar-GUYGQ44K-BvfZTVyH.js +1 -0
  176. package/viewer/dist/assets/requirementDiagram-4Y6WPE33-BOjca3VH.js +84 -0
  177. package/viewer/dist/assets/rough.esm-CSKSodPl.js +1 -0
  178. package/viewer/dist/assets/sankeyDiagram-5OEKKPKP-ANcjfNix.js +40 -0
  179. package/viewer/dist/assets/sequenceDiagram-3UESZ5HK-BLQ9AL7I.js +162 -0
  180. package/viewer/dist/assets/src-CAMdANUp.js +1 -0
  181. package/viewer/dist/assets/stateDiagram-AJRCARHV-D6CriBS6.js +1 -0
  182. package/viewer/dist/assets/stateDiagram-v2-BHNVJYJU-DcTp66RQ.js +1 -0
  183. package/viewer/dist/assets/timeline-definition-PNZ67QCA-BNhWZ_DL.js +120 -0
  184. package/viewer/dist/assets/treeView-BLDUP644-CY6Ph5Pu.js +1 -0
  185. package/viewer/dist/assets/treemap-LRROVOQU-DChSA_Qx.js +1 -0
  186. package/viewer/dist/assets/vennDiagram-CIIHVFJN-C01WznAC.js +34 -0
  187. package/viewer/dist/assets/wardley-L42UT6IY-BJ8uNoJu.js +1 -0
  188. package/viewer/dist/assets/wardleyDiagram-YWT4CUSO-DwDEzlVm.js +78 -0
  189. package/viewer/dist/assets/xychartDiagram-2RQKCTM6-BCvIDwU0.js +7 -0
  190. package/viewer/dist/index.html +20 -0
@@ -0,0 +1,109 @@
1
+ # Constellation MCP (v2)
2
+
3
+ The MCP server gives AI agents graph queries, hydrated retrieval, validated writes,
4
+ and git-powered change tracking over a plan folder. It is a thin layer over
5
+ `src/core/`: every tool call reloads the index from disk (tens of milliseconds at
6
+ realistic plan sizes), so it is always correct while files are being edited in
7
+ parallel — no watcher, no cache invalidation.
8
+
9
+ Entry point: `constellation mcp` (stdio). Bootstrap is folder discovery: the server
10
+ walks up from the working directory to find `constellation/`, **bounded by the repo
11
+ root** (the first ancestor with `.git`) — it never crosses into another repo's plan,
12
+ so a repo with no plan returns `NO_PLAN_FOUND` rather than silently adopting a
13
+ sibling's. No accounts, no tokens, no settings handshake. When no plan exists yet,
14
+ `init_plan` scaffolds `constellation/` + starter `plan.md` (same shared scaffold the
15
+ CLI `constellation init` uses); every other tool works immediately after — no restart.
16
+
17
+ ## Vocabulary
18
+
19
+ **Card** (a file in the plan), **connection** (an undirected link between two cards).
20
+ Tool responses use `connected_cards`, never `connected_nodes`.
21
+
22
+ ## Hydrated retrieval (core requirement)
23
+
24
+ Any read tool can return connected cards **with their full data** in one call. Three
25
+ levels, chosen by the caller because the cost is context tokens, not compute:
26
+
27
+ - `none` — the card(s) only
28
+ - `summary` — connected cards as `{ handle, type, kind, name, status }`
29
+ - `full` — connected cards with complete `frontmatter` and `body`
30
+
31
+ The acceptance test: one `get_card` call returns an API card plus the complete
32
+ content of every card connected to it (its datatypes, table, tests, docs).
33
+
34
+ ## Tools
35
+
36
+ ### Read
37
+
38
+ | Tool | Input | Returns |
39
+ |---|---|---|
40
+ | `get_card` | `handle`, `connected?` (default `summary`) | `{ card, connected_cards }` |
41
+ | `list_cards` | `types?`, `kind?`, `status?`, `connected?`, `limit?` | `{ cards: summary[], total }` (`connected:false` → orphans only) |
42
+ | `search` | `q`, `types?`, `limit?` (20), `connected?` (default `none`) | `{ matches: [{ card, score, excerpt, connected_cards? }] }` |
43
+ | `traverse` | `start` (handle or array), `depth?` (2, max 5), `types?`, `detail?` (`summary`/`full`) | `{ cards: [{ …, distance }], connections }` |
44
+
45
+ `search` scoring: handle match ≫ name match > kind/type match > body occurrences;
46
+ excerpt is the first matching body line. Seeding `traverse` with multiple handles
47
+ (e.g. the output of `diff_plan`) is the impact-analysis pattern.
48
+
49
+ ### Write
50
+
51
+ | Tool | Input | Notes |
52
+ |---|---|---|
53
+ | `create_card` | `handle`, `name?`, `kind?`, `status?`, `connections?`, `fields?`, `body?`, `validate?` | Writes `<folder>/<HANDLE>.md`; `fields` = type-specific frontmatter. `validate:false` skips lint (bulk import) |
54
+ | `create_cards` | `cards: [...]` (1–500) | Bulk create: validates all up front, writes all, lints ONCE → intra-batch references resolve. `{ created, failed, cards, issues }` |
55
+ | `update_card` | `handle`, `patch?`, `body?` | See merge semantics below |
56
+ | `delete_card` | `handle` | Returns `referenced_by` so the caller can clean up |
57
+ | `add_connection` | `from`, `to` | Appends to `from`'s connections (no-op if already connected from any source) |
58
+ | `add_connections` | `connections: [{from,to}]` (1–1000) | Bulk connect, idempotent/undirected, one lint pass. `{ added, failed, errors }` |
59
+ | `remove_connection` | `a`, `b` | Removes from either card's list; reports if the connection survives via other sources (frontmatter field, body link, mermaid) |
60
+
61
+ Every write reloads the index, lints, and returns the issues touching the written
62
+ file — an agent finds out immediately if it created a dangling reference. **A card
63
+ is created even when issues are returned** — `issues` are the current lint state,
64
+ not a failure. For migrations, prefer `create_cards` + `add_connections` (batched,
65
+ one lint pass, no transient dangling-reference noise) or `create_card validate:false`
66
+ followed by a single `check_integrity`.
67
+
68
+ **Merge semantics for `update_card`:** `patch.name/kind/status` set (or delete with
69
+ `null`); `patch.connections` replaces the list wholesale (use add/remove_connection
70
+ for incremental edits); `patch.fields` deep-merges into type-specific frontmatter —
71
+ objects merge recursively, arrays replace, `null` deletes a key. `body` replaces the
72
+ whole body.
73
+
74
+ **Formatting preservation:** a body-only update never touches frontmatter bytes.
75
+ A frontmatter update re-serializes **only the top-level keys whose values
76
+ changed** — unchanged keys keep their original text byte-for-byte (flow-style
77
+ maps, comments, and all), so a status flip is a one-line diff. Key order is
78
+ preserved; new keys append. Only a changed key's value normalizes to canonical
79
+ YAML style.
80
+
81
+ ### Git
82
+
83
+ | Tool | Input | Returns |
84
+ |---|---|---|
85
+ | `diff_plan` | `base?`, `head?` | `{ base, base_source, head, changes: [{ handle, file, change, changed_keys?, body_changed? }] }` |
86
+ | `plan_log` | `handle`, `limit?` (20) | commits touching that card |
87
+ | `set_sync_point` | `sha?` (default `HEAD`) | writes `constellation/.sync.json`; `warning` if the plan is uncommitted |
88
+ | `check_integrity` | — | lint result + `orphans` (zero-connection cards) |
89
+
90
+ `diff_plan` base resolution: explicit argument → sync marker (`.sync.json`) →
91
+ `HEAD` (i.e. uncommitted plan changes). `head` defaults to the working tree.
92
+ `change` is `added` / `modified` / `removed` / `renamed`; for modified cards the
93
+ old and new versions are parsed and compared, yielding `changed_keys` (frontmatter)
94
+ and `body_changed`.
95
+
96
+ The sync workflow: a code-sync agent calls `diff_plan` (base = marker), traverses
97
+ the changed handles for blast radius, updates code, then `set_sync_point` to move
98
+ the marker. `.sync.json` is versioned, rides branches, and the indexer ignores
99
+ dotfiles so it is never treated as a card.
100
+
101
+ ## What v1 tools deliberately died
102
+
103
+ - `expand_handles` — handles ARE the identifiers; nothing to resolve.
104
+ - `init_project` / `link_project` / `check_health` — bootstrap is finding a folder.
105
+ (`init_plan` exists for MCP-only environments, but it just creates the folder —
106
+ no server, no settings file, no linking.)
107
+ - `read_plan` / `update_plan` — `get_card("PLAN-PROJECT")` / `update_card`.
108
+ - Bulk variants — at in-memory speed, sequential calls are fine; revisit only if
109
+ real usage shows tool-call overhead matters.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: Code style rules
3
+ status: built
4
+ scope: src/**
5
+ applies_to: all-agents
6
+ priority: 1
7
+ ---
8
+
9
+ # Code style
10
+
11
+ - TypeScript strict mode; no `any` without a comment explaining why.
12
+ - Route handlers validate input with the shapes from [[DATATYPE-TICKET]] and
13
+ [[DATATYPE-CREATE-TICKET-INPUT]] — never hand-rolled checks.
14
+ - Errors return the shared envelope `{ error: { code, message } }`.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: List & create tickets
3
+ status: built
4
+ path: /api/v1/tickets
5
+ methods:
6
+ GET:
7
+ query_params:
8
+ - { name: status, type: string }
9
+ - { name: assignee_id, type: string }
10
+ response_schema: DATATYPE-TICKET
11
+ POST:
12
+ request_schema: DATATYPE-CREATE-TICKET-INPUT
13
+ response_schema: DATATYPE-TICKET
14
+ connections:
15
+ - DB-TICKETS
16
+ - ROLE-SUPPORT-AGENT
17
+ ---
18
+
19
+ # Tickets API
20
+
21
+ GET returns tickets filtered by status and assignee; requires
22
+ [[ROLE-SUPPORT-AGENT]]. POST is the public intake endpoint (no auth) and emits
23
+ [[EVENT-TICKET-CREATED]] after insert — see [[FLOW-CREATE-TICKET]] for the full
24
+ sequence.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: Ticket card
3
+ status: building
4
+ framework: svelte
5
+ props:
6
+ - { name: ticket, type: DATATYPE-TICKET, required: true }
7
+ - { name: selected, type: boolean }
8
+ variants: [default, compact]
9
+ connections:
10
+ - PAGE-INBOX
11
+ ---
12
+
13
+ # Ticket card
14
+
15
+ One ticket in the inbox list: subject, requester, status chip, age. The status
16
+ chip colors follow [[STATE-TICKET]].
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: Create ticket input
3
+ status: built
4
+ ---
5
+
6
+ Request body for creating a ticket. Server assigns `id`, `status`, and timestamps —
7
+ see [[API-TICKETS]].
8
+
9
+ ```ts
10
+ interface CreateTicketInput {
11
+ subject: string;
12
+ body: string;
13
+ requester_email: string;
14
+ }
15
+ ```
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Ticket
3
+ status: built
4
+ connections:
5
+ - DB-TICKETS
6
+ ---
7
+
8
+ The canonical ticket shape, returned by every ticket endpoint.
9
+
10
+ ```ts
11
+ interface Ticket {
12
+ id: string;
13
+ subject: string;
14
+ body: string;
15
+ status: 'open' | 'assigned' | 'resolved' | 'closed';
16
+ requester_email: string;
17
+ assignee_id: string | null;
18
+ created_at: string; // ISO 8601
19
+ }
20
+ ```
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: tickets table
3
+ kind: sql-table
4
+ status: built
5
+ table_name: tickets
6
+ columns:
7
+ - { name: id, sql_type: UUID, primary_key: true, default: gen_random_uuid() }
8
+ - { name: subject, sql_type: TEXT, not_null: true }
9
+ - { name: body, sql_type: TEXT, not_null: true }
10
+ - { name: status, sql_type: TEXT, not_null: true, default: "'open'" }
11
+ - { name: requester_email, sql_type: TEXT, not_null: true }
12
+ - { name: assignee_id, sql_type: UUID }
13
+ - { name: created_at, sql_type: TIMESTAMPTZ, not_null: true, default: now() }
14
+ indexes:
15
+ - { name: tickets_status_idx, columns: [status] }
16
+ connections:
17
+ - DATATYPE-TICKET
18
+ ---
19
+
20
+ One row per ticket. `status` values mirror [[STATE-TICKET]]; rows are never
21
+ deleted, only moved to `closed`.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: System overview
3
+ status: built
4
+ ---
5
+
6
+ # System overview
7
+
8
+ ```mermaid
9
+ flowchart LR
10
+ PAGE-INBOX --> API-TICKETS
11
+ API-TICKETS --> DB-TICKETS
12
+ API-TICKETS --> EVENT-TICKET-CREATED
13
+ EVENT-TICKET-CREATED --> JOB-AUTO-ASSIGN
14
+ JOB-AUTO-ASSIGN --> DB-TICKETS
15
+ JOB-AUTO-ASSIGN --> EXTERNAL-EMAIL-PROVIDER
16
+ ```
17
+
18
+ Intake is synchronous down the left edge; everything after the event is
19
+ asynchronous. The job is the only component that talks to the email provider.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: How tickets move through the system
3
+ kind: guide
4
+ status: built
5
+ ---
6
+
7
+ # Ticket lifecycle
8
+
9
+ A ticket is born when the public form posts to [[API-TICKETS]] — the whole
10
+ sequence is [[FLOW-CREATE-TICKET]]. From there it walks the state machine in
11
+ [[STATE-TICKET]]: `open → assigned → resolved → closed`, with one loop back when
12
+ a requester reopens.
13
+
14
+ Two invariants the code must never break:
15
+
16
+ 1. State transitions happen only in the API layer. Nothing writes the `status`
17
+ column directly.
18
+ 2. Every state change that matters to the requester produces an email through
19
+ [[EXTERNAL-EMAIL-PROVIDER]] — silence is a bug.
20
+
21
+ Assignment is asynchronous: [[EVENT-TICKET-CREATED]] decouples intake from
22
+ [[JOB-AUTO-ASSIGN]] so a slow assignment never blocks the requester's submit.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: Ticket created
3
+ status: built
4
+ emitter: API-TICKETS
5
+ payload_schema: DATATYPE-TICKET
6
+ delivery_semantics: at-least-once
7
+ ordering: none
8
+ idempotency_key_field: id
9
+ version: 1
10
+ ---
11
+
12
+ Fired after a ticket row is committed. Consumers must dedupe on the ticket `id`
13
+ — delivery is at-least-once. Currently the only consumer is [[JOB-AUTO-ASSIGN]].
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: Email provider
3
+ kind: saas-vendor
4
+ status: built
5
+ vendor: Postmark
6
+ purpose: Transactional email (ticket confirmations, assignment notices)
7
+ docs_url: https://postmarkapp.com/developer
8
+ credentials_envs:
9
+ - POSTMARK_SERVER_TOKEN
10
+ ---
11
+
12
+ All outbound email goes through Postmark. Templates live in the Postmark
13
+ dashboard; this app only sends template IDs and variables, never raw HTML.
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: tickets route handler
3
+ status: built
4
+ path: src/api/tickets.ts
5
+ language: typescript
6
+ summary: Express route handlers for the tickets API
7
+ connections:
8
+ - API-TICKETS
9
+ ---
10
+
11
+ Implements [[API-TICKETS]]. Validation lives here; persistence is in the
12
+ repository module it imports.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: Create ticket
3
+ status: built
4
+ triggers:
5
+ - { kind: manual }
6
+ ---
7
+
8
+ # Create ticket
9
+
10
+ 1. Requester submits the public ticket form
11
+ 2. [[API-TICKETS]] POST validates the body against [[DATATYPE-CREATE-TICKET-INPUT]]
12
+ - invalid → 422 with per-field errors
13
+ 3. Row inserted into [[DB-TICKETS]] with status `open`
14
+ 4. [[EVENT-TICKET-CREATED]] fires
15
+ 5. [[JOB-AUTO-ASSIGN]] picks an agent and sends the confirmation email via
16
+ [[EXTERNAL-EMAIL-PROVIDER]]
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: Auto-assign tickets
3
+ status: planned
4
+ trigger: event
5
+ max_retries: 3
6
+ connections:
7
+ - EVENT-TICKET-CREATED
8
+ ---
9
+
10
+ # Auto-assign
11
+
12
+ Listens for [[EVENT-TICKET-CREATED]], picks the support agent with the fewest
13
+ open tickets, writes the assignment to [[DB-TICKETS]], and sends the requester a
14
+ confirmation email through [[EXTERNAL-EMAIL-PROVIDER]]. Idempotent per ticket `id` —
15
+ re-delivery must not reassign.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Inbox
3
+ status: building
4
+ route: /inbox/:ticket_id?
5
+ path_params:
6
+ - { name: ticket_id, type: string, required: false }
7
+ query_params:
8
+ - { name: status, type: string }
9
+ connections:
10
+ - API-TICKETS
11
+ - COMPONENT-TICKET-CARD
12
+ - ROLE-SUPPORT-AGENT
13
+ ---
14
+
15
+ # Inbox
16
+
17
+ The agent-facing ticket list. Loads tickets from [[API-TICKETS]] and renders each
18
+ as a [[COMPONENT-TICKET-CARD]]. Selecting a ticket opens it inline (the optional
19
+ `ticket_id` path param).
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: Ticketing example — project plan
3
+ ---
4
+
5
+ # Project Plan
6
+
7
+ A minimal support-ticket app used as the golden example for the Constellation v2
8
+ format. One card of every type, fully connected.
9
+
10
+ ## Current state
11
+
12
+ - Core ticket loop is specced: [[FLOW-CREATE-TICKET]], [[STATE-TICKET]],
13
+ [[API-TICKETS]], [[DB-TICKETS]].
14
+ - Auto-assignment ([[JOB-AUTO-ASSIGN]]) is planned but not built.
15
+
16
+ ## Conventions
17
+
18
+ - All ticket payloads use [[DATATYPE-TICKET]]; never inline ticket shapes.
19
+ - Email goes through [[EXTERNAL-EMAIL-PROVIDER]] only.
20
+
21
+ ## Last synced
22
+
23
+ Code was last reconciled against plan commit `<sha>` (maintained by the sync agent).
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: Support agent
3
+ status: built
4
+ permissions:
5
+ - tickets:read
6
+ - tickets:write
7
+ - tickets:assign
8
+ issued_by: app
9
+ ---
10
+
11
+ A human agent working the inbox. Can read, update, and assign any ticket.
12
+ Cannot delete tickets — nothing can; see [[DB-TICKETS]].
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: Ticket lifecycle
3
+ status: built
4
+ states:
5
+ - { name: open, initial: true }
6
+ - { name: assigned }
7
+ - { name: resolved }
8
+ - { name: closed, terminal: true }
9
+ transitions:
10
+ - { from: open, to: assigned, action: notify assignee }
11
+ - { from: assigned, to: resolved }
12
+ - { from: resolved, to: assigned, guard: requester reopens }
13
+ - { from: resolved, to: closed, guard: requester confirms or 7 days pass }
14
+ connections:
15
+ - DB-TICKETS
16
+ ---
17
+
18
+ # Ticket lifecycle
19
+
20
+ ```mermaid
21
+ stateDiagram-v2
22
+ [*] --> open
23
+ open --> assigned: agent assigned
24
+ assigned --> resolved: agent resolves
25
+ resolved --> assigned: requester reopens
26
+ resolved --> closed: confirmed / 7 days
27
+ closed --> [*]
28
+ ```
29
+
30
+ The `status` column in the tickets table holds the current state; transitions are
31
+ enforced in the API layer, never by direct column updates.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Create ticket — integration
3
+ kind: integration
4
+ status: verified
5
+ framework: vitest
6
+ connections:
7
+ - API-TICKETS
8
+ - FLOW-CREATE-TICKET
9
+ ---
10
+
11
+ # Create ticket integration test
12
+
13
+ Covers the full [[FLOW-CREATE-TICKET]] happy path plus the 422 branch:
14
+
15
+ - valid input → 201, row in [[DB-TICKETS]], event emitted
16
+ - missing subject → 422, no row, no event
17
+ - duplicate delivery of the created event → no double-assignment
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@magic-spells/constellation",
3
+ "version": "0.1.0",
4
+ "description": "Files-first architecture planning for AI-assisted development. Your project plan as markdown cards in the repo — typed, connected, queryable, and diffable with git.",
5
+ "type": "module",
6
+ "license": "GPL-3.0-or-later",
7
+ "author": "Cory Schulz <coryschulz@gmail.com>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/magic-spells/constellation.git"
11
+ },
12
+ "homepage": "https://github.com/magic-spells/constellation#readme",
13
+ "bugs": "https://github.com/magic-spells/constellation/issues",
14
+ "files": [
15
+ "dist",
16
+ "schemas",
17
+ "skill",
18
+ "docs",
19
+ "examples",
20
+ "viewer/dist"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "engines": {
26
+ "node": ">=22"
27
+ },
28
+ "bin": {
29
+ "constellation": "./dist/cli/index.js"
30
+ },
31
+ "main": "./dist/core/index.js",
32
+ "types": "./dist/core/index.d.ts",
33
+ "keywords": [
34
+ "architecture",
35
+ "planning",
36
+ "documentation",
37
+ "markdown",
38
+ "diagram",
39
+ "knowledge-graph",
40
+ "mcp",
41
+ "cli",
42
+ "ai",
43
+ "llm"
44
+ ],
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "prepublishOnly": "npm run build && npm run build:viewer",
48
+ "dev": "tsx src/cli/index.ts",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "lint:examples": "tsx src/cli/index.ts lint examples",
52
+ "build:viewer": "vite build viewer",
53
+ "demo": "tsx scripts/demo.ts",
54
+ "dev:viewer": "vite viewer",
55
+ "serve:examples": "tsx src/cli/index.ts serve examples --no-open"
56
+ },
57
+ "dependencies": {
58
+ "@modelcontextprotocol/sdk": "^1.29.0",
59
+ "ajv": "^8.17.1",
60
+ "commander": "^15.0.0",
61
+ "gray-matter": "^4.0.3",
62
+ "js-yaml": "^4.2.0",
63
+ "picocolors": "^1.1.1",
64
+ "zod": "^4.4.3"
65
+ },
66
+ "devDependencies": {
67
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
68
+ "@tailwindcss/vite": "^4.3.1",
69
+ "@types/js-yaml": "^4.0.9",
70
+ "@types/node": "^25.9.3",
71
+ "marked": "^18.0.5",
72
+ "mermaid": "^11.15.0",
73
+ "svelte": "^5.56.3",
74
+ "tailwindcss": "^4.3.1",
75
+ "tsx": "^4.22.0",
76
+ "typescript": "^6.0.3",
77
+ "vite": "^8.0.0",
78
+ "vitest": "^4.1.8"
79
+ }
80
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "agent.json",
4
+ "title": "AGENT card frontmatter",
5
+ "description": "AI agent instructions/policy. The instruction text lives in the body.",
6
+ "type": "object",
7
+ "properties": {
8
+ "scope": {
9
+ "type": "string",
10
+ "description": "Glob or directory the instructions apply to, e.g. src/api/**"
11
+ },
12
+ "applies_to": {
13
+ "type": "string",
14
+ "description": "Which agents this binds, e.g. all-agents or a specific role."
15
+ },
16
+ "priority": {
17
+ "type": "integer",
18
+ "description": "Higher priority instructions are loaded first."
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "api.json",
4
+ "title": "API card frontmatter",
5
+ "description": "HTTP/RPC endpoint. Schema references are handles of DATATYPE cards.",
6
+ "type": "object",
7
+ "properties": {
8
+ "path": {
9
+ "type": "string",
10
+ "description": "URL pattern, e.g. /api/v1/tickets/:id"
11
+ },
12
+ "path_params": {
13
+ "type": "array",
14
+ "items": { "$ref": "#/$defs/param" }
15
+ },
16
+ "methods": {
17
+ "type": "object",
18
+ "propertyNames": {
19
+ "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
20
+ },
21
+ "additionalProperties": {
22
+ "type": "object",
23
+ "properties": {
24
+ "query_params": {
25
+ "type": "array",
26
+ "items": { "$ref": "#/$defs/param" }
27
+ },
28
+ "request_schema": { "$ref": "card.json#/$defs/handle" },
29
+ "response_schema": { "$ref": "card.json#/$defs/handle" }
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "$defs": {
35
+ "param": {
36
+ "type": "object",
37
+ "properties": {
38
+ "name": { "type": "string" },
39
+ "type": { "type": "string" },
40
+ "required": { "type": "boolean" },
41
+ "default": { "type": "string" },
42
+ "notes": { "type": "string" }
43
+ },
44
+ "required": ["name"]
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "card.json",
4
+ "title": "Constellation card — reserved frontmatter keys",
5
+ "description": "The four reserved keys every card may use. All other frontmatter keys are type-specific fields defined in the per-type schemas.",
6
+ "type": "object",
7
+ "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "maxLength": 200,
11
+ "description": "Display name. The handle (filename) is the identity; this is the human label."
12
+ },
13
+ "kind": {
14
+ "type": "string",
15
+ "pattern": "^[a-z][a-z0-9-]*$",
16
+ "maxLength": 64,
17
+ "description": "Subtype discriminator, lowercase slug (e.g. sql-table, e2e, decision)."
18
+ },
19
+ "status": {
20
+ "enum": ["planned", "building", "built", "verified"],
21
+ "description": "Lifecycle state. Orthogonal to git history: 'what changed' comes from git, not from this field."
22
+ },
23
+ "connections": {
24
+ "type": "array",
25
+ "items": { "$ref": "#/$defs/handle" },
26
+ "description": "Plain list of handles this card is connected to. Undirected; declare on whichever card you are editing."
27
+ }
28
+ },
29
+ "$defs": {
30
+ "handle": {
31
+ "type": "string",
32
+ "pattern": "^[A-Z][A-Z0-9]*-[A-Z0-9][A-Z0-9-]*$",
33
+ "minLength": 3,
34
+ "maxLength": 135
35
+ }
36
+ }
37
+ }