sprinty-mcp 0.1.1 → 0.1.3

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 (63) hide show
  1. package/README.md +62 -15
  2. package/clients/claude/.claude-plugin/plugin.json +2 -2
  3. package/clients/codex/README.md +10 -0
  4. package/clients/gemini/GEMINI.md +19 -9
  5. package/clients/gemini/gemini-extension.json +2 -2
  6. package/dist/dashboard/page.d.ts +1 -1
  7. package/dist/dashboard/page.js +3 -2
  8. package/dist/dashboard/page.js.map +1 -1
  9. package/dist/dashboard/server.js +44 -0
  10. package/dist/dashboard/server.js.map +1 -1
  11. package/dist/dashboard/ui/model.d.ts +86 -0
  12. package/dist/dashboard/ui/model.js +129 -0
  13. package/dist/dashboard/ui/model.js.map +1 -0
  14. package/dist/dashboard/ui/src/main.d.ts +1 -0
  15. package/dist/dashboard/ui/src/main.js +6 -0
  16. package/dist/dashboard/ui/src/main.js.map +1 -0
  17. package/dist/dashboard-ui/assets/index-DqR_XeMj.js +66 -0
  18. package/dist/dashboard-ui/assets/index-xdWTtMVs.css +1 -0
  19. package/dist/dashboard-ui/index.html +13 -0
  20. package/dist/domain/change-map.js +13 -4
  21. package/dist/domain/change-map.js.map +1 -1
  22. package/dist/domain/changelog.js +18 -5
  23. package/dist/domain/changelog.js.map +1 -1
  24. package/dist/domain/coverage.d.ts +145 -0
  25. package/dist/domain/coverage.js +5 -0
  26. package/dist/domain/coverage.js.map +1 -1
  27. package/dist/domain/enums.d.ts +3 -3
  28. package/dist/domain/enums.js +6 -3
  29. package/dist/domain/enums.js.map +1 -1
  30. package/dist/domain/events.d.ts +1059 -8
  31. package/dist/domain/events.js +55 -3
  32. package/dist/domain/events.js.map +1 -1
  33. package/dist/domain/gates.d.ts +64 -1
  34. package/dist/domain/gates.js +12 -0
  35. package/dist/domain/gates.js.map +1 -1
  36. package/dist/domain/projection.d.ts +34 -2
  37. package/dist/domain/projection.js +136 -14
  38. package/dist/domain/projection.js.map +1 -1
  39. package/dist/domain/search.js +7 -0
  40. package/dist/domain/search.js.map +1 -1
  41. package/dist/gates/run.js +5 -3
  42. package/dist/gates/run.js.map +1 -1
  43. package/dist/ledger/book.d.ts +2 -2
  44. package/dist/ledger/book.js +2 -2
  45. package/dist/ledger/book.js.map +1 -1
  46. package/dist/server.d.ts +6 -0
  47. package/dist/server.js +116 -9
  48. package/dist/server.js.map +1 -1
  49. package/dist/store/store.d.ts +68 -4
  50. package/dist/store/store.js +268 -29
  51. package/dist/store/store.js.map +1 -1
  52. package/dist/tools/current.d.ts +14 -2
  53. package/dist/tools/current.js +71 -3
  54. package/dist/tools/current.js.map +1 -1
  55. package/dist/tools/register.d.ts +6 -1
  56. package/dist/tools/register.js +36 -16
  57. package/dist/tools/register.js.map +1 -1
  58. package/dist/tools/schemas.d.ts +268 -3
  59. package/dist/tools/schemas.js +50 -3
  60. package/dist/tools/schemas.js.map +1 -1
  61. package/package.json +32 -6
  62. package/skills/how-to-run-a-sprint/SKILL.md +38 -9
  63. package/skills/using-sprinty/SKILL.md +46 -13
package/README.md CHANGED
@@ -5,7 +5,8 @@ A disciplined-sprint MCP server for AI coding agents — **Claude Code, Codex, a
5
5
  Sprinty gives an agent first-class tools to run a sprint with structure that can't silently rot:
6
6
  structured **sprint → subsprint → item** objects, dependency graphs with topological ordering and
7
7
  cycle detection, an **immutable append-only ledger** anchored to real git commits,
8
- Git-backed **change maps**, Markdown changelogs with file tables, **programmatic close-gates**
8
+ Git-backed **change maps**, durable **artifacts**, bug-backed **follow-ups**, feature-sized
9
+ **spikes**, Markdown changelogs with file tables, **programmatic close-gates**
9
10
  that re-run your tests and require coverage evidence before a sprint can close, a **regex search**
10
11
  over the record, and a **live follow-along dashboard**.
11
12
 
@@ -22,7 +23,9 @@ Sprinty has two layers:
22
23
  - optional client manifests under `clients/` for agents that support plugins or extensions.
23
24
 
24
25
  The npm package is `sprinty-mcp`. The server, MCP tool namespace, and client manifests are named
25
- `sprinty`.
26
+ `sprinty`. The tarball ships the canonical top-level `skills/` directory; client directories in a
27
+ Git checkout may symlink to it, but package consumers should treat the top-level `skills/` directory
28
+ as authoritative.
26
29
 
27
30
  ### Claude Code
28
31
 
@@ -44,6 +47,25 @@ command = "npx"
44
47
  args = ["-y", "sprinty-mcp"]
45
48
  ```
46
49
 
50
+ Sprinty does not guess from the MCP server process cwd. Start a sprint with explicit paths:
51
+
52
+ ```json
53
+ {
54
+ "goal": "Build the catalogue MCP",
55
+ "git_dir": "/absolute/path/to/your/repo",
56
+ "data_dir": "/absolute/path/to/your/repo/.sprinty",
57
+ "context_notes": ["optional context"]
58
+ }
59
+ ```
60
+
61
+ `git_dir` is where commits, gates, coverage, and change maps run. `data_dir` is where Sprinty stores
62
+ the `current` pointer and append-only JSONL ledgers. Use a worktree-scoped, uncommitted `data_dir`,
63
+ such as `<git_dir>/.sprinty` when that path is gitignored; avoid shared temp dirs or any directory
64
+ that will be committed. For read-only tools before `sprint_new`, you may pre-bind the MCP server
65
+ with `SPRINTY_GIT_DIR` and `SPRINTY_DATA_DIR` or `--git-dir` and
66
+ `--data-dir`; both are required together. `SPRINTY_REPO_DIR` and `SPRINTY_WORKTREE` remain accepted
67
+ as legacy aliases for `git_dir` only when a `data_dir` is also supplied.
68
+
47
69
  Codex CLI plugin path: install the repo-local marketplace from a repository checkout:
48
70
 
49
71
  ```bash
@@ -73,11 +95,16 @@ content rather than copying it.
73
95
  ## The loop
74
96
 
