@copperbox/why 1.0.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 (48) hide show
  1. package/README.md +168 -0
  2. package/dist/anchor.d.ts +112 -0
  3. package/dist/anchor.js +697 -0
  4. package/dist/anchors.d.ts +101 -0
  5. package/dist/anchors.js +223 -0
  6. package/dist/audit.d.ts +120 -0
  7. package/dist/audit.js +483 -0
  8. package/dist/blame.d.ts +91 -0
  9. package/dist/blame.js +294 -0
  10. package/dist/bundle.d.ts +78 -0
  11. package/dist/bundle.js +188 -0
  12. package/dist/capture.d.ts +76 -0
  13. package/dist/capture.js +462 -0
  14. package/dist/cli.d.ts +11 -0
  15. package/dist/cli.js +641 -0
  16. package/dist/dig-state.d.ts +46 -0
  17. package/dist/dig-state.js +146 -0
  18. package/dist/dig.d.ts +125 -0
  19. package/dist/dig.js +380 -0
  20. package/dist/discover.d.ts +11 -0
  21. package/dist/discover.js +47 -0
  22. package/dist/doctor.d.ts +135 -0
  23. package/dist/doctor.js +263 -0
  24. package/dist/evidence.d.ts +73 -0
  25. package/dist/evidence.js +425 -0
  26. package/dist/export.d.ts +67 -0
  27. package/dist/export.js +106 -0
  28. package/dist/find-symbol.d.ts +19 -0
  29. package/dist/find-symbol.js +267 -0
  30. package/dist/git.d.ts +17 -0
  31. package/dist/git.js +51 -0
  32. package/dist/init.d.ts +24 -0
  33. package/dist/init.js +100 -0
  34. package/dist/lint.d.ts +40 -0
  35. package/dist/lint.js +161 -0
  36. package/dist/serve-assets.d.ts +10 -0
  37. package/dist/serve-assets.js +55 -0
  38. package/dist/serve.d.ts +53 -0
  39. package/dist/serve.js +226 -0
  40. package/dist/trace-range.d.ts +22 -0
  41. package/dist/trace-range.js +216 -0
  42. package/package.json +44 -0
  43. package/ui/app.js +323 -0
  44. package/ui/graph.js +164 -0
  45. package/ui/highlight.js +202 -0
  46. package/ui/story-panel.d.ts +9 -0
  47. package/ui/story-panel.js +125 -0
  48. package/ui/style.css +229 -0
