@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.
- package/LICENSE +674 -0
- package/README.md +147 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/extract.d.ts +9 -0
- package/dist/core/extract.js +56 -0
- package/dist/core/extract.js.map +1 -0
- package/dist/core/handles.d.ts +13 -0
- package/dist/core/handles.js +45 -0
- package/dist/core/handles.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/indexer.d.ts +3 -0
- package/dist/core/indexer.js +209 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/lint.d.ts +9 -0
- package/dist/core/lint.js +19 -0
- package/dist/core/lint.js.map +1 -0
- package/dist/core/parse.d.ts +7 -0
- package/dist/core/parse.js +19 -0
- package/dist/core/parse.js.map +1 -0
- package/dist/core/resolve.d.ts +12 -0
- package/dist/core/resolve.js +57 -0
- package/dist/core/resolve.js.map +1 -0
- package/dist/core/scaffold.d.ts +3 -0
- package/dist/core/scaffold.js +36 -0
- package/dist/core/scaffold.js.map +1 -0
- package/dist/core/types.d.ts +50 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validate.d.ts +6 -0
- package/dist/core/validate.js +64 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/core/writer.d.ts +34 -0
- package/dist/core/writer.js +170 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/mcp/git.d.ts +34 -0
- package/dist/mcp/git.js +172 -0
- package/dist/mcp/git.js.map +1 -0
- package/dist/mcp/search.d.ts +11 -0
- package/dist/mcp/search.js +52 -0
- package/dist/mcp/search.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +684 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/serve/server.d.ts +12 -0
- package/dist/serve/server.js +281 -0
- package/dist/serve/server.js.map +1 -0
- package/docs/001-file-format.md +222 -0
- package/docs/002-mcp.md +109 -0
- package/examples/constellation/agent/AGENT-CODE-STYLE.md +14 -0
- package/examples/constellation/api/API-TICKETS.md +24 -0
- package/examples/constellation/component/COMPONENT-TICKET-CARD.md +16 -0
- package/examples/constellation/datatype/DATATYPE-CREATE-TICKET-INPUT.md +15 -0
- package/examples/constellation/datatype/DATATYPE-TICKET.md +20 -0
- package/examples/constellation/db/DB-TICKETS.md +21 -0
- package/examples/constellation/diagram/DIAGRAM-SYSTEM-OVERVIEW.md +19 -0
- package/examples/constellation/doc/DOC-TICKET-LIFECYCLE.md +22 -0
- package/examples/constellation/event/EVENT-TICKET-CREATED.md +13 -0
- package/examples/constellation/external/EXTERNAL-EMAIL-PROVIDER.md +13 -0
- package/examples/constellation/file/FILE-TICKETS-ROUTE.md +12 -0
- package/examples/constellation/flow/FLOW-CREATE-TICKET.md +16 -0
- package/examples/constellation/job/JOB-AUTO-ASSIGN.md +15 -0
- package/examples/constellation/page/PAGE-INBOX.md +19 -0
- package/examples/constellation/plan.md +23 -0
- package/examples/constellation/role/ROLE-SUPPORT-AGENT.md +12 -0
- package/examples/constellation/state/STATE-TICKET.md +31 -0
- package/examples/constellation/test/TEST-CREATE-TICKET.md +17 -0
- package/package.json +80 -0
- package/schemas/agent.json +21 -0
- package/schemas/api.json +47 -0
- package/schemas/card.json +37 -0
- package/schemas/component.json +40 -0
- package/schemas/datatype.json +8 -0
- package/schemas/db.json +51 -0
- package/schemas/diagram.json +95 -0
- package/schemas/doc.json +8 -0
- package/schemas/event.json +28 -0
- package/schemas/external.json +24 -0
- package/schemas/file.json +22 -0
- package/schemas/flow.json +20 -0
- package/schemas/job.json +25 -0
- package/schemas/page.json +36 -0
- package/schemas/plan.json +13 -0
- package/schemas/role.json +18 -0
- package/schemas/state.json +35 -0
- package/schemas/test.json +13 -0
- package/skill/SKILL.md +105 -0
- package/skill/types/agent.md +26 -0
- package/skill/types/api.md +38 -0
- package/skill/types/component.md +30 -0
- package/skill/types/datatype.md +26 -0
- package/skill/types/db.md +33 -0
- package/skill/types/diagram.md +32 -0
- package/skill/types/doc.md +23 -0
- package/skill/types/event.md +31 -0
- package/skill/types/external.md +30 -0
- package/skill/types/file.md +28 -0
- package/skill/types/flow.md +29 -0
- package/skill/types/job.md +27 -0
- package/skill/types/page.md +28 -0
- package/skill/types/plan.md +37 -0
- package/skill/types/role.md +25 -0
- package/skill/types/state.md +38 -0
- package/skill/types/test.md +28 -0
- package/viewer/dist/assets/arc-Kj6pF3JI.js +1 -0
- package/viewer/dist/assets/architecture-7EHR7CIX-CGfWeim3.js +1 -0
- package/viewer/dist/assets/architectureDiagram-3BPJPVTR-C5bZdErB.js +36 -0
- package/viewer/dist/assets/array-BifhSqXX.js +1 -0
- package/viewer/dist/assets/blockDiagram-GPEHLZMM-C1Q6l6fE.js +132 -0
- package/viewer/dist/assets/c4Diagram-AAUBKEIU-BmM6Tmtq.js +10 -0
- package/viewer/dist/assets/channel-19IdUS_c.js +1 -0
- package/viewer/dist/assets/chunk-2J33WTMH-z09tLTpZ.js +1 -0
- package/viewer/dist/assets/chunk-3OPIFGDE-BynpXh1r.js +62 -0
- package/viewer/dist/assets/chunk-4BX2VUAB-CDOVuPyG.js +1 -0
- package/viewer/dist/assets/chunk-55IACEB6-nBwigOgn.js +1 -0
- package/viewer/dist/assets/chunk-5ZQYHXKU-Bxe5xIy_.js +2 -0
- package/viewer/dist/assets/chunk-727SXJPM-DZmTgL68.js +206 -0
- package/viewer/dist/assets/chunk-AQP2D5EJ-B7wr_Owx.js +231 -0
- package/viewer/dist/assets/chunk-BSJP7CBP-DbAKfVCK.js +1 -0
- package/viewer/dist/assets/chunk-CSCIHK7Q-C0rsBwqP.js +124 -0
- package/viewer/dist/assets/chunk-FMBD7UC4-BAtzt0wv.js +15 -0
- package/viewer/dist/assets/chunk-KSCS5N6A-CXXwf52I.js +10 -0
- package/viewer/dist/assets/chunk-L5ZTLDWV-DS4vRI1U.js +1 -0
- package/viewer/dist/assets/chunk-LZXEDZCA-CclT9MXr.js +2 -0
- package/viewer/dist/assets/chunk-ND2GUHAM-CUSnPl8t.js +1 -0
- package/viewer/dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/viewer/dist/assets/chunk-NZK2D7GU-Dh986nJk.js +1 -0
- package/viewer/dist/assets/chunk-O5CBEL6O-JAEZ_pS6.js +70 -0
- package/viewer/dist/assets/chunk-QZHKN3VN-BKc_Kg2Z.js +1 -0
- package/viewer/dist/assets/chunk-WU5MYG2G-9ssTSMzt.js +1 -0
- package/viewer/dist/assets/chunk-XPW4576I-BwMZI0gv.js +32 -0
- package/viewer/dist/assets/classDiagram-4FO5ZUOK-DXv85WFd.js +1 -0
- package/viewer/dist/assets/classDiagram-v2-Q7XG4LA2-DXv85WFd.js +1 -0
- package/viewer/dist/assets/cose-bilkent-S5V4N54A-NGC7gYHM.js +1 -0
- package/viewer/dist/assets/cytoscape.esm-h6BdjjI9.js +321 -0
- package/viewer/dist/assets/dagre-BM42HDAG-RD63uyvd.js +4 -0
- package/viewer/dist/assets/dagre-Bx709z4p.js +1 -0
- package/viewer/dist/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/viewer/dist/assets/diagram-2AECGRRQ-hwnqqCcb.js +43 -0
- package/viewer/dist/assets/diagram-5GNKFQAL-q8EaoZSG.js +10 -0
- package/viewer/dist/assets/diagram-KO2AKTUF-D4_5Qf-l.js +3 -0
- package/viewer/dist/assets/diagram-LMA3HP47-D8pwekFs.js +24 -0
- package/viewer/dist/assets/diagram-OG6HWLK6-D9KinIWZ.js +24 -0
- package/viewer/dist/assets/dist-CFOOgrqc.js +1 -0
- package/viewer/dist/assets/erDiagram-TEJ5UH35-D0Wfq250.js +85 -0
- package/viewer/dist/assets/eventmodeling-FCH6USID-D3KRSuC1.js +1 -0
- package/viewer/dist/assets/flowDiagram-I6XJVG4X-Y2DY-Ze2.js +162 -0
- package/viewer/dist/assets/ganttDiagram-6RSMTGT7-BnqkeLVw.js +292 -0
- package/viewer/dist/assets/gitGraph-WXDBUCRP-Cft7usRT.js +1 -0
- package/viewer/dist/assets/gitGraphDiagram-PVQCEYII-D-cYtraK.js +106 -0
- package/viewer/dist/assets/graphlib-B8gBHxth.js +1 -0
- package/viewer/dist/assets/index-CDR-riG2.css +2 -0
- package/viewer/dist/assets/index-DRPsTWe2.js +98 -0
- package/viewer/dist/assets/info-J43DQDTF-Djc8Bx3F.js +1 -0
- package/viewer/dist/assets/infoDiagram-5YYISTIA-D-ehtyyJ.js +2 -0
- package/viewer/dist/assets/init-D6jRqBbL.js +1 -0
- package/viewer/dist/assets/ishikawaDiagram-YF4QCWOH-Ct3f6bH-.js +70 -0
- package/viewer/dist/assets/journeyDiagram-JHISSGLW-DXlULEmi.js +139 -0
- package/viewer/dist/assets/kanban-definition-UN3LZRKU-3vE9h-R7.js +89 -0
- package/viewer/dist/assets/katex-Vhh-h91d.js +257 -0
- package/viewer/dist/assets/line-B8MygbLB.js +1 -0
- package/viewer/dist/assets/linear-CfMuM0B3.js +1 -0
- package/viewer/dist/assets/mermaid-parser.core-DzlZTbbh.js +4 -0
- package/viewer/dist/assets/mermaid.core-IM-sPiyq.js +9 -0
- package/viewer/dist/assets/mindmap-definition-RKZ34NQL-CMnpAq1T.js +96 -0
- package/viewer/dist/assets/ordinal-hYBb2elL.js +1 -0
- package/viewer/dist/assets/packet-YPE3B663-D44AzgHh.js +1 -0
- package/viewer/dist/assets/path-BWPyau1x.js +1 -0
- package/viewer/dist/assets/pie-LRSECV5Y-DL8AVJH_.js +1 -0
- package/viewer/dist/assets/pieDiagram-4H26LBE5-FvKK5jd7.js +30 -0
- package/viewer/dist/assets/quadrantDiagram-W4KKPZXB-CmjSkU8c.js +7 -0
- package/viewer/dist/assets/radar-GUYGQ44K-BvfZTVyH.js +1 -0
- package/viewer/dist/assets/requirementDiagram-4Y6WPE33-BOjca3VH.js +84 -0
- package/viewer/dist/assets/rough.esm-CSKSodPl.js +1 -0
- package/viewer/dist/assets/sankeyDiagram-5OEKKPKP-ANcjfNix.js +40 -0
- package/viewer/dist/assets/sequenceDiagram-3UESZ5HK-BLQ9AL7I.js +162 -0
- package/viewer/dist/assets/src-CAMdANUp.js +1 -0
- package/viewer/dist/assets/stateDiagram-AJRCARHV-D6CriBS6.js +1 -0
- package/viewer/dist/assets/stateDiagram-v2-BHNVJYJU-DcTp66RQ.js +1 -0
- package/viewer/dist/assets/timeline-definition-PNZ67QCA-BNhWZ_DL.js +120 -0
- package/viewer/dist/assets/treeView-BLDUP644-CY6Ph5Pu.js +1 -0
- package/viewer/dist/assets/treemap-LRROVOQU-DChSA_Qx.js +1 -0
- package/viewer/dist/assets/vennDiagram-CIIHVFJN-C01WznAC.js +34 -0
- package/viewer/dist/assets/wardley-L42UT6IY-BJ8uNoJu.js +1 -0
- package/viewer/dist/assets/wardleyDiagram-YWT4CUSO-DwDEzlVm.js +78 -0
- package/viewer/dist/assets/xychartDiagram-2RQKCTM6-BCvIDwU0.js +7 -0
- package/viewer/dist/index.html +20 -0
package/docs/002-mcp.md
ADDED
|
@@ -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
|
+
}
|
package/schemas/api.json
ADDED
|
@@ -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
|
+
}
|