75
97
  ```
76
- sprint_new(goal, context_notes?)
98
+ sprint_new(goal, git_dir, data_dir, context_notes?)
99
+ -> dashboard()
77
100
  -> subsprint_new(description, goals[], gates[], dependencies?)
78
- -> add(subsprint, description, code_locations[], gates[], dependencies?)
101
+ -> spike(description, goals[], gates[], dependencies?)
102
+ -> add(subsprint, title, description, code_locations[], gates[], dependencies?)
103
+ -> artifact_add/list/amend/deprecate(...)
104
+ -> follow_up(target, description, bug_id|bug_ids)
79
105
  -> dependencies(target, dependencies[])
80
106
  -> done(commit_id, gate_results[], changelog) | split(...) | deprecate(reason)
107
+ -> spike_conclude(subsprint, conclusion) | spike_deprecate(subsprint, reason)
81
108
  -> changelog()
82
109
  -> sprint_close(coverage: { path, format: "lcov", command? })
83
110
  ```
@@ -93,12 +120,15 @@ The dashboard is for the human sitting next to the agent.
93
120
  2. Open the returned `http://127.0.0.1:<port>` URL in a browser.
94
121
  3. Leave it open while the sprint runs; it refreshes every two seconds.
95
122
 
96
- The dashboard shows the sprint goal, branch/worktree, start time, progress counts, line coverage,
97
- open items, gate evidence, dependency graph state, commit ids and changelog lines for completed
98
- items, changed-file hotspots, and a timestamped timeline from the immutable ledger.
123
+ Agents should call `dashboard()` right after `sprint_new()` and show the URL to the human. The
124
+ dashboard shows the sprint goal, explicit git/data paths, branch/worktree, artifact shelf, sprint progress, item status
125
+ distribution, code churn, subsprint progress, open items, gate evidence, dependency graph state,
126
+ commit ids and changelog lines for completed items, changed-file hotspots, and a paginated ledger
127
+ from the immutable timeline.
99
128
 
100
129
  The dashboard server binds to `127.0.0.1` on an ephemeral port and is read-only. It lives only for
101
- the running MCP server process.
130
+ the running MCP server process, and a successful `sprint_close()` or `sprint_archive()` stops that
131
+ dashboard URL so old sprint dashboards do not pile up.
102
132
 
103
133
  ## Proof Model
104
134
 
@@ -108,16 +138,33 @@ items require a real git commit id and controlled changelog entry when `done()`
108
138
  Git-backed change map for the commit: file, language, directory, additions, deletions, net change,
109
139
  churn, item ids, and commit ids. `changelog()` renders a Markdown release note with semver sections,
110
140
  coverage, and change-map tables. `done()` also requires passing evidence for every declared item
111
- gate, including manual gates. Dependencies are stored as a real graph: `current()` returns nodes,
112
- edges, adjacency indexes, topological order, and cycle information; writes reject cycles. At close,
113
- executable gates are re-run by Sprinty and `sprint_close()` requires an LCOV coverage report path.
141
+ gate, including manual gates. When an early declared gate was a placeholder, `done()` can record an
142
+ explicit supersession: the final passing gate result names the declared gate in `supersedes` and
143
+ includes a `supersession_reason`, preserving strict evidence without pretending the placeholder
144
+ command was the final proof. Dependencies are stored as a real graph: `current()` returns nodes,
145
+ edges, adjacency indexes, topological order, cycle information, the first actionable `current`
146
+ item, actionable `next` items, `blocked_open` items, and enriched relation rows that name direct
147
+ blockers/unblocked work with statuses. Writes reject cycles. At close, executable gates are re-run
148
+ by Sprinty and `sprint_close()` requires an LCOV coverage report path.
149
+ Artifacts are append-only too: amendments and deprecations are separate ledger events, never
150
+ in-place edits or deletes. Follow-ups require bug ids. Spikes are subsprints with a `spike` flag:
151
+ they can have normal items, but must be concluded or deprecated with a reason, and spike work is
152
+ kept out of release changelog output.
153
+
154
+ Items have two text fields by design: `title` is a short one-line label for the tree/dashboard, and
155
+ `description` is bounded detail for the expanded item body. Use one item per independently
156
+ verifiable behavior, tool, endpoint, component, or migration step. If an item needs a list of
157
+ unrelated deliverables in the title, split it before adding it. Oversized `add()` calls return a
158
+ validation nudge to use `split()` or smaller atomic items.
114
159
 
115
160
  ## Storage
116
161
 
117
- One append-only JSONL ledger file per sprint under `.sprinty/` in the repo you're working on, with a
118
- `.sprinty/current` pointer naming the active sprint (this enforces one-open-sprint unicity).
119
- `.sprinty/` is per working tree, so git worktrees run independent sprints. It is local state — keep
120
- it gitignored.
162
+ One append-only JSONL ledger file per sprint under the explicit `data_dir`, with a `current` pointer
163
+ naming the active sprint (this enforces one-open-sprint unicity for that binding). `git_dir` and
164
+ `data_dir` are intentionally separate so agents can run gates against one checkout while storing
165
+ Sprinty state somewhere deliberate. It is local state — keep it gitignored when `data_dir` lives
166
+ inside a repository, and scope it to the active worktree or repo instead of a shared process temp
167
+ directory.
121
168
 
122
169
  ## Develop
123
170
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sprinty",
3
- "version": "0.1.0",
4
- "description": "Disciplined sprint discipline: structured sprint/subsprint/item objects, an immutable git-anchored ledger, programmatic close-gates, and a live dashboard.",
3
+ "version": "0.1.3",
4
+ "description": "Disciplined sprint workflow: dashboard-at-start, feature subsprints, artifacts, bug-id follow-ups, spike investigations, an immutable git-anchored ledger, and programmatic close gates.",
5
5
  "author": { "name": "Elie Bursztein" },
6
6
  "homepage": "https://github.com/ebursztein/sprinty",
7
7
  "repository": "https://github.com/ebursztein/sprinty",
@@ -20,12 +20,22 @@ The installed plugin provides:
20
20
  - Sprinty MCP tools through `npx -y sprinty-mcp`
21
21
  - shared Sprinty skills from the canonical repository `skills/` directory
22
22
 
23
+ The npm package includes the top-level `skills/` directory for MCP resource serving. Client
24
+ directories may contain symlinks to that canonical directory in a Git checkout; npm tarballs do not
25
+ need per-client skill copies.
26
+
23
27
  MCP-only setup without plugin skills:
24
28
 
25
29
  ```bash
26
30
  codex mcp add sprinty -- npx -y sprinty-mcp
27
31
  ```
28
32
 
33
+ Sprinty does not infer state from Codex's MCP launch cwd. Start every sprint with explicit
34
+ `git_dir` and `data_dir`; `git_dir` is where commits, gates, coverage, and change maps run, while
35
+ `data_dir` stores the `current` pointer and JSONL ledgers. For read-only tools before `sprint_new`,
36
+ you may pre-bind with both `SPRINTY_GIT_DIR` and `SPRINTY_DATA_DIR` or both `--git-dir` and
37
+ `--data-dir`.
38
+
29
39
  Do not copy skill files into client directories; keep them in the top-level `skills/` directory.
