@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,83 @@
1
+ import { parse as parseToml } from "smol-toml";
2
+
3
+ export interface ParsedFrontmatter {
4
+ attributes: unknown;
5
+ body: string;
6
+ }
7
+
8
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+
12
+ function formatTomlPrimitive(value: string | number | boolean): string {
13
+ if (typeof value === "string") {
14
+ return JSON.stringify(value);
15
+ }
16
+
17
+ return String(value);
18
+ }
19
+
20
+ function formatTomlValue(value: unknown): string {
21
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
22
+ return formatTomlPrimitive(value);
23
+ }
24
+
25
+ if (Array.isArray(value)) {
26
+ return `[${value.map((item) => formatTomlValue(item)).join(", ")}]`;
27
+ }
28
+
29
+ throw new Error(`Unsupported TOML value: ${String(value)}`);
30
+ }
31
+
32
+ function formatTomlSection(data: Record<string, unknown>, prefix = ""): string[] {
33
+ const lines: string[] = [];
34
+ const nested: Array<{ name: string; value: Record<string, unknown> }> = [];
35
+
36
+ for (const [key, value] of Object.entries(data)) {
37
+ if (value === undefined) {
38
+ continue;
39
+ }
40
+
41
+ if (isPlainObject(value)) {
42
+ nested.push({ name: key, value });
43
+ continue;
44
+ }
45
+
46
+ lines.push(`${key} = ${formatTomlValue(value)}`);
47
+ }
48
+
49
+ for (const section of nested) {
50
+ const sectionName = prefix ? `${prefix}.${section.name}` : section.name;
51
+ lines.push("");
52
+ lines.push(`[${sectionName}]`);
53
+ lines.push(...formatTomlSection(section.value, sectionName));
54
+ }
55
+
56
+ return lines;
57
+ }
58
+
59
+ export function parseTomlFrontmatter(input: string): ParsedFrontmatter {
60
+ const normalized = input.replace(/\r\n/g, "\n");
61
+
62
+ if (!normalized.startsWith("+++\n")) {
63
+ return { attributes: {}, body: normalized.trim() };
64
+ }
65
+
66
+ const match = normalized.match(/^\+\+\+\n([\s\S]*?)\n\+\+\+\n?([\s\S]*)$/);
67
+
68
+ if (!match) {
69
+ throw new Error("Invalid TOML frontmatter. Expected opening and closing +++ fences.");
70
+ }
71
+
72
+ const [, rawAttributes, body = ""] = match;
73
+ return {
74
+ attributes: parseToml(rawAttributes),
75
+ body: body.trim(),
76
+ };
77
+ }
78
+
79
+ export function serializeTomlFrontmatter(attributes: Record<string, unknown>, body: string): string {
80
+ const toml = formatTomlSection(attributes).join("\n").trimEnd();
81
+ const trimmedBody = body.trim();
82
+ return `+++\n${toml}\n+++\n\n${trimmedBody}\n`;
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { cwd, exit } from "node:process";
4
+ import { helpText, parseArgs } from "./cli.ts";
5
+ import { runAgentsCommand } from "./commands/agents.ts";
6
+ import { runCurrentCommand } from "./commands/current.ts";
7
+ import { runDoctorCommand } from "./commands/doctor.ts";
8
+ import { runHandoffCommand } from "./commands/handoff.ts";
9
+ import { runInitCommand } from "./commands/init.ts";
10
+ import { runReshapeCommand } from "./commands/reshape.ts";
11
+ import { runResumeCommand } from "./commands/resume.ts";
12
+ import { runSpecCommand } from "./commands/spec.ts";
13
+ import { runStatusCommand } from "./commands/status.ts";
14
+ import { runTaskCommand } from "./commands/task.ts";
15
+ import { runUiCommand } from "./commands/ui.ts";
16
+
17
+ async function main(): Promise<void> {
18
+ const parsed = parseArgs(process.argv.slice(2));
19
+ const rootDir = cwd();
20
+
21
+ if (!parsed.command || parsed.command === "--help" || parsed.command === "help") {
22
+ console.log(helpText());
23
+ return;
24
+ }
25
+
26
+ let output: string;
27
+
28
+ switch (parsed.command) {
29
+ case "init":
30
+ output = await runInitCommand(rootDir, parsed);
31
+ break;
32
+ case "status":
33
+ output = await runStatusCommand(rootDir, parsed);
34
+ break;
35
+ case "doctor":
36
+ output = await runDoctorCommand(rootDir, parsed);
37
+ break;
38
+ case "ui":
39
+ output = await runUiCommand(rootDir, parsed);
40
+ break;
41
+ case "reshape":
42
+ output = await runReshapeCommand(rootDir, parsed);
43
+ break;
44
+ case "resume":
45
+ output = await runResumeCommand(rootDir, parsed);
46
+ break;
47
+ case "handoff":
48
+ output = await runHandoffCommand(rootDir, parsed);
49
+ break;
50
+ case "current":
51
+ output = await runCurrentCommand(rootDir, parsed);
52
+ break;
53
+ case "agents":
54
+ output = await runAgentsCommand(rootDir, parsed);
55
+ break;
56
+ case "spec":
57
+ output = await runSpecCommand(rootDir, parsed);
58
+ break;
59
+ case "task":
60
+ output = await runTaskCommand(rootDir, parsed);
61
+ break;
62
+ default:
63
+ throw new Error(`Unknown command: ${parsed.command}`);
64
+ }
65
+
66
+ if (output.length > 0) {
67
+ console.log(output);
68
+ }
69
+ }
70
+
71
+ main().catch((error) => {
72
+ const message = error instanceof Error ? error.message : String(error);
73
+ console.error(`spec-embryo error: ${message}`);
74
+ exit(1);
75
+ });
package/src/paths.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { join } from "node:path";
2
+ import type { ProjectConfig } from "./domain.ts";
3
+
4
+ export interface ProjectPaths {
5
+ rootDir: string;
6
+ specpmDir: string;
7
+ configFile: string;
8
+ tempDir: string;
9
+ memoryDir: string;
10
+ stateFile: string;
11
+ handoffsDir: string;
12
+ specsDir: string;
13
+ tasksDir: string;
14
+ skillsDir: string;
15
+ templatesDir: string;
16
+ }
17
+
18
+ export function resolveProjectPaths(rootDir: string, config?: ProjectConfig): ProjectPaths {
19
+ return {
20
+ rootDir,
21
+ specpmDir: join(rootDir, ".specpm"),
22
+ configFile: join(rootDir, ".specpm", "config.toml"),
23
+ tempDir: join(rootDir, ".specpm", "tmp"),
24
+ memoryDir: config ? join(rootDir, config.memoryDir) : join(rootDir, "docs", "spm"),
25
+ stateFile: config ? join(rootDir, config.stateFile) : join(rootDir, "docs", "spm", "current.md"),
26
+ handoffsDir: config ? join(rootDir, config.handoffsDir) : join(rootDir, "docs", "spm", "handoffs"),
27
+ specsDir: config ? join(rootDir, config.specsDir) : join(rootDir, "docs", "spm", "specs"),
28
+ tasksDir: config ? join(rootDir, config.tasksDir) : join(rootDir, "docs", "spm", "tasks"),
29
+ skillsDir: config ? join(rootDir, config.skillsDir) : join(rootDir, "docs", "spm", "skills"),
30
+ templatesDir: join(rootDir, ".specpm", "templates"),
31
+ };
32
+ }