@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.
- package/README.md +156 -0
- package/package.json +48 -0
- package/src/backends/base.ts +18 -0
- package/src/backends/deterministic.ts +105 -0
- package/src/backends/index.ts +26 -0
- package/src/backends/prompt.ts +169 -0
- package/src/backends/subprocess.ts +198 -0
- package/src/cli.ts +111 -0
- package/src/commands/agents.ts +16 -0
- package/src/commands/current.ts +95 -0
- package/src/commands/doctor.ts +12 -0
- package/src/commands/handoff.ts +64 -0
- package/src/commands/init.ts +101 -0
- package/src/commands/reshape.ts +20 -0
- package/src/commands/resume.ts +19 -0
- package/src/commands/spec.ts +108 -0
- package/src/commands/status.ts +98 -0
- package/src/commands/task.ts +190 -0
- package/src/commands/ui.ts +35 -0
- package/src/domain.ts +357 -0
- package/src/engine.ts +290 -0
- package/src/frontmatter.ts +83 -0
- package/src/index.ts +75 -0
- package/src/paths.ts +32 -0
- package/src/repository.ts +807 -0
- package/src/services/adoption.ts +169 -0
- package/src/services/agents.ts +191 -0
- package/src/services/dashboard.ts +776 -0
- package/src/services/details.ts +453 -0
- package/src/services/doctor.ts +452 -0
- package/src/services/layout.ts +420 -0
- package/src/services/spec-answer-evaluation.ts +103 -0
- package/src/services/spec-import.ts +217 -0
- package/src/services/spec-questions.ts +343 -0
- package/src/services/ui.ts +34 -0
- package/src/storage.ts +57 -0
- package/src/templates.ts +270 -0
- package/tsconfig.json +17 -0
- package/web/package.json +24 -0
- package/web/src/app.css +83 -0
- package/web/src/app.d.ts +6 -0
- package/web/src/app.html +11 -0
- package/web/src/lib/components/AnalysisFilters.svelte +293 -0
- package/web/src/lib/components/DocumentBody.svelte +100 -0
- package/web/src/lib/components/MultiSelectDropdown.svelte +280 -0
- package/web/src/lib/components/SelectDropdown.svelte +265 -0
- package/web/src/lib/server/project-root.ts +34 -0
- package/web/src/lib/task-board.ts +20 -0
- package/web/src/routes/+layout.server.ts +57 -0
- package/web/src/routes/+layout.svelte +421 -0
- package/web/src/routes/+layout.ts +1 -0
- package/web/src/routes/+page.svelte +530 -0
- package/web/src/routes/specs/+page.svelte +416 -0
- package/web/src/routes/specs/[specId]/+page.server.ts +81 -0
- package/web/src/routes/specs/[specId]/+page.svelte +675 -0
- package/web/src/routes/tasks/+page.svelte +341 -0
- package/web/src/routes/tasks/[taskId]/+page.server.ts +12 -0
- package/web/src/routes/tasks/[taskId]/+page.svelte +431 -0
- package/web/src/routes/timeline/+page.svelte +1093 -0
- package/web/svelte.config.js +10 -0
- package/web/tsconfig.json +9 -0
- package/web/vite.config.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# spec-embryo
|
|
2
|
+
|
|
3
|
+
`spec-embryo` is a local-first, spec-first, CLI-first project memory and orchestration tool for AI-assisted software work. It attaches to a repo's documentation surface when that is a good fit and otherwise creates its own `spm/` home. Durable memory stays readable in Markdown with TOML frontmatter while deterministic TypeScript derives status, resume context, and next actions from those files.
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
- Local-first: the repo is the source of truth.
|
|
8
|
+
- Deterministic-first: parsing, linking, validation, git inspection, and next-action derivation stay in local code.
|
|
9
|
+
- Spec-first: specs anchor work; tasks attach to specs.
|
|
10
|
+
- Review-gated execution: implementation work should pass through review before it is marked done, and review findings must be fixed before a task leaves the review gate.
|
|
11
|
+
- Resume-friendly: current state and handoffs make interrupted work easy to continue.
|
|
12
|
+
- Agent-native without agent-dependence: prompt-return is first-class, subprocess backends are adapters, and the tool is useful with zero agent configured.
|
|
13
|
+
|
|
14
|
+
## V1 workflow
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun run spec-embryo init
|
|
18
|
+
bun run spm init
|
|
19
|
+
bun run spm status
|
|
20
|
+
bun run spm current show
|
|
21
|
+
bun run spm current set --focus "Implement current-state maintenance flows" --active-task-ids TASK-006
|
|
22
|
+
bun run spm ui
|
|
23
|
+
bun run spm resume
|
|
24
|
+
bun run spm resume --backend prompt
|
|
25
|
+
bun run spm spec list
|
|
26
|
+
bun run spm spec create --title "Local visualization server" --summary "Define the UI slice for managed project memory" --status active --tags ui,server
|
|
27
|
+
bun run spm spec set-status SPEC-003 active
|
|
28
|
+
bun run spm spec import-prompt --source docs/specs --instructions "Preserve product intent and split execution into tasks"
|
|
29
|
+
bun run spm task list
|
|
30
|
+
bun run spm task create --title "Add spec create command" --summary "Let the CLI create new spec docs deterministically" --spec-ids SPEC-002 --depends-on TASK-004 --priority 1
|
|
31
|
+
bun run spm task set-status TASK-005 in_progress
|
|
32
|
+
bun run spm handoff --title "Checkpoint before refactor"
|
|
33
|
+
bun run spm reshape --dry-run
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun add -g @leonarto/spec-embryo
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or install directly from GitHub:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun add -g github:Leonarto/spec-embryo
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
You can also run the entrypoint directly:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
bun run src/index.ts status
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`spm` is the primary short command. `spec-embryo` is the long-form name.
|
|
55
|
+
|
|
56
|
+
## Source-of-truth layout
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
.specpm/
|
|
60
|
+
config.toml
|
|
61
|
+
templates/
|
|
62
|
+
docs/
|
|
63
|
+
spm/
|
|
64
|
+
current.md
|
|
65
|
+
handoffs/
|
|
66
|
+
specs/
|
|
67
|
+
tasks/
|
|
68
|
+
skills/
|
|
69
|
+
architecture.md
|
|
70
|
+
spec-format.md
|
|
71
|
+
src/
|
|
72
|
+
tests/
|
|
73
|
+
web/
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Command summary
|
|
77
|
+
|
|
78
|
+
- `init`: act as the primary adoption entrypoint by classifying repo state, discovering a docs/specs surface when appropriate, and creating or completing the managed memory home.
|
|
79
|
+
- `init --json`: return the same adoption classification plus a structured next-step guide for an already-running agent.
|
|
80
|
+
- `reshape`: bring project memory back to the standard managed layout if it drifted.
|
|
81
|
+
- `doctor`: inspect repo-memory health, adoption state, drift, and broken links without rewriting files.
|
|
82
|
+
- `doctor --json`: return structured findings plus deterministic migration and first-week guidance for agent continuation.
|
|
83
|
+
- `status`: inspect current repo state, linked work, blockers, git changes, changed managed docs, and critical path.
|
|
84
|
+
- `current show` / `current set`: inspect and deterministically update the explicit current-state checkpoint.
|
|
85
|
+
- `ui`: start the local SvelteKit dashboard that visualizes the managed project memory.
|
|
86
|
+
- `resume`: build deterministic resume context for a human, or a prompt bundle for an already-running agent.
|
|
87
|
+
- `handoff`: write a durable handoff Markdown checkpoint from the current state.
|
|
88
|
+
- `spec list`: list specs and their linked tasks.
|
|
89
|
+
- `spec create`: create a new spec doc with a deterministic ID and optional linked tasks.
|
|
90
|
+
- `spec set-status`: update a spec status deterministically in-place.
|
|
91
|
+
- `spec import-prompt`: scan an existing docs/specs folder and emit an agent-ready migration bundle for Spec Embryo format.
|
|
92
|
+
- `task list`: list tasks with dependency and status details.
|
|
93
|
+
- `task create`: create a new task doc with a deterministic ID and update linked specs automatically.
|
|
94
|
+
- `task set-status`: update a task status deterministically in-place, including the explicit `review` stage before `done`.
|
|
95
|
+
- `task archive` / `task archive-done` / `task archive-cancelled`: archive closed tasks without deleting their durable docs.
|
|
96
|
+
- `task restore`: clear archival metadata so a task is visible in active-flow views again.
|
|
97
|
+
|
|
98
|
+
Task docs now support explicit lifecycle metadata such as `created_at`, `completed_at`, and `cancelled_at`, so archival and time-based analysis can build on meaningful state transitions instead of `updated_at`.
|
|
99
|
+
|
|
100
|
+
## Execution modes
|
|
101
|
+
|
|
102
|
+
- `deterministic`: pure local code, the default.
|
|
103
|
+
- `prompt`: return structured context, a recommended prompt, expected output, and subtask prompts instead of nesting another AI.
|
|
104
|
+
- `codex` / `claude`: subprocess adapters that can be enabled later in `.specpm/config.toml`.
|
|
105
|
+
|
|
106
|
+
When a subprocess backend is enabled, Spec Embryo now gives it a stable launch contract:
|
|
107
|
+
|
|
108
|
+
- prompt text token: `{prompt}` or `{prompt_text}`
|
|
109
|
+
- prompt bundle JSON token: `{prompt_bundle}`
|
|
110
|
+
- prompt text file token: `{prompt_text_file}` or legacy `{prompt_file}`
|
|
111
|
+
- prompt bundle file token: `{prompt_bundle_file}` or `{bundle_file}`
|
|
112
|
+
- env vars: `SPM_BACKEND_NAME`, `SPM_PROJECT_ROOT`, `SPM_PROMPT_FILE`, `SPM_PROMPT_TEXT_FILE`, `SPM_PROMPT_BUNDLE_FILE`
|
|
113
|
+
|
|
114
|
+
That keeps prompt-return and subprocess execution aligned instead of making each backend invent its own prompt packaging.
|
|
115
|
+
|
|
116
|
+
## What is implemented now
|
|
117
|
+
|
|
118
|
+
- Bun + TypeScript CLI with small, typed modules
|
|
119
|
+
- Markdown + TOML frontmatter document model
|
|
120
|
+
- Deterministic parsing and validation
|
|
121
|
+
- Git-aware status and resume summaries, including deterministic changed-item derivation
|
|
122
|
+
- Task dependency graph and simple critical path derivation
|
|
123
|
+
- Local SvelteKit workspace launched through `spm ui`
|
|
124
|
+
- Workspace views for overview dashboard, task Kanban, Specs, and a compact grouped Gantt timeline
|
|
125
|
+
- Task Kanban spec filters that narrow the active board without pulling archived work back into the operational flow
|
|
126
|
+
- Multi-spec and lifecycle-window filters for analytical Specs and Gantt slices, including archived work when the goal is slice analysis instead of active-board hygiene
|
|
127
|
+
- Dedicated spec and task detail routes with deterministic not-found states, linked context, and inline source narrative rendering
|
|
128
|
+
- Spec detail pages that can parse `Open Questions`, answer them in batch from the UI, write those answers back into Markdown deterministically, and surface continuation guidance plus an agent evaluation prompt bundle
|
|
129
|
+
- A denser split-pane timeline with spec grouping and active-vs-completed display modes for broader planning scans
|
|
130
|
+
- Interactive timeline tracing with URL-driven task selection, distinct upstream/downstream highlighting, and related-spec emphasis for cross-spec diagnosis
|
|
131
|
+
- Prompt-return backend for agent-safe orchestration
|
|
132
|
+
- Handoff file generation
|
|
133
|
+
- Deterministic spec/task creation with automatic back-linking
|
|
134
|
+
- Initial spec/task/current-state templates and seed files
|
|
135
|
+
- Adoption-aware `init` output that classifies fresh, legacy-docs, partial, and already-adopted repos before seeding files
|
|
136
|
+
|
|
137
|
+
## Architecture
|
|
138
|
+
|
|
139
|
+
Architecture details live in [docs/architecture.md](docs/architecture.md).
|
|
140
|
+
|
|
141
|
+
Spec format details live in [docs/spec-format.md](docs/spec-format.md).
|
|
142
|
+
|
|
143
|
+
## Agent guidance
|
|
144
|
+
|
|
145
|
+
- Root repo guidance lives in [AGENTS.md](AGENTS.md).
|
|
146
|
+
- Growing areas also have local `AGENTS.md` files with folder-specific context.
|
|
147
|
+
- Use `bun run check:agents` after updating those files to keep them concise and fast.
|
|
148
|
+
- The same logic is available in-product through `bun run spm agents check`.
|
|
149
|
+
- `bun run spm status` includes an AGENTS health summary so instruction drift shows up in normal PM flow.
|
|
150
|
+
- The repo-local AGENTS maintenance skill lives in `skills/agents-md-maintainer/`.
|
|
151
|
+
|
|
152
|
+
## Testing
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bun test
|
|
156
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leonarto/spec-embryo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Spec Embryo: local-first, spec-first, CLI-first project memory and orchestration for AI-assisted software work.",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Leonarto/spec-embryo.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Leonarto/spec-embryo#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Leonarto/spec-embryo/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"cli",
|
|
18
|
+
"project-memory",
|
|
19
|
+
"specs",
|
|
20
|
+
"tasks",
|
|
21
|
+
"agent"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bin": {
|
|
25
|
+
"spec-embryo": "src/index.ts",
|
|
26
|
+
"spm": "src/index.ts"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"spec-embryo": "bun run src/index.ts",
|
|
30
|
+
"spm": "bun run src/index.ts",
|
|
31
|
+
"ui": "bun run src/index.ts ui",
|
|
32
|
+
"start": "bun run src/index.ts",
|
|
33
|
+
"test": "bun test",
|
|
34
|
+
"check:agents": "bun run scripts/check-agents.ts",
|
|
35
|
+
"web:check": "bun run --cwd web check"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@lucide/svelte": "^1.7.0",
|
|
39
|
+
"@sveltejs/adapter-auto": "7.0.1",
|
|
40
|
+
"@sveltejs/kit": "2.55.0",
|
|
41
|
+
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
42
|
+
"@types/node": "25.5.0",
|
|
43
|
+
"smol-toml": "1.6.1",
|
|
44
|
+
"svelte": "5.55.1",
|
|
45
|
+
"typescript": "6.0.2",
|
|
46
|
+
"vite": "8.0.3"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BackendName, ProjectConfig, ResumeContext } from "../domain.ts";
|
|
2
|
+
|
|
3
|
+
export interface BackendRunOptions {
|
|
4
|
+
rootDir: string;
|
|
5
|
+
config: ProjectConfig;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
spawn?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BackendRunResult {
|
|
11
|
+
text: string;
|
|
12
|
+
json?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ResumeBackend {
|
|
16
|
+
name: BackendName;
|
|
17
|
+
run(context: ResumeContext, options: BackendRunOptions): Promise<BackendRunResult>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ResumeContext } from "../domain.ts";
|
|
2
|
+
import type { BackendRunOptions, BackendRunResult, ResumeBackend } from "./base.ts";
|
|
3
|
+
|
|
4
|
+
function section(title: string, lines: string[]): string {
|
|
5
|
+
return [title, ...lines.map((line) => ` ${line}`)].join("\n");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class DeterministicBackend implements ResumeBackend {
|
|
9
|
+
name = "deterministic" as const;
|
|
10
|
+
|
|
11
|
+
async run(context: ResumeContext, options: BackendRunOptions): Promise<BackendRunResult> {
|
|
12
|
+
const lines: string[] = [];
|
|
13
|
+
|
|
14
|
+
lines.push(`Project: ${context.projectName}`);
|
|
15
|
+
lines.push(`Focus: ${context.focus}`);
|
|
16
|
+
lines.push(`Summary: ${context.summary}`);
|
|
17
|
+
|
|
18
|
+
if (context.git.available) {
|
|
19
|
+
lines.push(`Git: ${context.git.branch ?? "unknown branch"}${context.git.dirty ? " (dirty)" : " (clean)"}`);
|
|
20
|
+
if (context.git.lastCommit) {
|
|
21
|
+
lines.push(`Last commit: ${context.git.lastCommit}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sections = [
|
|
26
|
+
section(
|
|
27
|
+
"Active Specs",
|
|
28
|
+
context.activeSpecs.length > 0
|
|
29
|
+
? context.activeSpecs.map((spec) => `${spec.id} [${spec.status}] ${spec.title}`)
|
|
30
|
+
: ["No active specs recorded."],
|
|
31
|
+
),
|
|
32
|
+
section(
|
|
33
|
+
"Active Tasks",
|
|
34
|
+
context.activeTasks.length > 0
|
|
35
|
+
? context.activeTasks.map((entry) => {
|
|
36
|
+
const deps = entry.unmetDependencies.length > 0 ? ` | unmet deps: ${entry.unmetDependencies.join(", ")}` : "";
|
|
37
|
+
return `${entry.task.id} [${entry.task.status}] p${entry.task.priority} ${entry.task.title}${deps}`;
|
|
38
|
+
})
|
|
39
|
+
: ["No active tasks recorded."],
|
|
40
|
+
),
|
|
41
|
+
section(
|
|
42
|
+
"Blocked",
|
|
43
|
+
context.blockedTasks.length > 0
|
|
44
|
+
? context.blockedTasks.map((entry) => {
|
|
45
|
+
const reason = entry.unmetDependencies.length > 0 ? entry.unmetDependencies.join(", ") : "manually blocked";
|
|
46
|
+
return `${entry.task.id} ${entry.task.title} | waiting on ${reason}`;
|
|
47
|
+
})
|
|
48
|
+
: ["No blocked tasks."],
|
|
49
|
+
),
|
|
50
|
+
section(
|
|
51
|
+
"Review",
|
|
52
|
+
context.reviewTasks.length > 0
|
|
53
|
+
? context.reviewTasks.map((entry) => `${entry.task.id} [${entry.task.status}] p${entry.task.priority} ${entry.task.title}`)
|
|
54
|
+
: ["No tasks currently in review."],
|
|
55
|
+
),
|
|
56
|
+
section(
|
|
57
|
+
"Ready Next",
|
|
58
|
+
context.readyTasks.length > 0
|
|
59
|
+
? context.readyTasks.slice(0, 5).map((entry) => `${entry.task.id} p${entry.task.priority} ${entry.task.title}`)
|
|
60
|
+
: ["No ready tasks."],
|
|
61
|
+
),
|
|
62
|
+
section(
|
|
63
|
+
"Critical Path",
|
|
64
|
+
context.criticalPath.length > 0
|
|
65
|
+
? context.criticalPath.map((task) => `${task.id} [${task.status}] ${task.title}`)
|
|
66
|
+
: ["No unfinished dependency chain found."],
|
|
67
|
+
),
|
|
68
|
+
section(
|
|
69
|
+
"Next Actions",
|
|
70
|
+
context.nextActions.map((action) => action),
|
|
71
|
+
),
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
if (context.recentHandoff) {
|
|
75
|
+
sections.push(
|
|
76
|
+
section("Recent Handoff", [
|
|
77
|
+
`${context.recentHandoff.id} at ${context.recentHandoff.createdAt}`,
|
|
78
|
+
context.recentHandoff.title,
|
|
79
|
+
context.recentHandoff.summary,
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (context.git.shortStatus.length > 0) {
|
|
85
|
+
sections.push(section("Git Changes", context.git.shortStatus.slice(0, 10)));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (context.changedItems.length > 0) {
|
|
89
|
+
sections.push(
|
|
90
|
+
section(
|
|
91
|
+
"Changed Items",
|
|
92
|
+
context.changedItems.slice(0, 10).map((item) =>
|
|
93
|
+
item.id ? `[${item.kind}] ${item.id}${item.title ? ` ${item.title}` : ""}` : `[${item.kind}] ${item.path}`,
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const text = `${lines.join("\n")}\n\n${sections.join("\n\n")}`;
|
|
100
|
+
return {
|
|
101
|
+
text,
|
|
102
|
+
json: options.json ? context : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BackendName, ProjectConfig } from "../domain.ts";
|
|
2
|
+
import type { ResumeBackend } from "./base.ts";
|
|
3
|
+
import { DeterministicBackend } from "./deterministic.ts";
|
|
4
|
+
import { PromptBackend } from "./prompt.ts";
|
|
5
|
+
import { SubprocessBackend } from "./subprocess.ts";
|
|
6
|
+
|
|
7
|
+
class CodexBackend extends SubprocessBackend {
|
|
8
|
+
name = "codex" as const;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class ClaudeBackend extends SubprocessBackend {
|
|
12
|
+
name = "claude" as const;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveResumeBackend(name: BackendName, config: ProjectConfig): ResumeBackend {
|
|
16
|
+
switch (name) {
|
|
17
|
+
case "deterministic":
|
|
18
|
+
return new DeterministicBackend();
|
|
19
|
+
case "prompt":
|
|
20
|
+
return new PromptBackend();
|
|
21
|
+
case "codex":
|
|
22
|
+
return new CodexBackend(config.backends.codex);
|
|
23
|
+
case "claude":
|
|
24
|
+
return new ClaudeBackend(config.backends.claude);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { ResumeContext } from "../domain.ts";
|
|
2
|
+
import type { BackendRunOptions, BackendRunResult, ResumeBackend } from "./base.ts";
|
|
3
|
+
|
|
4
|
+
export interface PromptBundle {
|
|
5
|
+
context: {
|
|
6
|
+
project_name: string;
|
|
7
|
+
focus: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
active_specs: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
status: string;
|
|
13
|
+
task_ids: string[];
|
|
14
|
+
}>;
|
|
15
|
+
active_tasks: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
status: string;
|
|
19
|
+
spec_ids: string[];
|
|
20
|
+
unmet_dependencies: string[];
|
|
21
|
+
priority: number;
|
|
22
|
+
}>;
|
|
23
|
+
blocked_tasks: Array<{
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
unmet_dependencies: string[];
|
|
27
|
+
}>;
|
|
28
|
+
review_tasks: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
status: string;
|
|
32
|
+
priority: number;
|
|
33
|
+
}>;
|
|
34
|
+
ready_tasks: Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
priority: number;
|
|
38
|
+
}>;
|
|
39
|
+
critical_path: string[];
|
|
40
|
+
recent_handoff: {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
created_at: string;
|
|
44
|
+
summary: string;
|
|
45
|
+
} | null;
|
|
46
|
+
git: {
|
|
47
|
+
branch: string | null;
|
|
48
|
+
dirty: boolean;
|
|
49
|
+
last_commit: string | null;
|
|
50
|
+
short_status: string[];
|
|
51
|
+
};
|
|
52
|
+
changed_items: ResumeContext["changedItems"];
|
|
53
|
+
next_actions: string[];
|
|
54
|
+
};
|
|
55
|
+
recommended_prompt: string;
|
|
56
|
+
expected_output_format: {
|
|
57
|
+
summary_sections: string[];
|
|
58
|
+
require_explicit_file_refs: boolean;
|
|
59
|
+
prefer_deterministic_updates: boolean;
|
|
60
|
+
};
|
|
61
|
+
subtask_prompts: Array<{
|
|
62
|
+
task_id: string;
|
|
63
|
+
prompt: string;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildPromptBundle(context: ResumeContext): PromptBundle {
|
|
68
|
+
return {
|
|
69
|
+
context: {
|
|
70
|
+
project_name: context.projectName,
|
|
71
|
+
focus: context.focus,
|
|
72
|
+
summary: context.summary,
|
|
73
|
+
active_specs: context.activeSpecs.map((spec) => ({
|
|
74
|
+
id: spec.id,
|
|
75
|
+
title: spec.title,
|
|
76
|
+
status: spec.status,
|
|
77
|
+
task_ids: spec.taskIds,
|
|
78
|
+
})),
|
|
79
|
+
active_tasks: context.activeTasks.map((entry) => ({
|
|
80
|
+
id: entry.task.id,
|
|
81
|
+
title: entry.task.title,
|
|
82
|
+
status: entry.task.status,
|
|
83
|
+
spec_ids: entry.task.specIds,
|
|
84
|
+
unmet_dependencies: entry.unmetDependencies,
|
|
85
|
+
priority: entry.task.priority,
|
|
86
|
+
})),
|
|
87
|
+
blocked_tasks: context.blockedTasks.map((entry) => ({
|
|
88
|
+
id: entry.task.id,
|
|
89
|
+
title: entry.task.title,
|
|
90
|
+
unmet_dependencies: entry.unmetDependencies,
|
|
91
|
+
})),
|
|
92
|
+
review_tasks: context.reviewTasks.map((entry) => ({
|
|
93
|
+
id: entry.task.id,
|
|
94
|
+
title: entry.task.title,
|
|
95
|
+
status: entry.task.status,
|
|
96
|
+
priority: entry.task.priority,
|
|
97
|
+
})),
|
|
98
|
+
ready_tasks: context.readyTasks.map((entry) => ({
|
|
99
|
+
id: entry.task.id,
|
|
100
|
+
title: entry.task.title,
|
|
101
|
+
priority: entry.task.priority,
|
|
102
|
+
})),
|
|
103
|
+
critical_path: context.criticalPath.map((task) => task.id),
|
|
104
|
+
recent_handoff: context.recentHandoff
|
|
105
|
+
? {
|
|
106
|
+
id: context.recentHandoff.id,
|
|
107
|
+
title: context.recentHandoff.title,
|
|
108
|
+
created_at: context.recentHandoff.createdAt,
|
|
109
|
+
summary: context.recentHandoff.summary,
|
|
110
|
+
}
|
|
111
|
+
: null,
|
|
112
|
+
git: {
|
|
113
|
+
branch: context.git.branch ?? null,
|
|
114
|
+
dirty: context.git.dirty,
|
|
115
|
+
last_commit: context.git.lastCommit ?? null,
|
|
116
|
+
short_status: context.git.shortStatus,
|
|
117
|
+
},
|
|
118
|
+
changed_items: context.changedItems,
|
|
119
|
+
next_actions: context.nextActions,
|
|
120
|
+
},
|
|
121
|
+
recommended_prompt: [
|
|
122
|
+
"You are resuming work on this repository.",
|
|
123
|
+
`Current focus: ${context.focus}`,
|
|
124
|
+
"Use the structured context provided by Spec Embryo as the source of truth.",
|
|
125
|
+
"Do not restate everything. First answer:",
|
|
126
|
+
"1. where the project currently is",
|
|
127
|
+
"2. what should happen next",
|
|
128
|
+
"3. what is blocked or risky",
|
|
129
|
+
"4. which files/docs should be updated as part of the next slice",
|
|
130
|
+
"Then propose the smallest high-value next implementation step.",
|
|
131
|
+
].join("\n"),
|
|
132
|
+
expected_output_format: {
|
|
133
|
+
summary_sections: ["where-we-are", "next-step", "blockers", "docs-to-touch"],
|
|
134
|
+
require_explicit_file_refs: true,
|
|
135
|
+
prefer_deterministic_updates: true,
|
|
136
|
+
},
|
|
137
|
+
subtask_prompts: context.readyTasks.slice(0, 3).map((entry) => ({
|
|
138
|
+
task_id: entry.task.id,
|
|
139
|
+
prompt: `Work on ${entry.task.id} (${entry.task.title}). Keep changes aligned with spec-backed files and update current state if the focus changes.`,
|
|
140
|
+
})),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function renderPromptBundleText(bundle: PromptBundle): string {
|
|
145
|
+
return [
|
|
146
|
+
bundle.recommended_prompt,
|
|
147
|
+
"",
|
|
148
|
+
"Expected output format:",
|
|
149
|
+
JSON.stringify(bundle.expected_output_format, null, 2),
|
|
150
|
+
"",
|
|
151
|
+
"Structured context:",
|
|
152
|
+
JSON.stringify(bundle.context, null, 2),
|
|
153
|
+
].join("\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export class PromptBackend implements ResumeBackend {
|
|
157
|
+
name = "prompt" as const;
|
|
158
|
+
|
|
159
|
+
async run(context: ResumeContext, options: BackendRunOptions): Promise<BackendRunResult> {
|
|
160
|
+
const bundle = buildPromptBundle(context);
|
|
161
|
+
const jsonText = JSON.stringify(bundle, null, 2);
|
|
162
|
+
const text = ["Prompt-return bundle", "", "Recommended prompt:", renderPromptBundleText(bundle)].join("\n");
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
text: options.json ? jsonText : text,
|
|
166
|
+
json: bundle,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|