30
40
 
31
41
  For human visibility during a sprint, ask Codex to call `dashboard()` and open the returned
@@ -5,22 +5,32 @@ When a task is non-trivial, run a disciplined sprint with the sprinty MCP.
5
5
  See `skills/how-to-run-a-sprint` and `skills/using-sprinty`. The loop:
6
6
 
7
7
  ```
8
- sprint_new(goal, context_notes?)
8
+ sprint_new(goal, git_dir, data_dir, context_notes?)
9
+ -> dashboard()
9
10
  -> subsprint_new(description, goals[], gates[], dependencies?)
10
- -> add(subsprint, description, code_locations[], gates[], dependencies?)
11
+ -> spike(description, goals[], gates[], dependencies?)
12
+ -> add(subsprint, title, description, code_locations[], gates[], dependencies?)
13
+ -> artifact_add/list/amend/deprecate(...)
14
+ -> follow_up(target, description, bug_id|bug_ids)
11
15
  -> dependencies(target, dependencies[])
12
16
  -> done(commit_id, gate_results[], changelog) | split(...) | deprecate(reason)
17
+ -> spike_conclude(subsprint, conclusion) | spike_deprecate(subsprint, reason)
13
18
  -> changelog()
14
19
  -> sprint_close(coverage: { path, format: "lcov", command? })
15
20
  ```
16
21
 
17
- Rules: IDs are minted by the server (`S01`, `S01-001`) — never invent them. Every item needs a
22
+ Rules: IDs are minted by the server (`S01`, `S01-001`) — never invent them. Start with explicit
23
+ absolute `git_dir` and `data_dir`; `git_dir` is where commits/gates/coverage run, and `data_dir`
24
+ stores Sprinty's `current` pointer and JSONL ledgers. Every item needs a
18
25
  description, at least one code location, and at least one gate. `done` requires a real commit,
19
26
  passing evidence for every declared item gate, and a semver-style changelog line with a verb such as
20
- `added`, `fixed`, or `removed`. `split` and `deprecate` are terminal non-code exits. `current()`
21
- returns the dependency graph, including topological order and cycles. `done` records a Git-backed
22
- change map. `changelog()` returns Markdown with changelog and change-map tables. `sprint_close`
23
- rechecks commits, re-runs executable gates, requires an LCOV coverage report path, and refuses to
24
- close on any blocker. Use
25
- `search(pattern, context_lines)` to query the immutable ledger. Use `dashboard()` for a live view,
27
+ `added`, `fixed`, or `removed`. `split` and `deprecate` are terminal non-code exits. Each
28
+ subsprint should be one feature; use `spike()` for feature investigations, then close the spike with
29
+ `spike_conclude()` or `spike_deprecate()`. Use `artifact_add/list/amend/deprecate` for durable
30
+ outputs and `follow_up()` with bug ids for bugs found while moving fast. `current()` returns the
31
+ dependency graph, including topological order and cycles, plus relevant artifacts, recent artifacts,
32
+ and recent activity. `done` records a Git-backed change map. `changelog()` returns Markdown with
33
+ changelog and change-map tables. `sprint_close` rechecks commits, re-runs executable gates, requires
34
+ an LCOV coverage report path, and refuses to close on any blocker. Use `search(pattern,
35
+ context_lines)` to query the immutable ledger. Use `dashboard()` immediately after `sprint_new()`,
26
36
  and give the returned localhost URL to the human so they can watch the sprint timeline.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sprinty",
3
- "version": "0.1.0",
4
- "description": "Disciplined sprint discipline with an immutable git-anchored ledger and live dashboard.",
3
+ "version": "0.1.3",
4
+ "description": "Disciplined sprint workflow with dashboard-at-start, feature subsprints, artifacts, bug-id follow-ups, spike investigations, an immutable git-anchored ledger, and close gates.",
5
5
  "contextFileName": "GEMINI.md",
