@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
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
+ }