@@ -0,0 +1,125 @@
1
+ // Story panel: a dumb renderer over the story payload (`why blame --json`,
2
+ // schemas/story.schema.json). Every semantic arrives precomputed — the hedge
3
+ // is baked into renderedRationale (displayed verbatim, never re-derived) and
4
+ // the expired-constraint blast radius arrives as `downstream` — so this file
5
+ // only lays cards out. Exported as a pure (document, story) → element
6
+ // function so the DOM smoke test can drive it without a browser.
7
+
8
+ /** Same vocabulary the engine precomputes into coverage spans; the contract
9
+ * doc says stories derive theirs the same way from type + status. */
10
+ export function glyphFor(type, status) {
11
+ if (type === "question") return "?";
12
+ if (status === "expired" || status === "superseded") return "⚠";
13
+ return "●";
14
+ }
15
+
16
+ export function formatTarget(target) {
17
+ if (!target.lines) return target.path;
18
+ const { start, end } = target.lines;
19
+ return `${target.path}:${start}${end === start ? "" : `-${end}`}`;
20
+ }
21
+
22
+ function el(doc, tag, className, text) {
23
+ const node = doc.createElement(tag);
24
+ if (className) node.className = className;
25
+ if (text !== undefined) node.textContent = text;
26
+ return node;
27
+ }
28
+
29
+ function isExpiredConstraint(block) {
30
+ return block.type === "constraint" && block.status === "expired";
31
+ }
32
+
33
+ function badge(doc, className, text) {
34
+ return el(doc, "span", `badge ${className}`, text);
35
+ }
36
+
37
+ const EDGE_LABELS = [
38
+ ["becauseOf", "because of"],
39
+ ["insteadOf", "instead of"],
40
+ ["supersededBy", "superseded by"],
41
+ ];
42
+
43
+ function renderCard(doc, block, isWarning) {
44
+ const expired = isExpiredConstraint(block);
45
+ const classes = ["card", `type-${block.type}`];
46
+ if (expired) classes.push("expired");
47
+ if (isWarning) classes.push("warning");
48
+ const card = el(doc, "article", classes.join(" "));
49
+
50
+ const head = el(doc, "header", "card-head");
51
+ head.append(el(doc, "span", `glyph${expired ? " glyph-warn" : ""}`, glyphFor(block.type, block.status)));
52
+ head.append(el(doc, "strong", "card-title", block.title));
53
+ head.append(badge(doc, "type", block.type));
54
+ if (expired) {
55
+ head.append(badge(doc, "loud", `EXPIRED ${block.expired_on ?? "(date unknown)"}`));
56
+ } else if (block.status !== undefined) {
57
+ head.append(badge(doc, `status status-${block.status}`, block.status));
58
+ }
59
+ if (block.happened_on !== undefined) head.append(badge(doc, "date", block.happened_on));
60
+ if (block.type !== "question" && block.confidence !== undefined) {
61
+ head.append(badge(doc, `conf conf-${block.confidence}`, block.confidence));
62
+ }
63
+ card.append(head);
64
+
65
+ // Display verbatim: the mandatory hedge prefix is already in the data.
66
+ if (block.renderedRationale !== "") {
67
+ card.append(el(doc, "p", `rationale${block.hedged ? " hedged" : ""}`, block.renderedRationale));
68
+ }
69
+
70
+ for (const [key, label] of EDGE_LABELS) {
71
+ const edges = block.edges[key];
72
+ if (edges.length === 0) continue;
73
+ const row = el(doc, "div", "edges");
74
+ row.append(el(doc, "span", "edge-label", label));
75
+ const list = el(doc, "ul", "edge-list");
76
+ for (const edge of edges) {
77
+ const suffix = edge.type === undefined ? "" : ` (${edge.type}${edge.status === undefined ? "" : ` — ${edge.status}`})`;
78
+ list.append(el(doc, "li", "edge", `${edge.title}${suffix}`));
79
+ }
80
+ row.append(list);
81
+ card.append(row);
82
+ }
83
+
84
+ for (const decision of block.downstream) {
85
+ card.append(el(doc, "p", "scar", `→ downstream decision "${decision.title}" may now be scar tissue.`));
86
+ }
87
+
88
+ if (block.citations.length > 0) {
89
+ const list = el(doc, "ul", "citations");
90
+ for (const citation of block.citations) {
91
+ const item = el(doc, "li");
92
+ const link = el(doc, "a", "citation", citation.label);
93
+ link.href = citation.url;
94
+ link.target = "_blank";
95
+ link.rel = "noreferrer";
96
+ item.append(link);
97
+ list.append(item);
98
+ }
99
+ card.append(list);
100
+ }
101
+ return card;
102
+ }
103
+
104
+ /** Render one story payload into a detached element the caller mounts. */
105
+ export function renderStoryPanel(doc, story) {
106
+ const root = el(doc, "section", "story");
107
+ root.append(el(doc, "h2", "story-target", story.span ?? formatTarget(story.target)));
108
+ if (story.hits.length === 0) {
109
+ root.append(el(doc, "p", "story-empty", `No concepts anchor ${formatTarget(story.target)}.`));
110
+ }
111
+ // Warnings first, matching the VS Code hover/webview order — both surfaces
112
+ // must tell the same story, and expired constraints stay loud.
113
+ for (const warning of story.warnings) root.append(renderCard(doc, warning, true));
114
+ for (const hit of story.hits) root.append(renderCard(doc, hit, false));
115
+ if (story.hits.length === 0 && story.nearby.length > 0) {
116
+ root.append(el(doc, "h3", "nearby-head", "Anchored concepts nearby (nearest first)"));
117
+ const list = el(doc, "ul", "nearby");
118
+ for (const near of story.nearby) {
119
+ const anchor = near.anchor.lines === undefined ? near.anchor.path : `${near.anchor.path}:${near.anchor.lines}`;
120
+ list.append(el(doc, "li", "nearby-item", `${glyphFor(near.type, near.status)} ${near.title} — ${near.type} · ${anchor}`));
121
+ }
122
+ root.append(list);
123
+ }
124
+ return root;
125
+ }
package/ui/style.css ADDED
@@ -0,0 +1,229 @@
1
+ /* `why serve` UI. One sheet, no imports, no external references — the
2
+ * self-containment test forbids any http(s):// in built assets. Confidence
3
+ * colors and the scar-tissue/question treatments implement the status →
4
+ * treatment table in docs/ui-contract.md. */
5
+
6
+ :root {
7
+ --bg: #14181f;
8
+ --panel: #1b212b;
9
+ --line: #2a3340;
10
+ --fg: #dde3ec;
11
+ --dim: #8b95a5;
12
+ --accent: #5b9cf5;
13
+ --red: #ff6b6b;
14
+ --yellow: #e2a336;
15
+ --ok: #57b47a;
16
+ --question: #b57edc;
17
+ --conf-recorded: #57b47a;
18
+ --conf-corroborated: #7fc98f;
19
+ --conf-inferred: #e2a336;
20
+ --conf-speculative: #d0743c;
21
+ --conf-none: #d0743c;
22
+ }
23
+
24
+ * { box-sizing: border-box; }
25
+
26
+ body {
27
+ margin: 0;
28
+ background: var(--bg);
29
+ color: var(--fg);
30
+ font: 14px/1.5 system-ui, sans-serif;
31
+ }
32
+
33
+ .error { color: var(--red); padding: 0.5rem 1rem; }
34
+ .hint { color: var(--dim); padding: 0.5rem 1rem; }
35
+
36
+ /* --- Header ----------------------------------------------------------- */
37
+
38
+ .topbar {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 1.25rem;
42
+ padding: 0.5rem 1rem;
43
+ background: var(--panel);
44
+ border-bottom: 1px solid var(--line);
45
+ position: sticky;
46
+ top: 0;
47
+ z-index: 2;
48
+ }
49
+
50
+ .brand { font-weight: 700; font-size: 1.1rem; color: var(--accent); }
51
+
52
+ nav .tab {
53
+ color: var(--dim);
54
+ text-decoration: none;
55
+ padding: 0.25rem 0.6rem;
56
+ border-radius: 4px;
57
+ }
58
+ nav .tab.active, nav .tab:hover { color: var(--fg); background: var(--line); }
59
+
60
+ .chips { margin-left: auto; display: flex; gap: 0.5rem; }
61
+ .chip {
62
+ text-decoration: none;
63
+ font-size: 0.8rem;
64
+ padding: 0.15rem 0.6rem;
65
+ border-radius: 999px;
66
+ border: 1px solid var(--line);
67
+ color: var(--dim);
68
+ }
69
+ .chip-red { color: var(--red); border-color: var(--red); }
70
+ .chip-yellow { color: var(--yellow); border-color: var(--yellow); }
71
+ .chip-ok { color: var(--ok); }
72
+
73
+ /* --- Layout ----------------------------------------------------------- */
74
+
75
+ .layout {
76
+ display: grid;
77
+ grid-template-columns: minmax(180px, 240px) 1fr minmax(280px, 380px);
78
+ gap: 0;
79
+ min-height: calc(100vh - 3rem);
80
+ }
81
+
82
+ .sidebar {
83
+ border-right: 1px solid var(--line);
84
+ padding: 0.75rem 0.5rem;
85
+ overflow: auto;
86
+ max-height: calc(100vh - 3rem);
87
+ position: sticky;
88
+ top: 3rem;
89
+ }
90
+
91
+ .story-panel {
92
+ border-left: 1px solid var(--line);
93
+ padding: 0.75rem;
94
+ overflow: auto;
95
+ max-height: calc(100vh - 3rem);
96
+ position: sticky;
97
+ top: 3rem;
98
+ }
99
+
100
+ /* --- File tree ---------------------------------------------------------- */
101
+
102
+ .tree { list-style: none; margin: 0; padding-left: 0.9rem; }
103
+ .tree summary.dir { cursor: pointer; color: var(--dim); }
104
+ .tree a.file { color: var(--fg); text-decoration: none; display: inline-block; padding: 0.05rem 0.25rem; }
105
+ .tree a.file:hover { background: var(--line); border-radius: 3px; }
106
+ .covered-dot { color: var(--accent); font-size: 0.6rem; margin-left: 0.35rem; vertical-align: middle; }
107
+
108
+ /* --- File view (blame gutter) ------------------------------------------- */
109
+
110
+ .file-path { font-size: 1rem; padding: 0.5rem 1rem 0; margin: 0; }
111
+
112
+ table.code {
113
+ border-collapse: collapse;
114
+ width: 100%;
115
+ font: 12px/1.45 ui-monospace, monospace;
116
+ margin-top: 0.5rem;
117
+ }
118
+ table.code td { padding: 0 0.5rem; white-space: pre; vertical-align: top; }
119
+ table.code tr.group-start td { border-top: 1px solid var(--line); }
120
+ table.code tr:hover td { background: #222a36; }
121
+ table.code tr.covered { cursor: pointer; }
122
+
123
+ td.num { color: var(--dim); text-align: right; user-select: none; width: 3.5rem; }
124
+ td.git { color: var(--dim); width: 12rem; max-width: 12rem; overflow: hidden; text-overflow: ellipsis; }
125
+ td.text { width: 100%; }
126
+
127
+ /* Syntax highlighting (ui/highlight.js) — best-effort token classes over the
128
+ * code text; purely presentational, layered on top of the blame payload. */
129
+ td.text .tok-kw { color: #c678dd; }
130
+ td.text .tok-str { color: #98c379; }
131
+ td.text .tok-com { color: var(--dim); font-style: italic; }
132
+ td.text .tok-num { color: #d19a66; }
133
+ td.text .tok-type { color: #e5c07b; }
134
+ td.text .tok-fn { color: #61afef; }
135
+
136
+ /* The why gutter stripe: color by confidence; scar tissue and questions
137
+ * get their own loud treatments. */
138
+ td.why {
139
+ width: 1.4rem;
140
+ min-width: 1.4rem;
141
+ text-align: center;
142
+ border-left: 3px solid transparent;
143
+ color: var(--dim);
144
+ user-select: none;
145
+ }
146
+ td.why.why-conf-recorded { border-left-color: var(--conf-recorded); color: var(--conf-recorded); }
147
+ td.why.why-conf-corroborated { border-left-color: var(--conf-corroborated); color: var(--conf-corroborated); }
148
+ td.why.why-conf-inferred { border-left-color: var(--conf-inferred); color: var(--conf-inferred); }
149
+ td.why.why-conf-speculative { border-left-color: var(--conf-speculative); color: var(--conf-speculative); }
150
+ td.why.why-conf-none { border-left-color: var(--conf-none); color: var(--conf-none); }
151
+ td.why.why-question { border-left-color: var(--question); color: var(--question); border-left-style: dotted; }
152
+ td.why.why-expired {
153
+ border-left-color: var(--red);
154
+ color: var(--red);
155
+ background: rgba(255, 107, 107, 0.12);
156
+ }
157
+
158
+ /* --- Story panel ---------------------------------------------------------- */
159
+
160
+ .story-target { font-size: 0.95rem; margin: 0.25rem 0 0.75rem; color: var(--dim); }
161
+
162
+ .card {
163
+ background: var(--panel);
164
+ border: 1px solid var(--line);
165
+ border-radius: 6px;
166
+ padding: 0.6rem 0.75rem;
167
+ margin-bottom: 0.75rem;
168
+ }
169
+ .card.expired { border-color: var(--red); box-shadow: 0 0 0 1px var(--red); }
170
+
171
+ .card-head { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: baseline; }
172
+ .card-title { margin-right: 0.25rem; }
173
+ .glyph-warn { color: var(--red); }
174
+
175
+ .badge {
176
+ font-size: 0.7rem;
177
+ padding: 0.05rem 0.45rem;
178
+ border-radius: 999px;
179
+ border: 1px solid var(--line);
180
+ color: var(--dim);
181
+ }
182
+ .badge.loud {
183
+ color: #fff;
184
+ background: var(--red);
185
+ border-color: var(--red);
186
+ font-weight: 700;
187
+ }
188
+ .badge.conf-recorded { color: var(--conf-recorded); border-color: var(--conf-recorded); }
189
+ .badge.conf-corroborated { color: var(--conf-corroborated); border-color: var(--conf-corroborated); }
190
+ .badge.conf-inferred { color: var(--conf-inferred); border-color: var(--conf-inferred); }
191
+ .badge.conf-speculative { color: var(--conf-speculative); border-color: var(--conf-speculative); }
192
+
193
+ .rationale { margin: 0.5rem 0; }
194
+ .rationale.hedged { font-style: italic; }
195
+
196
+ .edges { display: flex; gap: 0.5rem; font-size: 0.85rem; }
197
+ .edge-label { color: var(--dim); min-width: 7.5rem; }
198
+ .edge-list { list-style: none; margin: 0; padding: 0; }
199
+
200
+ .scar {
201
+ color: var(--red);
202
+ font-weight: 600;
203
+ margin: 0.5rem 0;
204
+ }
205
+
206
+ .citations { list-style: none; margin: 0.5rem 0 0; padding: 0; font-size: 0.85rem; }
207
+ .citations a { color: var(--accent); }
208
+
209
+ .nearby { list-style: none; padding: 0; }
210
+ .nearby-item { color: var(--dim); padding: 0.15rem 0; }
211
+
212
+ /* --- Graph -------------------------------------------------------------- */
213
+
214
+ .graph-view { padding: 0.75rem 1rem; }
215
+ .legend { display: flex; gap: 1rem; margin-bottom: 0.5rem; color: var(--dim); }
216
+ .legend-dot { margin-right: 0.3rem; }
217
+ .graph-view canvas { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; }
218
+
219
+ /* --- Doctor ---------------------------------------------------------------- */
220
+
221
+ .doctor-view { padding: 0.75rem 1.25rem; max-width: 64rem; }
222
+ .doctor-headline.ok { color: var(--ok); }
223
+ .doctor-headline.red { color: var(--red); }
224
+ .doctor-title { margin: 0.9rem 0 0.25rem; font-size: 0.95rem; }
225
+ .doctor-title.tone-ok { color: var(--ok); }
226
+ .doctor-title.tone-red { color: var(--red); }
227
+ .doctor-title.tone-yellow { color: var(--yellow); }
228
+ .doctor-items { list-style: none; padding-left: 0.5rem; margin: 0; }
229
+ .doctor-item { font: 12px/1.6 ui-monospace, monospace; color: var(--fg); }