6
6
  "mcpServers": {
7
7
  "sprinty": { "command": "npx", "args": ["-y", "sprinty-mcp"] }
@@ -1 +1 @@
1
- export declare const PAGE = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>sprinty dashboard</title>\n<style>\n:root{color-scheme:dark;--bg:#101112;--panel:#191b1f;--panel2:#20242a;--line:#333842;--text:#f0f2f4;--muted:#9ca6b3;--blue:#80b8ff;--green:#7bd88f;--red:#ff8b8b;--yellow:#f4c96b}\n*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.45 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif}\nheader{display:grid;grid-template-columns:1fr auto;gap:1rem;align-items:end;padding:1.25rem 1.5rem;border-bottom:1px solid var(--line);background:#15171a}\nh1{margin:0;font-size:1.25rem;font-weight:700;letter-spacing:0}.meta{display:flex;flex-wrap:wrap;gap:.6rem;color:var(--muted);font-size:.85rem}.pill{border:1px solid var(--line);border-radius:999px;padding:.2rem .55rem;background:var(--panel2);color:var(--muted)}\n.pill.active{color:var(--green);border-color:#3b7049}.pill.closed{color:var(--blue);border-color:#355c88}\nmain{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(22rem,.75fr);gap:1rem;padding:1rem;max-width:1400px;margin:0 auto}\nsection{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:1rem;min-width:0}section+section{margin-top:1rem}\nh2{margin:0 0 .75rem;font-size:.82rem;text-transform:uppercase;color:var(--muted);letter-spacing:.04em}\n.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.6rem}.metric{background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:.75rem}.metric b{display:block;font-size:1.3rem}.metric span{color:var(--muted);font-size:.8rem}\n.sub{border-top:1px solid var(--line);padding:1rem 0}.sub:first-child{border-top:0;padding-top:0}.row{display:flex;gap:.6rem;align-items:center;justify-content:space-between}.title{font-weight:650}.desc{color:var(--muted);margin:.15rem 0 .6rem}.items{display:grid;gap:.55rem}\n.item{border:1px solid var(--line);background:var(--panel2);border-radius:6px;padding:.7rem}.item.terminal{opacity:.72}.item-head{display:flex;flex-wrap:wrap;gap:.45rem;align-items:center;margin-bottom:.3rem}.id{font:12px ui-monospace,monospace;color:var(--blue)}\n.status{font-size:.75rem;color:var(--muted)}.open{color:var(--yellow)}.completed{color:var(--green)}.split{color:var(--blue)}.deprecated,.failed{color:var(--red)}\n.gates{display:grid;gap:.25rem;margin-top:.45rem}.gate{font:12px ui-monospace,monospace;color:var(--muted);overflow-wrap:anywhere}.gate.pass{color:var(--green)}.gate.fail{color:var(--red)}\n.timeline{display:grid;gap:.5rem}.event{display:grid;grid-template-columns:8.5rem minmax(0,1fr);gap:.7rem;border-top:1px solid var(--line);padding-top:.5rem}.event:first-child{border-top:0;padding-top:0}.time{color:var(--muted);font:12px ui-monospace,monospace}.event b{font-size:.82rem}.event p{margin:.12rem 0 0;color:var(--muted);overflow-wrap:anywhere}\n.empty{color:var(--muted);padding:.6rem 0}.error{color:var(--red)}\n@media(max-width:850px){header{grid-template-columns:1fr}main{grid-template-columns:1fr}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.event{grid-template-columns:1fr}}\n</style>\n</head>\n<body>\n<header>\n <div>\n <h1 id=\"goal\">Loading sprint...</h1>\n <div class=\"meta\">\n <span id=\"branch\"></span>\n <span id=\"worktree\"></span>\n <span id=\"created\"></span>\n </div>\n </div>\n <span id=\"status\" class=\"pill\">loading</span>\n</header>\n<main>\n <div>\n <section>\n <h2>Progress</h2>\n <div class=\"metrics\">\n <div class=\"metric\"><b id=\"metric-subsprints\">0</b><span>subsprints</span></div>\n <div class=\"metric\"><b id=\"metric-items\">0</b><span>items</span></div>\n <div class=\"metric\"><b id=\"metric-open\">0</b><span>open</span></div>\n <div class=\"metric\"><b id=\"metric-gates\">0</b><span>gate results</span></div>\n <div class=\"metric\"><b id=\"metric-coverage\">--</b><span>line coverage</span></div>\n </div>\n </section>\n <section>\n <h2>Active Items</h2>\n <div id=\"active-items\" class=\"items\"></div>\n </section>\n <section>\n <h2>Subsprints</h2>\n <div id=\"subsprints\"></div>\n </section>\n </div>\n <aside>\n <section>\n <h2>Hotspots</h2>\n <div id=\"hotspots\" class=\"items\"></div>\n </section>\n <section>\n <h2>Timeline</h2>\n <div id=\"timeline\" class=\"timeline\"></div>\n </section>\n </aside>\n</main>\n<script>\nfunction byId(id){return document.getElementById(id)}\nfunction clear(node){node.replaceChildren()}\nfunction text(tag, className, value){\n const node=document.createElement(tag);\n if(className)node.className=className;\n node.textContent=value == null ? \"\" : String(value);\n return node;\n}\nfunction fmt(ts){\n if(!ts)return \"\";\n const d=new Date(ts);\n return Number.isNaN(d.valueOf()) ? ts : d.toLocaleString();\n}\nfunction summarize(s){\n const subs=s.subsprints||[];\n const items=subs.flatMap(function(ss){return ss.items||[]});\n const gateResults=items.reduce(function(n,i){return n+(i.gate_results||[]).length},0);\n return {subs:subs,items:items,open:items.filter(function(i){return i.status===\"open\"}),gateResults:gateResults};\n}\nfunction renderGateResults(item, target){\n const gates=document.createElement(\"div\");\n gates.className=\"gates\";\n const results=item.gate_results||[];\n if(results.length===0){\n gates.append(text(\"div\",\"gate\",\"pending evidence\"));\n } else {\n results.forEach(function(g){\n gates.append(text(\"div\",\"gate \"+(g.passed?\"pass\":\"fail\"),(g.passed?\"pass \":\"fail \")+g.kind+\": \"+g.spec+\" - \"+g.evidence));\n });\n }\n target.append(gates);\n}\nfunction renderItem(item){\n const node=document.createElement(\"div\");\n node.className=\"item \"+(item.status!==\"open\"?\"terminal\":\"\");\n const head=document.createElement(\"div\");\n head.className=\"item-head\";\n head.append(text(\"span\",\"id\",item.id));\n head.append(text(\"span\",\"status \"+item.status,item.disposition||item.status));\n if(item.commit_id)head.append(text(\"span\",\"status\",\"commit \"+item.commit_id));\n if(item.changelog)head.append(text(\"span\",\"status\",item.changelog.verb+\": \"+item.changelog.line));\n head.append(text(\"span\",\"status\",fmt(item.created_at)));\n node.append(head);\n node.append(text(\"div\",\"title\",item.description));\n if(item.code_locations&&item.code_locations.length)node.append(text(\"div\",\"desc\",item.code_locations.join(\", \")));\n renderGateResults(item,node);\n return node;\n}\nfunction renderSubsprint(ss){\n const node=document.createElement(\"div\");\n node.className=\"sub\";\n const row=document.createElement(\"div\");\n row.className=\"row\";\n row.append(text(\"div\",\"title\",ss.id+\" - \"+ss.description));\n row.append(text(\"span\",\"pill \"+ss.status,ss.status));\n node.append(row);\n node.append(text(\"div\",\"desc\",(ss.goals||[]).join(\"; \")));\n const items=document.createElement(\"div\");\n items.className=\"items\";\n (ss.items||[]).forEach(function(item){items.append(renderItem(item))});\n if(!items.childNodes.length)items.append(text(\"div\",\"empty\",\"No items yet.\"));\n node.append(items);\n return node;\n}\nfunction renderTimeline(s){\n const timeline=byId(\"timeline\");\n clear(timeline);\n const events=(s.timeline||[]).slice(-30).reverse();\n if(events.length===0){timeline.append(text(\"div\",\"empty\",\"No events yet.\"));return}\n events.forEach(function(e){\n const node=document.createElement(\"div\");\n node.className=\"event\";\n node.append(text(\"div\",\"time\",fmt(e.ts)));\n const body=document.createElement(\"div\");\n body.append(text(\"b\",\"\",e.id+\" / \"+e.type));\n body.append(text(\"p\",\"\",e.text));\n node.append(body);\n timeline.append(node);\n });\n}\nfunction renderHotspots(s){\n const target=byId(\"hotspots\");\n clear(target);\n const rows=((s.change_map&&s.change_map.hotspots)||[]).slice(0,8);\n if(rows.length===0){target.append(text(\"div\",\"empty\",\"No changed files yet.\"));return}\n rows.forEach(function(row){\n const node=document.createElement(\"div\");\n node.className=\"item\";\n const head=document.createElement(\"div\");\n head.className=\"item-head\";\n head.append(text(\"span\",\"id\",row.file));\n head.append(text(\"span\",\"status\",\"churn \"+row.churn));\n head.append(text(\"span\",\"status\",\"+\"+row.additions+\" -\"+row.deletions));\n node.append(head);\n node.append(text(\"div\",\"desc\",row.language+\" / \"+row.directory+\" / \"+(row.items||[]).join(\", \")));\n target.append(node);\n });\n}\nfunction render(s){\n const summary=summarize(s);\n byId(\"goal\").textContent=s.goal||\"(no sprint)\";\n byId(\"branch\").textContent=s.branch ? \"branch \"+s.branch : \"\";\n byId(\"worktree\").textContent=s.worktree||s.dir||\"\";\n byId(\"created\").textContent=s.created_at ? \"started \"+fmt(s.created_at) : \"\";\n const status=byId(\"status\");\n status.className=\"pill \"+(s.status||\"active\");\n status.textContent=s.status||\"active\";\n byId(\"metric-subsprints\").textContent=String(summary.subs.length);\n byId(\"metric-items\").textContent=String(summary.items.length);\n byId(\"metric-open\").textContent=String(summary.open.length);\n byId(\"metric-gates\").textContent=String(summary.gateResults);\n byId(\"metric-coverage\").textContent=s.coverage&&s.coverage.lines ? String(s.coverage.lines.percent)+\"%\" : \"--\";\n const active=byId(\"active-items\");\n clear(active);\n summary.open.forEach(function(item){active.append(renderItem(item))});\n if(!active.childNodes.length)active.append(text(\"div\",\"empty\",\"No open items.\"));\n const subsprints=byId(\"subsprints\");\n clear(subsprints);\n summary.subs.forEach(function(ss){subsprints.append(renderSubsprint(ss))});\n if(!subsprints.childNodes.length)subsprints.append(text(\"div\",\"empty\",\"No subsprints yet.\"));\n renderHotspots(s);\n renderTimeline(s);\n}\nasync function tick(){\n try{\n const response=await fetch(\"/state\");\n render(await response.json());\n }catch(err){\n byId(\"goal\").textContent=\"Dashboard disconnected\";\n const active=byId(\"active-items\");\n clear(active);\n active.append(text(\"div\",\"error\",err && err.message ? err.message : \"Unable to load state.\"));\n }\n}\ntick();setInterval(tick,2000);\n</script>\n</body>\n</html>";
1
+ export declare const PAGE = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>sprinty dashboard</title>\n<style>\n:root{color-scheme:dark;--bg:#101112;--panel:#191b1f;--panel2:#20242a;--line:#333842;--text:#f0f2f4;--muted:#9ca6b3;--blue:#80b8ff;--green:#7bd88f;--red:#ff8b8b;--yellow:#f4c96b}\n*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.45 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif}\nheader{display:grid;grid-template-columns:1fr auto;gap:1rem;align-items:end;padding:1.25rem 1.5rem;border-bottom:1px solid var(--line);background:#15171a}\nh1{margin:0;font-size:1.25rem;font-weight:700;letter-spacing:0}.meta{display:flex;flex-wrap:wrap;gap:.6rem;color:var(--muted);font-size:.85rem}.pill{border:1px solid var(--line);border-radius:999px;padding:.2rem .55rem;background:var(--panel2);color:var(--muted)}\n.pill.active{color:var(--green);border-color:#3b7049}.pill.closed{color:var(--blue);border-color:#355c88}\nmain{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(22rem,.75fr);gap:1rem;padding:1rem;max-width:1400px;margin:0 auto}\nsection{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:1rem;min-width:0}section+section{margin-top:1rem}\nh2{margin:0 0 .75rem;font-size:.82rem;text-transform:uppercase;color:var(--muted);letter-spacing:.04em}\n.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.6rem}.metric{background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:.75rem}.metric b{display:block;font-size:1.3rem}.metric span{color:var(--muted);font-size:.8rem}\n.sub{border-top:1px solid var(--line);padding:1rem 0}.sub:first-child{border-top:0;padding-top:0}.row{display:flex;gap:.6rem;align-items:center;justify-content:space-between}.title{font-weight:650}.desc{color:var(--muted);margin:.15rem 0 .6rem}.items{display:grid;gap:.55rem}\n.item{border:1px solid var(--line);background:var(--panel2);border-radius:6px;padding:.7rem}.item.terminal{opacity:.72}.item-head{display:flex;flex-wrap:wrap;gap:.45rem;align-items:center;margin-bottom:.3rem}.id{font:12px ui-monospace,monospace;color:var(--blue)}\n.status{font-size:.75rem;color:var(--muted)}.open{color:var(--yellow)}.completed{color:var(--green)}.split{color:var(--blue)}.deprecated,.failed{color:var(--red)}\n.gates{display:grid;gap:.25rem;margin-top:.45rem}.gate{font:12px ui-monospace,monospace;color:var(--muted);overflow-wrap:anywhere}.gate.pass{color:var(--green)}.gate.fail{color:var(--red)}\n.timeline{display:grid;gap:.5rem}.event{display:grid;grid-template-columns:8.5rem minmax(0,1fr);gap:.7rem;border-top:1px solid var(--line);padding-top:.5rem}.event:first-child{border-top:0;padding-top:0}.time{color:var(--muted);font:12px ui-monospace,monospace}.event b{font-size:.82rem}.event p{margin:.12rem 0 0;color:var(--muted);overflow-wrap:anywhere}\n.empty{color:var(--muted);padding:.6rem 0}.error{color:var(--red)}\n@media(max-width:850px){header{grid-template-columns:1fr}main{grid-template-columns:1fr}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.event{grid-template-columns:1fr}}\n</style>\n</head>\n<body>\n<header>\n <div>\n <h1 id=\"goal\">Loading sprint...</h1>\n <div class=\"meta\">\n <span id=\"branch\"></span>\n <span id=\"worktree\"></span>\n <span id=\"created\"></span>\n </div>\n </div>\n <span id=\"status\" class=\"pill\">loading</span>\n</header>\n<main>\n <div>\n <section>\n <h2>Progress</h2>\n <div class=\"metrics\">\n <div class=\"metric\"><b id=\"metric-subsprints\">0</b><span>subsprints</span></div>\n <div class=\"metric\"><b id=\"metric-items\">0</b><span>items</span></div>\n <div class=\"metric\"><b id=\"metric-open\">0</b><span>open</span></div>\n <div class=\"metric\"><b id=\"metric-gates\">0</b><span>gate results</span></div>\n <div class=\"metric\"><b id=\"metric-coverage\">--</b><span>line coverage</span></div>\n </div>\n </section>\n <section>\n <h2>Active Items</h2>\n <div id=\"active-items\" class=\"items\"></div>\n </section>\n <section>\n <h2>Subsprints</h2>\n <div id=\"subsprints\"></div>\n </section>\n </div>\n <aside>\n <section>\n <h2>Hotspots</h2>\n <div id=\"hotspots\" class=\"items\"></div>\n </section>\n <section>\n <h2>Timeline</h2>\n <div id=\"timeline\" class=\"timeline\"></div>\n </section>\n </aside>\n</main>\n<script>\nfunction byId(id){return document.getElementById(id)}\nfunction clear(node){node.replaceChildren()}\nfunction text(tag, className, value){\n const node=document.createElement(tag);\n if(className)node.className=className;\n node.textContent=value == null ? \"\" : String(value);\n return node;\n}\nfunction fmt(ts){\n if(!ts)return \"\";\n const d=new Date(ts);\n return Number.isNaN(d.valueOf()) ? ts : d.toLocaleString();\n}\nfunction summarize(s){\n const subs=s.subsprints||[];\n const items=subs.flatMap(function(ss){return ss.items||[]});\n const gateResults=items.reduce(function(n,i){return n+(i.gate_results||[]).length},0);\n return {subs:subs,items:items,open:items.filter(function(i){return i.status===\"open\"}),gateResults:gateResults};\n}\nfunction renderGateResults(item, target){\n const gates=document.createElement(\"div\");\n gates.className=\"gates\";\n const results=item.gate_results||[];\n if(results.length===0){\n gates.append(text(\"div\",\"gate\",\"pending evidence\"));\n } else {\n results.forEach(function(g){\n gates.append(text(\"div\",\"gate \"+(g.passed?\"pass\":\"fail\"),(g.passed?\"pass \":\"fail \")+g.kind+\": \"+g.spec+\" - \"+g.evidence));\n });\n }\n target.append(gates);\n}\nfunction renderItem(item){\n const node=document.createElement(\"div\");\n node.className=\"item \"+(item.status!==\"open\"?\"terminal\":\"\");\n const head=document.createElement(\"div\");\n head.className=\"item-head\";\n head.append(text(\"span\",\"id\",item.id));\n head.append(text(\"span\",\"status \"+item.status,item.disposition||item.status));\n if(item.commit_id)head.append(text(\"span\",\"status\",\"commit \"+item.commit_id));\n if(item.changelog)head.append(text(\"span\",\"status\",item.changelog.verb+\": \"+item.changelog.line));\n head.append(text(\"span\",\"status\",fmt(item.created_at)));\n node.append(head);\n node.append(text(\"div\",\"title\",item.title||item.description));\n if(item.title&&item.description&&item.title!==item.description)node.append(text(\"div\",\"desc\",item.description));\n if(item.code_locations&&item.code_locations.length)node.append(text(\"div\",\"desc\",item.code_locations.join(\", \")));\n renderGateResults(item,node);\n return node;\n}\nfunction renderSubsprint(ss){\n const node=document.createElement(\"div\");\n node.className=\"sub\";\n const row=document.createElement(\"div\");\n row.className=\"row\";\n row.append(text(\"div\",\"title\",ss.id+\" - \"+ss.description));\n row.append(text(\"span\",\"pill \"+ss.status,ss.status));\n node.append(row);\n node.append(text(\"div\",\"desc\",(ss.goals||[]).join(\"; \")));\n const items=document.createElement(\"div\");\n items.className=\"items\";\n (ss.items||[]).forEach(function(item){items.append(renderItem(item))});\n if(!items.childNodes.length)items.append(text(\"div\",\"empty\",\"No items yet.\"));\n node.append(items);\n return node;\n}\nfunction renderTimeline(s){\n const timeline=byId(\"timeline\");\n clear(timeline);\n const events=(s.timeline||[]).slice(-30).reverse();\n if(events.length===0){timeline.append(text(\"div\",\"empty\",\"No events yet.\"));return}\n events.forEach(function(e){\n const node=document.createElement(\"div\");\n node.className=\"event\";\n node.append(text(\"div\",\"time\",fmt(e.ts)));\n const body=document.createElement(\"div\");\n body.append(text(\"b\",\"\",e.id+\" / \"+e.type));\n body.append(text(\"p\",\"\",e.text));\n node.append(body);\n timeline.append(node);\n });\n}\nfunction renderHotspots(s){\n const target=byId(\"hotspots\");\n clear(target);\n const rows=((s.change_map&&s.change_map.hotspots)||[]).slice(0,8);\n if(rows.length===0){target.append(text(\"div\",\"empty\",\"No changed files yet.\"));return}\n rows.forEach(function(row){\n const node=document.createElement(\"div\");\n node.className=\"item\";\n const head=document.createElement(\"div\");\n head.className=\"item-head\";\n head.append(text(\"span\",\"id\",row.file));\n head.append(text(\"span\",\"status\",\"churn \"+row.churn));\n head.append(text(\"span\",\"status\",\"+\"+row.additions+\" -\"+row.deletions));\n node.append(head);\n node.append(text(\"div\",\"desc\",row.language+\" / \"+row.directory+\" / \"+(row.items||[]).join(\", \")));\n target.append(node);\n });\n}\nfunction render(s){\n const summary=summarize(s);\n byId(\"goal\").textContent=s.goal||\"(no sprint)\";\n byId(\"branch\").textContent=s.branch ? \"branch \"+s.branch : \"\";\n byId(\"worktree\").textContent=\"git \"+(s.dir||s.worktree||\"\")+\" data \"+(s.data_dir||\"\");\n byId(\"created\").textContent=s.created_at ? \"started \"+fmt(s.created_at) : \"\";\n const status=byId(\"status\");\n status.className=\"pill \"+(s.status||\"active\");\n status.textContent=s.status||\"active\";\n byId(\"metric-subsprints\").textContent=String(summary.subs.length);\n byId(\"metric-items\").textContent=String(summary.items.length);\n byId(\"metric-open\").textContent=String(summary.open.length);\n byId(\"metric-gates\").textContent=String(summary.gateResults);\n byId(\"metric-coverage\").textContent=s.coverage&&s.coverage.lines ? String(s.coverage.lines.percent)+\"%\" : \"--\";\n const active=byId(\"active-items\");\n clear(active);\n summary.open.forEach(function(item){active.append(renderItem(item))});\n if(!active.childNodes.length)active.append(text(\"div\",\"empty\",\"No open items.\"));\n const subsprints=byId(\"subsprints\");\n clear(subsprints);\n summary.subs.forEach(function(ss){subsprints.append(renderSubsprint(ss))});\n if(!subsprints.childNodes.length)subsprints.append(text(\"div\",\"empty\",\"No subsprints yet.\"));\n renderHotspots(s);\n renderTimeline(s);\n}\nasync function tick(){\n try{\n const response=await fetch(\"/state\");\n render(await response.json());\n }catch(err){\n byId(\"goal\").textContent=\"Dashboard disconnected\";\n const active=byId(\"active-items\");\n clear(active);\n active.append(text(\"div\",\"error\",err && err.message ? err.message : \"Unable to load state.\"));\n }\n}\ntick();setInterval(tick,2000);\n</script>\n</body>\n</html>";
@@ -111,7 +111,8 @@ function renderItem(item){
111
111
  if(item.changelog)head.append(text("span","status",item.changelog.verb+": "+item.changelog.line));
112
112
  head.append(text("span","status",fmt(item.created_at)));
113
113
  node.append(head);
114
- node.append(text("div","title",item.description));
114
+ node.append(text("div","title",item.title||item.description));
115
+ if(item.title&&item.description&&item.title!==item.description)node.append(text("div","desc",item.description));
115
116
  if(item.code_locations&&item.code_locations.length)node.append(text("div","desc",item.code_locations.join(", ")));
116
117
  renderGateResults(item,node);
117
118
  return node;
@@ -170,7 +171,7 @@ function render(s){
170
171
  const summary=summarize(s);
171
172
  byId("goal").textContent=s.goal||"(no sprint)";
172
173
  byId("branch").textContent=s.branch ? "branch "+s.branch : "";
173
- byId("worktree").textContent=s.worktree||s.dir||"";
174
+ byId("worktree").textContent="git "+(s.dir||s.worktree||"")+" data "+(s.data_dir||"");
174
175
  byId("created").textContent=s.created_at ? "started "+fmt(s.created_at) : "";
175
176
  const status=byId("status");
176
177
  status.className="pill "+(s.status||"active");
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+MZ,CAAC"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgNZ,CAAC"}
@@ -1,6 +1,10 @@
1
1
  import { createServer } from "node:http";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join, normalize } from "node:path";
4
+ import { fileURLToPath } from "node:url";
2
5
  import { PAGE } from "./page.js";
3
6
  export function startDashboard(getState) {
7
+ const staticRoot = dashboardStaticRoot();
4
8
  const server = createServer((req, res) => {
5
9
  if (req.url === "/state") {
6
10
  res.writeHead(200, { "content-type": "application/json" });
@@ -9,6 +13,7 @@ export function startDashboard(getState) {
9
13
  worktree: "",
10
14
  branch: "",
11
15
  dir: "",
16
+ data_dir: "",
12
17
  created_at: new Date().toISOString(),
13
18
  closed_at: null,
14
19
  status: "active",
@@ -16,12 +21,32 @@ export function startDashboard(getState) {
16
21
  timeline: [],
17
22
  context_notes: [],
18
23
  graph: { nodes: [], edges: [], blocked_by: {}, unblocks: {}, topological_order: [], cycles: [] },
24
+ artifacts: [],
19
25
  changelog: [],
20
26
  change_map: { by_file: [], by_directory: [], by_language: [], hotspots: [] },
21
27
  coverage: null,
28
+ coverage_state: { status: "not_configured" },
22
29
  }));
23
30
  return;
24
31
  }
32
+ if (req.url?.startsWith("/assets/")) {
33
+ const pathname = decodeURIComponent(new URL(req.url, "http://127.0.0.1").pathname);
34
+ const asset = normalize(join(staticRoot, pathname));
35
+ if (!asset.startsWith(staticRoot) || !existsSync(asset)) {
36
+ res.writeHead(404);
37
+ res.end("not found");
38
+ return;
39
+ }
40
+ res.writeHead(200, { "content-type": contentType(asset) });
41
+ res.end(readFileSync(asset));
42
+ return;
43
+ }
44
+ const html = join(staticRoot, "index.html");
45
+ if (existsSync(html)) {
46
+ res.writeHead(200, { "content-type": "text/html" });
47
+ res.end(readFileSync(html, "utf8"));
48
+ return;
49
+ }
25
50
  res.writeHead(200, { "content-type": "text/html" });
26
51
  res.end(PAGE);
27
52
  });
@@ -35,4 +60,23 @@ export function startDashboard(getState) {
35
60
  });
36
61
  });
