@leonarto/spec-embryo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +156 -0
  2. package/package.json +48 -0
  3. package/src/backends/base.ts +18 -0
  4. package/src/backends/deterministic.ts +105 -0
  5. package/src/backends/index.ts +26 -0
  6. package/src/backends/prompt.ts +169 -0
  7. package/src/backends/subprocess.ts +198 -0
  8. package/src/cli.ts +111 -0
  9. package/src/commands/agents.ts +16 -0
  10. package/src/commands/current.ts +95 -0
  11. package/src/commands/doctor.ts +12 -0
  12. package/src/commands/handoff.ts +64 -0
  13. package/src/commands/init.ts +101 -0
  14. package/src/commands/reshape.ts +20 -0
  15. package/src/commands/resume.ts +19 -0
  16. package/src/commands/spec.ts +108 -0
  17. package/src/commands/status.ts +98 -0
  18. package/src/commands/task.ts +190 -0
  19. package/src/commands/ui.ts +35 -0
  20. package/src/domain.ts +357 -0
  21. package/src/engine.ts +290 -0
  22. package/src/frontmatter.ts +83 -0
  23. package/src/index.ts +75 -0
  24. package/src/paths.ts +32 -0
  25. package/src/repository.ts +807 -0
  26. package/src/services/adoption.ts +169 -0
  27. package/src/services/agents.ts +191 -0
  28. package/src/services/dashboard.ts +776 -0
  29. package/src/services/details.ts +453 -0
  30. package/src/services/doctor.ts +452 -0
  31. package/src/services/layout.ts +420 -0
  32. package/src/services/spec-answer-evaluation.ts +103 -0
  33. package/src/services/spec-import.ts +217 -0
  34. package/src/services/spec-questions.ts +343 -0
  35. package/src/services/ui.ts +34 -0
  36. package/src/storage.ts +57 -0
  37. package/src/templates.ts +270 -0
  38. package/tsconfig.json +17 -0
  39. package/web/package.json +24 -0
  40. package/web/src/app.css +83 -0
  41. package/web/src/app.d.ts +6 -0
  42. package/web/src/app.html +11 -0
  43. package/web/src/lib/components/AnalysisFilters.svelte +293 -0
  44. package/web/src/lib/components/DocumentBody.svelte +100 -0
  45. package/web/src/lib/components/MultiSelectDropdown.svelte +280 -0
  46. package/web/src/lib/components/SelectDropdown.svelte +265 -0
  47. package/web/src/lib/server/project-root.ts +34 -0
  48. package/web/src/lib/task-board.ts +20 -0
  49. package/web/src/routes/+layout.server.ts +57 -0
  50. package/web/src/routes/+layout.svelte +421 -0
  51. package/web/src/routes/+layout.ts +1 -0
  52. package/web/src/routes/+page.svelte +530 -0
  53. package/web/src/routes/specs/+page.svelte +416 -0
  54. package/web/src/routes/specs/[specId]/+page.server.ts +81 -0
  55. package/web/src/routes/specs/[specId]/+page.svelte +675 -0
  56. package/web/src/routes/tasks/+page.svelte +341 -0
  57. package/web/src/routes/tasks/[taskId]/+page.server.ts +12 -0
  58. package/web/src/routes/tasks/[taskId]/+page.svelte +431 -0
  59. package/web/src/routes/timeline/+page.svelte +1093 -0
  60. package/web/svelte.config.js +10 -0
  61. package/web/tsconfig.json +9 -0
  62. package/web/vite.config.ts +11 -0
