@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,341 @@
1
+ <script lang="ts">
2
+ import {
3
+ Blocks,
4
+ CircleCheckBig,
5
+ Filter,
6
+ FolderKanban,
7
+ Hourglass,
8
+ ListChecks,
9
+ Play,
10
+ SquareDashedKanban,
11
+ } from "@lucide/svelte";
12
+ import type { DashboardData, DashboardKanbanColumnId } from "../../../../src/services/dashboard.ts";
13
+ import AnalysisFilters from "../../lib/components/AnalysisFilters.svelte";
14
+ import { countKanbanTasks, filterKanbanBySpecs } from "../../lib/task-board.ts";
15
+
16
+ let { data } = $props();
17
+ const dashboard = $derived(data.dashboard as DashboardData);
18
+ const filteredKanban = $derived(filterKanbanBySpecs(dashboard.kanban, dashboard.analysis.selectedSpecIds));
19
+ const filteredTaskCount = $derived(countKanbanTasks(filteredKanban));
20
+ const hasSpecFilters = $derived(dashboard.analysis.selectedSpecIds.length > 0);
21
+
22
+ const laneNotes: Record<DashboardKanbanColumnId, string> = {
23
+ blocked: "Work stopped by declared dependencies or explicit blocked state.",
24
+ todo: "Ready to be picked up once attention shifts here.",
25
+ in_progress: "Active implementation slices with current movement.",
26
+ review: "Implementation is complete enough for a real review pass before completion.",
27
+ done: "Completed or intentionally cancelled slices kept as durable project memory.",
28
+ };
29
+
30
+ const laneIcons: Record<
31
+ DashboardKanbanColumnId,
32
+ typeof SquareDashedKanban | typeof Play | typeof Blocks | typeof CircleCheckBig | typeof ListChecks
33
+ > = {
34
+ blocked: Blocks,
35
+ todo: SquareDashedKanban,
36
+ in_progress: Play,
37
+ review: ListChecks,
38
+ done: CircleCheckBig,
39
+ };
40
+ </script>
41
+
42
+ <svelte:head>
43
+ <title>{dashboard.project.name} Tasks</title>
44
+ </svelte:head>
45
+
46
+ <section class="view-header">
47
+ <div class="header-copy">
48
+ <p class="eyebrow">Derived work flow</p>
49
+ <h3>Task Kanban</h3>
50
+ </div>
51
+ <p class="intro">
52
+ Effective task state is deterministic here: dependency-blocked `todo` work moves into the blocked lane, and real `review` tasks must clear review before they are marked done.
53
+ </p>
54
+ </section>
55
+
56
+ <AnalysisFilters
57
+ {dashboard}
58
+ eyebrow="Operational focus"
59
+ title="Board spec filters"
60
+ detail="Use one or more specs to focus the Kanban on the active work slice you want to execute. This stays operational, so archived tasks remain hidden even while a spec filter is active."
61
+ showTimeWindow={false}
62
+ resultCount={filteredTaskCount}
63
+ resultLabel="tasks visible on the current board slice"
64
+ hasActiveFilters={hasSpecFilters}
65
+ activeDetail="Archived tasks stay hidden here so the Kanban remains an active-work surface."
66
+ inactiveDetail="Without a spec filter, the board shows all non-archived work across the workspace."
67
+ />
68
+
69
+ {#if hasSpecFilters}
70
+ <section class="filter-note">
71
+ <div class="note-pill">
72
+ <Filter size={14} strokeWidth={1.9} />
73
+ <span>Filtered by {dashboard.analysis.selectedSpecIds.join(", ")}</span>
74
+ </div>
75
+ <p>
76
+ This board intentionally ignores archived tasks even when a spec filter is active, so the selected slice stays operational instead of becoming historical analysis.
77
+ </p>
78
+ </section>
79
+ {/if}
80
+
81
+ <section class="board">
82
+ {#each filteredKanban as column}
83
+ {@const LaneIcon = laneIcons[column.id]}
84
+ <article class="lane">
85
+ <header class="lane-header">
86
+ <div class="lane-title">
87
+ <span class="icon-pill">
88
+ <LaneIcon size={15} strokeWidth={1.9} />
89
+ </span>
90
+ <div>
91
+ <p class="lane-name">{column.title}</p>
92
+ <strong>{column.tasks.length}</strong>
93
+ </div>
94
+ </div>
95
+ <span class="lane-note">{laneNotes[column.id]}</span>
96
+ </header>
97
+
98
+ {#if column.tasks.length === 0}
99
+ <p class="empty">No tasks in this lane.</p>
100
+ {:else}
101
+ <div class="card-stack">
102
+ {#each column.tasks as task}
103
+ <article class="task-card">
104
+ <div class="card-top">
105
+ <a class="task-id" href={`/tasks/${task.id}`}>
106
+ <FolderKanban size={13} strokeWidth={1.9} />
107
+ {task.id}
108
+ </a>
109
+ <span class="meta">p{task.priority}</span>
110
+ <span class="meta">
111
+ <Hourglass size={12} strokeWidth={1.9} />
112
+ {task.effort}u
113
+ </span>
114
+ </div>
115
+
116
+ <h4>{task.title}</h4>
117
+ <p>{task.summary}</p>
118
+
119
+ <div class="chip-row">
120
+ {#each task.specIds as specId}
121
+ <a class="chip" href={`/specs/${specId}`}>{specId}</a>
122
+ {/each}
123
+ </div>
124
+
125
+ <div class="detail-grid">
126
+ <span>raw {task.rawStatus.replaceAll("_", " ")}</span>
127
+ <span>{task.downstreamCount} downstream</span>
128
+ </div>
129
+
130
+ {#if task.unmetDependencies.length > 0}
131
+ <div class="chip-row">
132
+ {#each task.unmetDependencies as dependencyId}
133
+ <a class="chip chip-danger" href={`/tasks/${dependencyId}`}>waits for {dependencyId}</a>
134
+ {/each}
135
+ </div>
136
+ {/if}
137
+ </article>
138
+ {/each}
139
+ </div>
140
+ {/if}
141
+ </article>
142
+ {/each}
143
+ </section>
144
+
145
+ <style>
146
+ .view-header {
147
+ display: flex;
148
+ justify-content: space-between;
149
+ gap: 1rem;
150
+ align-items: end;
151
+ margin-bottom: 1rem;
152
+ }
153
+
154
+ .header-copy {
155
+ display: grid;
156
+ gap: 0.18rem;
157
+ }
158
+
159
+ .eyebrow {
160
+ margin: 0 0 0.35rem;
161
+ text-transform: uppercase;
162
+ letter-spacing: 0.14em;
163
+ font-size: 0.68rem;
164
+ color: var(--muted);
165
+ }
166
+
167
+ h3,
168
+ h4 {
169
+ margin: 0;
170
+ font-family: var(--display-font);
171
+ }
172
+
173
+ h3 {
174
+ font-size: clamp(1.55rem, 1.8vw, 2rem);
175
+ }
176
+
177
+ h4 {
178
+ font-size: 1.14rem;
179
+ line-height: 1.08;
180
+ }
181
+
182
+ .intro {
183
+ max-width: 34rem;
184
+ color: var(--muted);
185
+ line-height: 1.45;
186
+ }
187
+
188
+ .board {
189
+ display: grid;
190
+ grid-template-columns: repeat(5, minmax(17rem, 1fr));
191
+ gap: 0.85rem;
192
+ overflow-x: auto;
193
+ padding-bottom: 0.2rem;
194
+ }
195
+
196
+ .filter-note {
197
+ display: grid;
198
+ gap: 0.5rem;
199
+ margin-bottom: 1rem;
200
+ padding: 0.92rem 1rem;
201
+ border-radius: var(--radius-xl);
202
+ border: 1px solid rgba(15, 141, 96, 0.18);
203
+ background: linear-gradient(180deg, rgba(239, 248, 243, 0.88), rgba(255, 252, 246, 0.94));
204
+ box-shadow: var(--shadow-soft);
205
+ }
206
+
207
+ .filter-note p {
208
+ margin: 0;
209
+ color: var(--muted);
210
+ line-height: 1.45;
211
+ }
212
+
213
+ .note-pill {
214
+ display: inline-flex;
215
+ align-items: center;
216
+ gap: 0.42rem;
217
+ width: fit-content;
218
+ border-radius: 999px;
219
+ padding: 0.35rem 0.68rem;
220
+ border: 1px solid rgba(15, 141, 96, 0.2);
221
+ background: rgba(255, 255, 255, 0.88);
222
+ color: var(--muted-soft);
223
+ font-size: 0.78rem;
224
+ box-shadow: var(--shadow-soft);
225
+ }
226
+
227
+ .lane {
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 0.9rem;
231
+ min-width: 17rem;
232
+ padding: 0.9rem;
233
+ border-radius: var(--radius-xl);
234
+ border: 1px solid var(--line);
235
+ background: var(--panel);
236
+ box-shadow: var(--shadow);
237
+ }
238
+
239
+ .lane-header,
240
+ .lane-title {
241
+ display: grid;
242
+ gap: 0.55rem;
243
+ }
244
+
245
+ .lane-title {
246
+ grid-template-columns: auto 1fr;
247
+ align-items: center;
248
+ }
249
+
250
+ .icon-pill {
251
+ display: inline-flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ width: 2.05rem;
255
+ height: 2.05rem;
256
+ border-radius: 0.9rem;
257
+ border: 1px solid var(--line);
258
+ background: rgba(255, 255, 255, 0.84);
259
+ color: var(--muted-soft);
260
+ }
261
+
262
+ .lane-name {
263
+ margin: 0 0 0.2rem;
264
+ font-size: 0.76rem;
265
+ text-transform: uppercase;
266
+ letter-spacing: 0.12em;
267
+ color: var(--muted);
268
+ }
269
+
270
+ .lane-header strong {
271
+ font-family: var(--display-font);
272
+ font-size: 1.35rem;
273
+ line-height: 1;
274
+ }
275
+
276
+ .lane-note,
277
+ .task-card p,
278
+ .empty {
279
+ color: var(--muted);
280
+ line-height: 1.45;
281
+ }
282
+
283
+ .card-stack {
284
+ display: grid;
285
+ gap: 0.8rem;
286
+ }
287
+
288
+ .task-card {
289
+ display: grid;
290
+ gap: 0.72rem;
291
+ padding: 0.88rem;
292
+ border-radius: var(--radius-lg);
293
+ border: 1px solid var(--line);
294
+ background: rgba(255, 255, 255, 0.84);
295
+ box-shadow: var(--shadow-soft);
296
+ }
297
+
298
+ .card-top,
299
+ .detail-grid,
300
+ .chip-row {
301
+ display: flex;
302
+ flex-wrap: wrap;
303
+ gap: 0.42rem;
304
+ align-items: center;
305
+ }
306
+
307
+ .task-id,
308
+ .meta,
309
+ .chip {
310
+ display: inline-flex;
311
+ align-items: center;
312
+ gap: 0.3rem;
313
+ border-radius: 999px;
314
+ padding: 0.31rem 0.6rem;
315
+ font-size: 0.75rem;
316
+ border: 1px solid var(--line);
317
+ background: rgba(255, 255, 255, 0.9);
318
+ }
319
+
320
+ .task-id {
321
+ font-weight: 700;
322
+ text-decoration: none;
323
+ }
324
+
325
+ .chip-danger {
326
+ background: var(--danger-soft);
327
+ border-color: rgba(151, 45, 45, 0.22);
328
+ }
329
+
330
+ .detail-grid {
331
+ color: var(--muted);
332
+ font-size: 0.78rem;
333
+ }
334
+
335
+ @media (max-width: 900px) {
336
+ .view-header {
337
+ flex-direction: column;
338
+ align-items: start;
339
+ }
340
+ }
341
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { PageServerLoad } from "./$types";
2
+ import { buildTaskDetailData } from "../../../../../src/services/details.ts";
3
+ import { resolveProjectRoot } from "../../../lib/server/project-root.ts";
4
+
5
+ export const load: PageServerLoad = async ({ params }) => {
6
+ const rootDir = resolveProjectRoot();
7
+ const detail = await buildTaskDetailData(rootDir, params.taskId);
8
+
9
+ return {
10
+ detail,
11
+ };
12
+ };