37
62
  }
63
+ function contentType(path) {
64
+ if (path.endsWith(".js"))
65
+ return "text/javascript";
66
+ if (path.endsWith(".css"))
67
+ return "text/css";
68
+ if (path.endsWith(".svg"))
69
+ return "image/svg+xml";
70
+ if (path.endsWith(".png"))
71
+ return "image/png";
72
+ return "application/octet-stream";
73
+ }
74
+ function dashboardStaticRoot() {
75
+ const here = dirname(fileURLToPath(import.meta.url));
76
+ const candidates = [
77
+ join(process.cwd(), "dist", "dashboard-ui"),
78
+ join(here, "..", "dashboard-ui"),
79
+ ];
80
+ return candidates.find((candidate) => existsSync(join(candidate, "index.html"))) ?? candidates[0];
81
+ }
38
82
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAEtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,MAAM,UAAU,cAAc,CAAC,QAAiC;IAC9D,MAAM,MAAM,GAAW,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/C,IAAI,GAAG,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI;gBACnC,IAAI,EAAE,aAAa;gBACnB,QAAQ,EAAE,EAAE;gBACZ,MAAM,EAAE,EAAE;gBACV,GAAG,EAAE,EAAE;gBACP,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACpC,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,QAAQ;gBAChB,UAAU,EAAE,EAAE;gBACd,QAAQ,EAAE,EAAE;gBACZ,aAAa,EAAE,EAAE;gBACjB,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;gBAChG,SAAS,EAAE,EAAE;gBACb,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;gBAC5E,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC,CAAC;YACJ,OAAO;QACT,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;QACpD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,EAAiB,CAAC;YACjD,OAAO,CAAC;gBACN,GAAG,EAAE,oBAAoB,IAAI,EAAE;gBAC/B,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;aACxD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import type { ArtifactView, ItemView, SprintView, SubsprintView, TimelineEntry } from "../../domain/projection.js";
2
+ export type TreeTone = "done" | "active" | "current" | "next" | "blocked" | "muted" | "normal";
3
+ export interface DashboardModel {
4
+ sprint: SprintView;
5
+ activeSubsprint: SubsprintView | null;
6
+ currentItem: ItemView | null;
7
+ nextItem: ItemView | null;
8
+ blockedItems: ItemView[];
9
+ progress: {
10
+ items: {
11
+ total: number;
12
+ done: number;
13
+ open: number;
14
+ percent: number;
15
+ };
16
+ statuses: {
17
+ total: number;
18
+ completed: number;
19
+ open: number;
20
+ split: number;
21
+ deprecated: number;
22
+ };
23
+ gates: {
24
+ total: number;
25
+ passed: number;
26
+ failed: number;
27
+ pending: number;
28
+ };
29
+ subsprints: Array<{
30
+ id: string;
31
+ label: string;
32
+ done: number;
33
+ total: number;
34
+ percent: number;
35
+ }>;
36
+ code: {
37
+ files: number;
38
+ additions: number;
39
+ deletions: number;
40
+ net: number;
41
+ churn: number;
42
+ hotspots: number;
43
+ };
44
+ };
45
+ artifacts: {
46
+ active: ArtifactView[];
47
+ recent: ArtifactView[];
48
+ deprecated: ArtifactView[];
49
+ };
50
+ tree: TreeSubsprint[];
51
+ timeline: TimelineRow[];
52
+ ledger: LedgerRow[];
53
+ }
54
+ export interface TreeSubsprint {
55
+ id: string;
56
+ label: string;
57
+ goals: string[];
58
+ status: SubsprintView["status"];
59
+ defaultOpen: boolean;
60
+ tone: TreeTone;
61
+ progress: {
62
+ done: number;
63
+ total: number;
64
+ percent: number;
65
+ };
66
+ items: TreeItem[];
67
+ }
68
+ export interface TreeItem {
69
+ id: string;
70
+ label: string;
71
+ status: ItemView["status"];
72
+ tone: TreeTone;
73
+ gateSummary: string;
74
+ dependencies: string[];
75
+ blocked: boolean;
76
+ }
77
+ export interface TimelineRow {
78
+ seq: number;
79
+ id: string;
80
+ type: TimelineEntry["type"];
81
+ text: string;
82
+ time: string;
83
+ }
84
+ export interface LedgerRow extends TimelineRow {
85
+ }
86
+ export declare function deriveDashboardModel(sprint: SprintView): DashboardModel;
@@ -0,0 +1,129 @@
1
+ export function deriveDashboardModel(sprint) {
2
+ const items = sprint.subsprints.flatMap((sub) => sub.items);
3
+ const { available, blocked } = orderOpenItems(sprint, items);
4
+ const blockedIds = new Set(blocked.map((item) => item.id));
5
+ const currentItem = available[0] ?? null;
6
+ const nextItem = available[1] ?? null;
7
+ const activeSubsprint = currentItem
8
+ ? sprint.subsprints.find((sub) => sub.id === currentItem.subsprint_id) ?? null
9
+ : sprint.subsprints.find((sub) => sub.status === "open") ?? null;
10
+ const done = items.filter((item) => item.status !== "open").length;
11
+ const statusTotals = statusProgress(items);
12
+ const gateTotals = gateProgress(items);
13
+ const code = codeStats(sprint);
14
+ const subsprintProgress = sprint.subsprints.map((sub) => {
15
+ const total = sub.items.length;
16
+ const subDone = sub.items.filter((item) => item.status !== "open").length;
17
+ return { id: sub.id, label: sub.description, done: subDone, total, percent: percent(subDone, total) };
18
+ });
19
+ return {
20
+ sprint,
21
+ activeSubsprint,
22
+ currentItem,
23
+ nextItem,
24
+ blockedItems: blocked,
25
+ progress: {
26
+ items: { total: items.length, done, open: items.length - done, percent: percent(done, items.length) },
27
+ statuses: statusTotals,
28
+ gates: gateTotals,
29
+ subsprints: subsprintProgress,
30
+ code,
31
+ },
32
+ artifacts: {
33
+ active: sprint.artifacts.filter((artifact) => artifact.status === "active"),
34
+ recent: sprint.artifacts.filter((artifact) => artifact.status === "active").slice(-6).reverse(),
35
+ deprecated: sprint.artifacts.filter((artifact) => artifact.status === "deprecated"),
36
+ },
37
+ tree: sprint.subsprints.map((sub) => treeSubsprint(sub, activeSubsprint, currentItem, nextItem, blockedIds)),
38
+ timeline: sprint.timeline.slice().reverse().map(timelineRow),
39
+ ledger: sprint.timeline.map(timelineRow),
40
+ };
41
+ }
42
+ function orderOpenItems(sprint, items) {
43
+ const openById = new Map(items.filter((item) => item.status === "open").map((item) => [item.id, item]));
44
+ const statusById = new Map(sprint.graph.nodes.map((node) => [node.id, node.status]));
45
+ const ordered = (sprint.graph.topological_order ?? [])
46
+ .map((id) => openById.get(id))
47
+ .filter((item) => Boolean(item));
48
+ const seen = new Set(ordered.map((item) => item.id));
49
+ const allOpen = [...ordered, ...items.filter((item) => item.status === "open" && !seen.has(item.id))];
50
+ const available = [];
51
+ const blocked = [];
52
+ for (const item of allOpen) {
53
+ const blockers = sprint.graph.blocked_by?.[item.id] ?? item.dependencies;
54
+ if (blockers.some((id) => statusById.get(id) === "open"))
55
+ blocked.push(item);
56
+ else
57
+ available.push(item);
58
+ }
59
+ return { available, blocked };
60
+ }
61
+ function treeSubsprint(sub, active, current, next, blockedIds) {
62
+ const total = sub.items.length;
63
+ const done = sub.items.filter((item) => item.status !== "open").length;
64
+ const isActive = active?.id === sub.id;
65
+ return {
66
+ id: sub.id,
67
+ label: sub.description,
68
+ goals: sub.goals,
69
+ status: sub.status,
70
+ defaultOpen: isActive,
71
+ tone: sub.status === "closed" ? "done" : sub.status === "deprecated" ? "muted" : isActive ? "active" : "normal",
72
+ progress: { done, total, percent: percent(done, total) },
73
+ items: sub.items.map((item) => ({
74
+ id: item.id,
75
+ label: itemTitle(item),
76
+ status: item.status,
77
+ tone: item.id === current?.id ? "current" : item.id === next?.id ? "next" : blockedIds.has(item.id) ? "blocked" : item.status === "open" ? "normal" : "muted",
78
+ gateSummary: gateSummary(item),
79
+ dependencies: item.dependencies,
80
+ blocked: blockedIds.has(item.id),
81
+ })),
82
+ };
83
+ }
84
+ function statusProgress(items) {
85
+ return {
86
+ total: items.length,
87
+ completed: items.filter((item) => item.status === "completed").length,
88
+ open: items.filter((item) => item.status === "open").length,
89
+ split: items.filter((item) => item.status === "split").length,
90
+ deprecated: items.filter((item) => item.status === "deprecated").length,
91
+ };
92
+ }
93
+ function gateProgress(items) {
94
+ const total = items.reduce((sum, item) => sum + item.gates.length, 0);
95
+ const passed = items.reduce((sum, item) => sum + item.gate_results.filter((gate) => gate.passed).length, 0);
96
+ const failed = items.reduce((sum, item) => sum + item.gate_results.filter((gate) => !gate.passed).length, 0);
97
+ return { total, passed, failed, pending: Math.max(0, total - passed - failed) };
98
+ }
99
+ function codeStats(sprint) {
100
+ return {
101
+ files: sprint.change_map.by_file.length,
102
+ additions: sprint.change_map.by_file.reduce((sum, row) => sum + row.additions, 0),
103
+ deletions: sprint.change_map.by_file.reduce((sum, row) => sum + row.deletions, 0),
104
+ net: sprint.change_map.by_file.reduce((sum, row) => sum + row.net, 0),
105
+ churn: sprint.change_map.by_file.reduce((sum, row) => sum + row.churn, 0),
106
+ hotspots: sprint.change_map.hotspots.length,
107
+ };
108
+ }
109
+ function gateSummary(item) {
110
+ if (item.gates.length === 0)
111
+ return "no gates";
112
+ const passed = item.gate_results.filter((gate) => gate.passed).length;
113
+ const failed = item.gate_results.filter((gate) => !gate.passed).length;
114
+ const pending = Math.max(0, item.gates.length - passed - failed);
115
+ return `${passed}/${item.gates.length} pass${failed ? `, ${failed} fail` : ""}${pending ? `, ${pending} pending` : ""}`;
116
+ }
117
+ function itemTitle(item) {
118
+ const title = item.title;
119
+ if (title?.trim())
120
+ return title;
121
+ return item.description.split(/\s+/).slice(0, 10).join(" ");
122
+ }
123
+ function timelineRow(entry) {
124
+ return { seq: entry.seq, id: entry.id, type: entry.type, text: entry.text, time: entry.ts };
125
+ }
126
+ function percent(value, total) {
127
+ return total === 0 ? 0 : Math.round((value / total) * 100);
128
+ }
129
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/dashboard/ui/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import "./styles.css";