@@ -0,0 +1,57 @@
1
+ import type { LayoutServerLoad } from "./$types";
2
+ import { buildDashboardData, type DashboardTimeWindow, type DashboardTimelineMode } from "../../../src/services/dashboard.ts";
3
+ import { resolveProjectRoot } from "../lib/server/project-root.ts";
4
+
5
+ const timeWindows = new Set<DashboardTimeWindow>(["7d", "30d", "90d"]);
6
+ const timelineModes = new Set<DashboardTimelineMode>(["active", "all"]);
7
+
8
+ function parseSelectedSpecIds(searchParams: URLSearchParams): string[] {
9
+ const fromRepeated = searchParams.getAll("spec");
10
+ if (fromRepeated.length > 0) {
11
+ return fromRepeated.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
12
+ }
13
+
14
+ const fromSingle = searchParams.get("spec");
15
+ if (!fromSingle) {
16
+ return [];
17
+ }
18
+
19
+ return fromSingle.split(",").map((value) => value.trim()).filter(Boolean);
20
+ }
21
+
22
+ function parseTimeWindow(searchParams: URLSearchParams): DashboardTimeWindow | undefined {
23
+ const value = searchParams.get("window");
24
+ if (!value || !timeWindows.has(value as DashboardTimeWindow)) {
25
+ return undefined;
26
+ }
27
+
28
+ return value as DashboardTimeWindow;
29
+ }
30
+
31
+ function parseTimelineMode(searchParams: URLSearchParams): DashboardTimelineMode | undefined {
32
+ const value = searchParams.get("timeline_mode");
33
+ if (!value || !timelineModes.has(value as DashboardTimelineMode)) {
34
+ return undefined;
35
+ }
36
+
37
+ return value as DashboardTimelineMode;
38
+ }
39
+
40
+ function parseSelectedTimelineTaskId(searchParams: URLSearchParams): string | undefined {
41
+ const value = searchParams.get("selected_task");
42
+ return value?.trim() || undefined;
43
+ }
44
+
45
+ export const load: LayoutServerLoad = async ({ url }) => {
46
+ const rootDir = resolveProjectRoot();
47
+ const dashboard = await buildDashboardData(rootDir, {
48
+ selectedSpecIds: parseSelectedSpecIds(url.searchParams),
49
+ timeWindow: parseTimeWindow(url.searchParams),
50
+ timelineMode: parseTimelineMode(url.searchParams),
51
+ selectedTimelineTaskId: parseSelectedTimelineTaskId(url.searchParams),
52
+ });
53
+
54
+ return {
55
+ dashboard,
56
+ };
57
+ };
@@ -0,0 +1,421 @@
1
+ <script lang="ts">
2
+ import "../app.css";
3
+ import { page } from "$app/state";
4
+ import {
5
+ ChartNoAxesGantt,
6
+ FolderKanban,
7
+ LayoutDashboard,
8
+ NotebookText,
9
+ ShieldAlert,
10
+ ShieldCheck,
11
+ Sparkles,
12
+ } from "@lucide/svelte";
13
+ import type { DashboardData } from "../../../src/services/dashboard.ts";
14
+
15
+ let { data, children } = $props();
16
+
17
+ const dashboard = $derived(data.dashboard as DashboardData);
18
+
19
+ const navItems = [
20
+ { href: "/", label: "Dash", caption: "Overview", icon: LayoutDashboard },
21
+ { href: "/tasks", label: "Tasks", caption: "Kanban", icon: FolderKanban },
22
+ { href: "/specs", label: "Specs", caption: "Intent", icon: NotebookText },
23
+ { href: "/timeline", label: "Gantt", caption: "Timeline", icon: ChartNoAxesGantt },
24
+ ];
25
+
26
+ const activePath = $derived(page.url.pathname);
27
+ const activeSearch = $derived(page.url.search);
28
+ </script>
29
+
30
+ <svelte:head>
31
+ <title>{dashboard.project.name} Workspace</title>
32
+ </svelte:head>
33
+
34
+ <div class="workspace-shell">
35
+ <aside class="sidebar">
36
+ <div class="brand">
37
+ <div class="brand-mark">
38
+ <Sparkles size={16} strokeWidth={1.8} />
39
+ <span>spm</span>
40
+ </div>
41
+
42
+ <div class="brand-copy">
43
+ <p class="eyebrow">spec embryo</p>
44
+ <h1>{dashboard.project.name}</h1>
45
+ <p class="brand-summary">Project memory views with deterministic state underneath.</p>
46
+ </div>
47
+ </div>
48
+
49
+ <nav class="nav-stack" aria-label="Workspace views">
50
+ {#each navItems as item}
51
+ {@const Icon = item.icon}
52
+ <a class:active={activePath === item.href} href={`${item.href}${activeSearch}`}>
53
+ <span class="nav-badge">
54
+ <Icon size={17} strokeWidth={1.9} />
55
+ </span>
56
+ <span class="nav-copy">
57
+ <strong>{item.label}</strong>
58
+ <small>{item.caption}</small>
59
+ </span>
60
+ </a>
61
+ {/each}
62
+ </nav>
63
+
64
+ <div class="sidebar-note">
65
+ <div class="sidebar-note-head">
66
+ <p class="eyebrow">Memory home</p>
67
+ <strong>{dashboard.project.memoryDir}</strong>
68
+ </div>
69
+ <p>{dashboard.currentState.summary}</p>
70
+ </div>
71
+ </aside>
72
+
73
+ <div class="main-column">
74
+ <header class="topbar">
75
+ <div class="topbar-copy">
76
+ <p class="eyebrow">Current focus</p>
77
+ <h2>{dashboard.currentState.focus}</h2>
78
+ </div>
79
+
80
+ <div class="signal-row">
81
+ <div class="signal-card">
82
+ <NotebookText size={14} strokeWidth={1.9} />
83
+ <span>Specs</span>
84
+ <strong>{dashboard.counts.specs}</strong>
85
+ </div>
86
+ <div class="signal-card">
87
+ <FolderKanban size={14} strokeWidth={1.9} />
88
+ <span>In Progress</span>
89
+ <strong>{dashboard.counts.inProgressTasks}</strong>
90
+ </div>
91
+ <div class="signal-card">
92
+ <ChartNoAxesGantt size={14} strokeWidth={1.9} />
93
+ <span>Blocked</span>
94
+ <strong>{dashboard.counts.blockedTasks}</strong>
95
+ </div>
96
+ <div class="signal-card {dashboard.agents.ok ? 'signal-ok' : 'signal-warn'}">
97
+ {#if dashboard.agents.ok}
98
+ <ShieldCheck size={14} strokeWidth={1.9} />
99
+ {:else}
100
+ <ShieldAlert size={14} strokeWidth={1.9} />
101
+ {/if}
102
+ <span>AGENTS</span>
103
+ <strong>{dashboard.agents.ok ? "Healthy" : "Watch"}</strong>
104
+ </div>
105
+ </div>
106
+ </header>
107
+
108
+ <main class="view-frame">
109
+ {@render children()}
110
+ </main>
111
+ </div>
112
+ </div>
113
+
114
+ <style>
115
+ .workspace-shell {
116
+ display: grid;
117
+ grid-template-columns: 15.75rem minmax(0, 1fr);
118
+ height: 100dvh;
119
+ min-height: 100vh;
120
+ max-height: 100dvh;
121
+ overflow: hidden;
122
+ }
123
+
124
+ .sidebar {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 1rem;
128
+ padding: 0.95rem 0.8rem;
129
+ border-right: 1px solid var(--line);
130
+ background:
131
+ linear-gradient(180deg, rgba(255, 252, 247, 0.96), rgba(247, 241, 232, 0.88));
132
+ backdrop-filter: blur(18px);
133
+ box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.45);
134
+ min-height: 0;
135
+ overflow-y: auto;
136
+ overscroll-behavior: contain;
137
+ }
138
+
139
+ .brand {
140
+ display: grid;
141
+ gap: 0.75rem;
142
+ }
143
+
144
+ .brand-mark {
145
+ display: inline-grid;
146
+ grid-auto-flow: column;
147
+ align-items: center;
148
+ justify-content: start;
149
+ gap: 0.45rem;
150
+ width: fit-content;
151
+ padding: 0.45rem 0.7rem;
152
+ border-radius: 999px;
153
+ border: 1px solid rgba(24, 22, 18, 0.14);
154
+ background: linear-gradient(135deg, rgba(15, 141, 96, 0.16), rgba(255, 255, 255, 0.76));
155
+ box-shadow: var(--shadow-soft);
156
+ font-family: var(--display-font);
157
+ font-size: 1.05rem;
158
+ text-transform: lowercase;
159
+ }
160
+
161
+ .brand-copy {
162
+ display: grid;
163
+ gap: 0.18rem;
164
+ }
165
+
166
+ .brand-summary {
167
+ margin: 0;
168
+ color: var(--muted);
169
+ font-size: 0.83rem;
170
+ line-height: 1.45;
171
+ }
172
+
173
+ .eyebrow {
174
+ margin: 0 0 0.25rem;
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.14em;
177
+ font-size: 0.68rem;
178
+ color: var(--muted);
179
+ }
180
+
181
+ h1,
182
+ h2 {
183
+ margin: 0;
184
+ font-family: var(--display-font);
185
+ line-height: 1;
186
+ }
187
+
188
+ h1 {
189
+ font-size: 1.2rem;
190
+ }
191
+
192
+ h2 {
193
+ font-size: clamp(1.35rem, 2vw, 2rem);
194
+ max-width: 58rem;
195
+ }
196
+
197
+ .nav-stack {
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 0.4rem;
201
+ }
202
+
203
+ .nav-stack a {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 0.6rem;
207
+ padding: 0.58rem 0.62rem;
208
+ border-radius: 1.05rem;
209
+ text-decoration: none;
210
+ border: 1px solid transparent;
211
+ transition:
212
+ transform 120ms ease,
213
+ border-color 120ms ease,
214
+ background 120ms ease,
215
+ box-shadow 120ms ease;
216
+ }
217
+
218
+ .nav-stack a:hover,
219
+ .nav-stack a.active {
220
+ transform: translateX(1px);
221
+ border-color: rgba(24, 22, 18, 0.12);
222
+ background: rgba(255, 255, 255, 0.76);
223
+ box-shadow: var(--shadow-soft);
224
+ }
225
+
226
+ .nav-stack a.active {
227
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(248, 244, 237, 0.92));
228
+ border-color: rgba(24, 22, 18, 0.18);
229
+ }
230
+
231
+ .nav-badge {
232
+ display: inline-flex;
233
+ align-items: center;
234
+ justify-content: center;
235
+ width: 2.1rem;
236
+ height: 2.1rem;
237
+ border-radius: 0.85rem;
238
+ border: 1px solid var(--line);
239
+ background: rgba(255, 255, 255, 0.86);
240
+ color: var(--muted-soft);
241
+ flex-shrink: 0;
242
+ }
243
+
244
+ .nav-copy {
245
+ display: flex;
246
+ flex-direction: column;
247
+ min-width: 0;
248
+ }
249
+
250
+ .nav-copy strong {
251
+ font-size: 0.9rem;
252
+ line-height: 1.1;
253
+ }
254
+
255
+ .nav-copy small,
256
+ .sidebar-note p {
257
+ color: var(--muted);
258
+ }
259
+
260
+ .sidebar-note {
261
+ margin-top: auto;
262
+ display: grid;
263
+ gap: 0.45rem;
264
+ padding: 0.82rem;
265
+ border-radius: 1.15rem;
266
+ border: 1px solid var(--line);
267
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.7), rgba(250, 245, 237, 0.78));
268
+ box-shadow: var(--shadow-soft);
269
+ }
270
+
271
+ .sidebar-note-head {
272
+ display: grid;
273
+ gap: 0.1rem;
274
+ }
275
+
276
+ .sidebar-note strong {
277
+ font-size: 0.86rem;
278
+ line-height: 1.35;
279
+ }
280
+
281
+ .main-column {
282
+ display: flex;
283
+ flex-direction: column;
284
+ min-width: 0;
285
+ min-height: 0;
286
+ height: 100%;
287
+ overflow: hidden;
288
+ padding: 0.95rem 1.15rem 1.15rem;
289
+ }
290
+
291
+ .topbar {
292
+ position: sticky;
293
+ top: 0;
294
+ z-index: 5;
295
+ flex-shrink: 0;
296
+ display: flex;
297
+ justify-content: space-between;
298
+ gap: 1rem;
299
+ align-items: start;
300
+ margin: -0.95rem -1.15rem 0;
301
+ padding: 0.95rem 1.15rem 1.1rem;
302
+ background:
303
+ linear-gradient(180deg, rgba(241, 235, 223, 0.94), rgba(241, 235, 223, 0.82) 70%, rgba(241, 235, 223, 0));
304
+ backdrop-filter: blur(10px);
305
+ }
306
+
307
+ .topbar-copy {
308
+ display: grid;
309
+ gap: 0.18rem;
310
+ }
311
+
312
+ .signal-row {
313
+ display: grid;
314
+ grid-template-columns: repeat(4, minmax(7.25rem, 1fr));
315
+ gap: 0.55rem;
316
+ }
317
+
318
+ .signal-card {
319
+ display: grid;
320
+ grid-template-columns: auto 1fr;
321
+ grid-template-areas:
322
+ "icon label"
323
+ "icon value";
324
+ column-gap: 0.55rem;
325
+ padding: 0.8rem 0.9rem;
326
+ border-radius: 1.05rem;
327
+ border: 1px solid var(--line);
328
+ background: rgba(255, 255, 255, 0.76);
329
+ min-width: 0;
330
+ box-shadow: var(--shadow-soft);
331
+ }
332
+
333
+ .signal-card :global(svg) {
334
+ grid-area: icon;
335
+ align-self: center;
336
+ color: var(--muted-soft);
337
+ }
338
+
339
+ .signal-card span {
340
+ grid-area: label;
341
+ display: block;
342
+ margin-bottom: 0.2rem;
343
+ font-size: 0.72rem;
344
+ text-transform: uppercase;
345
+ letter-spacing: 0.1em;
346
+ color: var(--muted);
347
+ }
348
+
349
+ .signal-card strong {
350
+ grid-area: value;
351
+ font-family: var(--display-font);
352
+ font-size: 1.2rem;
353
+ line-height: 1;
354
+ }
355
+
356
+ .signal-ok {
357
+ background: var(--accent-soft);
358
+ }
359
+
360
+ .signal-warn {
361
+ background: var(--warning-soft);
362
+ }
363
+
364
+ .view-frame {
365
+ flex: 1;
366
+ min-width: 0;
367
+ min-height: 0;
368
+ overflow-y: auto;
369
+ overscroll-behavior: contain;
370
+ padding-top: 1.1rem;
371
+ }
372
+
373
+ .sidebar,
374
+ .view-frame {
375
+ scrollbar-gutter: stable;
376
+ }
377
+
378
+ @media (max-width: 1024px) {
379
+ .workspace-shell {
380
+ grid-template-columns: 13.75rem minmax(0, 1fr);
381
+ }
382
+
383
+ .signal-row {
384
+ grid-template-columns: repeat(2, minmax(8rem, 1fr));
385
+ }
386
+ }
387
+
388
+ @media (max-width: 860px) {
389
+ .workspace-shell {
390
+ grid-template-columns: 1fr;
391
+ height: auto;
392
+ max-height: none;
393
+ overflow: visible;
394
+ }
395
+
396
+ .sidebar {
397
+ border-right: 0;
398
+ border-bottom: 1px solid var(--line);
399
+ overflow: visible;
400
+ }
401
+
402
+ .main-column {
403
+ height: auto;
404
+ overflow: visible;
405
+ }
406
+
407
+ .topbar {
408
+ position: static;
409
+ flex-direction: column;
410
+ margin: 0 0 1.1rem;
411
+ padding: 0;
412
+ background: none;
413
+ backdrop-filter: none;
414
+ }
415
+
416
+ .view-frame {
417
+ overflow: visible;
418
+ padding-top: 0;
419
+ }
420
+ }
421
+ </style>
@@ -0,0 +1 @@
1
+ export const prerender = false;