intentdocs 0.8.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 ADDED
@@ -0,0 +1,261 @@
1
+ # intentdocs
2
+
3
+ **IntentDocs is an MCP-native spec-driven development platform for Claude Code, Cursor and other AI coding agents.** It captures product intent as structured specifications — a story map with acceptance criteria, data model and build order — gives agents live access to those specs over MCP or this CLI, and records implementation progress and verification against what was planned.
4
+
5
+ This package is the `intent` CLI: the bridge between your repository and your IntentDocs project. Learn the concepts at [intentdocs.com/intent-driven-development](https://www.intentdocs.com/intent-driven-development) and the file format at [intentdocs.com/intent-md](https://www.intentdocs.com/intent-md).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g intentdocs
11
+ ```
12
+
13
+ Installs the **`intent`** command.
14
+
15
+ Or use without installing:
16
+
17
+ ```bash
18
+ npx intentdocs init
19
+ ```
20
+
21
+ ## How it works
22
+
23
+ intentdocs is the **context engineering harness** for agentic development. It structures everything your AI coding agent needs to work reliably — not just the stories, but the full picture:
24
+
25
+ | Context type | What it holds | How to populate |
26
+ |---|---|---|
27
+ | **Instructions** | Role, goals, boundaries for this project | Project page → Instructions |
28
+ | **Scope** | Story maps, user stories, acceptance criteria | Web UI or `intent add` / `intent propose` |
29
+ | **Memory** | Architectural decisions, lessons learned | `intent decide` / `intent done` |
30
+ | **Examples** | Reference patterns, before/after code | `intent example` |
31
+ | **Tools** | APIs, services, MCP config | Project page → Tools |
32
+ | **Guardrails** | Hard constraints, safety rules | Project page → Guardrails |
33
+
34
+ All of this is assembled into `CLAUDE.md` (or `AGENTS.md` / `GEMINI.md`) by running:
35
+
36
+ ```bash
37
+ intent sync --context
38
+ ```
39
+
40
+ ```
41
+ intentdocs.com Your project
42
+ ┌─────────────────────┐ ┌──────────────────────────┐
43
+ │ Project page │ │ CLI │
44
+ │ ├ Instructions │ │ ├ CLAUDE.md ← harness │
45
+ │ ├ Story maps │◄── sync ─────►│ ├ INTENT.md │
46
+ │ ├ Decisions │ │ ├ .intent/ │
47
+ │ ├ Examples │ --context │ │ stories/<id>.md │
48
+ │ ├ Tools / MCP │──────────────►│ └ mcp.json │
49
+ │ └ Guardrails │ │ │
50
+ └─────────────────────┘ │ Claude Code / Cursor │
51
+ └──────────────────────────┘
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Getting Started
57
+
58
+ ### 1. Log in
59
+
60
+ ```bash
61
+ intent login
62
+ ```
63
+
64
+ Opens your browser to authenticate. One-time per machine.
65
+
66
+ ### 2. Connect your codebase
67
+
68
+ Run inside your project directory after creating a project on [intentdocs.com](https://intentdocs.com):
69
+
70
+ ```bash
71
+ npx intentdocs init
72
+ ```
73
+
74
+ This creates:
75
+ - `CLAUDE.md` — full context harness assembled from your project page (instructions, stories, decisions, examples, tools, guardrails)
76
+ - `INTENT.md` — story map summary (stories, ACs, status)
77
+ - `ARCHITECTURE.md` — pre-filled with your detected tech stack
78
+ - `.intent/config.json` — sync config (auto-gitignored)
79
+
80
+ ### 3. Set up your AI tool
81
+
82
+ ```bash
83
+ intent setup
84
+ ```
85
+
86
+ Configures [Claude Code](https://claude.com/claude-code) or [Cursor](https://cursor.com) with:
87
+ - Slash commands for the full development workflow
88
+ - `CONVENTIONS.md` scaffolding
89
+
90
+ ### 4. Build
91
+
92
+ ```bash
93
+ intent next # Pick up the next story
94
+ intent done <id> # Mark complete + log a decision or lesson
95
+ intent decide # Log an architectural decision
96
+ intent example # Log a reference pattern
97
+ intent sync # Refresh INTENT.md
98
+ intent sync --context # Regenerate CLAUDE.md from your project page
99
+ ```
100
+
101
+ ---
102
+
103
+ ## All Commands
104
+
105
+ ### Setup & sync
106
+
107
+ | Command | Description |
108
+ |---|---|
109
+ | `intent login` | Authenticate (opens browser) |
110
+ | `intent logout` | Log out |
111
+ | `intent init` | Connect project directory to a story map + generate CLAUDE.md |
112
+ | `intent setup` | Configure Claude Code / Cursor with slash commands |
113
+ | `intent sync` | Refresh `INTENT.md` with latest stories |
114
+ | `intent sync --context` | Regenerate `CLAUDE.md` from your full project page context |
115
+
116
+ ### Story management
117
+
118
+ | Command | Description |
119
+ |---|---|
120
+ | `intent status` | Progress dashboard + recent activity |
121
+ | `intent stories [-f filter]` | List stories (`todo` / `done` / keyword) |
122
+ | `intent next` | Next priority story with acceptance criteria |
123
+ | `intent done <id>` | Mark done — prompts for a decision or lesson learned |
124
+ | `intent add <title>` | Create a new story |
125
+ | `intent propose [name]` | Plan a new feature — creates stories + saves a local spec |
126
+ | `intent note <id> [text]` | Add implementation notes to a story |
127
+
128
+ ### Context & memory
129
+
130
+ | Command | Description |
131
+ |---|---|
132
+ | `intent decide [title]` | Log an architectural or product decision |
133
+ | `intent example` | Log a reference pattern (before/after code) for this codebase |
134
+
135
+ ### AI features (Builder plan)
136
+
137
+ | Command | Description |
138
+ |---|---|
139
+ | `intent complexity` | AI complexity scoring — 1-10 score, risks, suggested effort |
140
+ | `intent research` | AI research with your project context |
141
+ | `intent import` | Parse a PRD into structured user stories |
142
+ | `intent next --stub` | Generate a failing test skeleton from acceptance criteria |
143
+
144
+ ### MCP server
145
+
146
+ | Command | Description |
147
+ |---|---|
148
+ | `intent mcp [--mode]` | Start MCP server (`core` / `standard` / `all`) |
149
+
150
+ ---
151
+
152
+ ## Slash Commands (Claude Code / Cursor)
153
+
154
+ Run `intent setup` to install these. Use inside your AI coding agent, not the terminal.
155
+
156
+ | Command | What it does |
157
+ |---|---|
158
+ | `/intent-next` | Pick up the next story |
159
+ | `/intent-implement` | Full implementation playbook — load harness → ACs → code → done |
160
+ | `/intent-propose` | Plan a new feature before writing code |
161
+ | `/intent-review` | Review implementation against acceptance criteria |
162
+ | `/intent-document` | Document what was built |
163
+ | `/intent-test` | Generate tests from acceptance criteria |
164
+ | `/intent-decide` | Log an architectural or product decision |
165
+ | `/intent-example` | Log a reference pattern for this codebase |
166
+ | `/intent-status` | Show story map progress and recent activity |
167
+ | `/intent-done` | Mark a story done + log a decision |
168
+ | `/intent-sync` | Refresh story map or regenerate full CLAUDE.md harness |
169
+ | `/intent-context` | Load focused context for the current story |
170
+
171
+ ---
172
+
173
+ ## MCP Server
174
+
175
+ The intentdocs MCP server exposes **tools** (actions your agent can take) and **resources** (project context your agent can read). Configure it once and your AI coding agent has native access to your full spec.
176
+
177
+ Give Claude Code or Cursor native tool and resource access to your story map.
178
+
179
+ Add to `~/.claude/settings.json` (Claude Code) or your Cursor MCP config:
180
+
181
+ ```json
182
+ {
183
+ "mcpServers": {
184
+ "intentdocs": {
185
+ "command": "npx",
186
+ "args": ["intentdocs", "mcp"],
187
+ "cwd": "/path/to/your/project"
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Available tools (19)
194
+
195
+ | Tool | Description |
196
+ |---|---|
197
+ | `list_projects` | List user's projects |
198
+ | `get_context` | Full product context for a project |
199
+ | `list_stories` | List stories with optional filter |
200
+ | `update_stories` | Batch update story status/fields, including user-story text (`as_a`/`i_want`/`so_that`) |
201
+ | `create_stories` | Batch create new stories |
202
+ | `report_criteria` | Report acceptance criteria pass/fail results |
203
+ | `add_criteria` | Append acceptance criteria to a story — returns new criterion IDs for use with `report_criteria` |
204
+ | `replace_criteria` | Replace ALL acceptance criteria on a story — returns new criterion IDs |
205
+ | `check_release_readiness` | Pre-flight scan for missing ACs and implementation plans |
206
+ | `start_story` | Mark a story in_progress (blocks if another is already in_progress) |
207
+ | `complete_story` | Mark a story done + returns next story in release |
208
+ | `get_status` | Progress summary |
209
+ | `raise_blocker` | Flag a blocker on a story |
210
+ | `add_note` | Add implementation notes |
211
+ | `log_decision` | Log an architectural decision |
212
+ | `analyze_complexity` | AI complexity scoring |
213
+ | `research` | AI research with project context |
214
+ | `import_prd` | Parse a PRD into stories |
215
+ | `get_build_order` | Recommended implementation order |
216
+
217
+ ### MCP Resources
218
+
219
+ Resources let any MCP-compatible agent (Claude Code, Cursor, Codex) discover and read your project spec without invoking commands. Unlike tools, resources are read-only and auto-discoverable — your coding agent can pull exactly the context it needs.
220
+
221
+ | Resource | URI | Description |
222
+ |---|---|---|
223
+ | Story Map | `intentdocs://project/storymap` | Full product context — personas, activities, stories, acceptance criteria |
224
+ | Stories | `intentdocs://project/stories` | All stories with status, priority, effort, and ACs |
225
+ | Status | `intentdocs://project/status` | Progress summary — done/in-progress/todo counts |
226
+ | Decisions | `intentdocs://project/decisions` | Architectural and product decisions |
227
+ | Data Model | `intentdocs://project/data-model` | Schema and data model from your spec |
228
+ | Build Order | `intentdocs://project/build-order` | Recommended implementation order |
229
+
230
+ Resources are always available regardless of tool mode.
231
+
232
+ **Example usage in Claude Code:**
233
+
234
+ Your coding agent can read these directly:
235
+ ```
236
+ Read resource: intentdocs://project/data-model
237
+ ```
238
+
239
+ No command needed — the agent discovers available resources from the MCP server automatically.
240
+
241
+ ---
242
+
243
+ ## AI Features & Pricing
244
+
245
+ AI-powered commands are gated behind the Builder plan. Unlock two ways:
246
+
247
+ 1. **Upgrade** — [intentdocs.com/pricing](https://intentdocs.com/pricing)
248
+ 2. **Bring your own Anthropic key** — works on any plan:
249
+
250
+ ```bash
251
+ intent keys set # add your Anthropic API key (encrypted at rest)
252
+ intent keys status # check whether a key is configured
253
+ intent keys remove # fall back to plan limits
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Links
259
+
260
+ - [intentdocs.com](https://intentdocs.com) — web app
261
+ - [npm](https://www.npmjs.com/package/intentdocs) — this package
package/bin/intent.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/index.js";
@@ -0,0 +1,249 @@
1
+ import {
2
+ ask,
3
+ closePrompt,
4
+ confirm
5
+ } from "./chunk-WPVDURTJ.js";
6
+ import {
7
+ getContext
8
+ } from "./chunk-6XKB6RTU.js";
9
+ import {
10
+ writeConfig
11
+ } from "./chunk-ICOMMHKG.js";
12
+ import {
13
+ API_BASE,
14
+ requireAuth
15
+ } from "./chunk-PCNDP5SX.js";
16
+
17
+ // src/commands/scope.ts
18
+ import { writeFileSync, existsSync } from "fs";
19
+ import { join } from "path";
20
+ async function apiFetch(path, token, opts = {}) {
21
+ return fetch(`${API_BASE}${path}`, {
22
+ ...opts,
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ Authorization: `Bearer ${token}`,
26
+ ...opts.headers ?? {}
27
+ }
28
+ });
29
+ }
30
+ async function scope(projectId) {
31
+ const auth = requireAuth();
32
+ const { token } = auth;
33
+ if (!projectId) {
34
+ projectId = await ask(" Project ID (from `intent start`): ");
35
+ }
36
+ if (!projectId?.trim()) {
37
+ console.log(" Project ID required.");
38
+ closePrompt();
39
+ return;
40
+ }
41
+ console.log("\n \u{1F52C} Scoping your product\n");
42
+ console.log(" I'll ask a few questions to build your story map.\n");
43
+ const journey = await ask(
44
+ " What are the main steps a user takes through your product?\n (e.g. Sign up \u2192 Browse \u2192 Purchase \u2192 Review)\n > "
45
+ );
46
+ const personas = await ask(
47
+ "\n Who are the key personas? Describe 1-2 types of user.\n (e.g. 'Sarah, a busy marketing manager who needs...')\n > "
48
+ );
49
+ const mvp = await ask(
50
+ "\n What's the minimum you need to build to test the idea?\n (e.g. 'A simple landing page with a sign-up form and one core workflow')\n > "
51
+ );
52
+ const constraints = await ask(
53
+ "\n Any technical constraints or preferences?\n (e.g. 'Must be a web app, using React and Supabase')\n > "
54
+ );
55
+ console.log("\n Generating blueprint + story map...");
56
+ console.log(" This takes 1-3 minutes. Doing this manually takes hours.\n");
57
+ const scopingContext = [
58
+ journey ? `User journey: ${journey}` : "",
59
+ personas ? `Key personas: ${personas}` : "",
60
+ mvp ? `MVP scope: ${mvp}` : "",
61
+ constraints ? `Technical constraints: ${constraints}` : ""
62
+ ].filter(Boolean).join("\n");
63
+ const res = await apiFetch("/api/blueprint/generate", token, {
64
+ method: "POST",
65
+ body: JSON.stringify({
66
+ projectId,
67
+ phase: "generation",
68
+ idea: scopingContext,
69
+ messages: [
70
+ { role: "user", content: scopingContext }
71
+ ]
72
+ })
73
+ });
74
+ if (!res.ok) {
75
+ const errText = await res.text();
76
+ if (errText.includes("RATE_LIMIT")) {
77
+ console.error(" Rate limit reached. Try again later or upgrade your plan.");
78
+ } else {
79
+ console.error(` Generation failed: ${errText.slice(0, 200)}`);
80
+ }
81
+ closePrompt();
82
+ return;
83
+ }
84
+ const reader = res.body?.getReader();
85
+ const decoder = new TextDecoder();
86
+ let fullResponse = "";
87
+ let dots = 0;
88
+ if (reader) {
89
+ while (true) {
90
+ const { done, value } = await reader.read();
91
+ if (done) break;
92
+ fullResponse += decoder.decode(value, { stream: true });
93
+ dots++;
94
+ if (dots % 20 === 0) process.stdout.write(".");
95
+ }
96
+ }
97
+ console.log("\n");
98
+ try {
99
+ const cleaned = fullResponse.replace(/^\s*```json\s*/m, "").replace(/\s*```\s*$/m, "").trim();
100
+ const firstBrace = cleaned.indexOf("{");
101
+ const lastBrace = cleaned.lastIndexOf("}");
102
+ if (firstBrace !== -1 && lastBrace > firstBrace) {
103
+ const bp = JSON.parse(cleaned.slice(firstBrace, lastBrace + 1));
104
+ console.log(" \u2713 Blueprint generated!\n");
105
+ if (bp.founding_hypothesis) {
106
+ console.log(` Hypothesis: ${bp.founding_hypothesis.slice(0, 150)}${bp.founding_hypothesis.length > 150 ? "..." : ""}
107
+ `);
108
+ }
109
+ if (bp.personas?.length) {
110
+ console.log(` Personas: ${bp.personas.map((p) => p.name).join(", ")}`);
111
+ }
112
+ if (bp.stories?.length) {
113
+ console.log(` Stories: ${bp.stories.length} user stories generated`);
114
+ }
115
+ if (bp.build_order?.length) {
116
+ console.log(` Build order: ${bp.build_order.length} steps`);
117
+ }
118
+ }
119
+ } catch {
120
+ console.log(" \u2713 Blueprint generated (couldn't parse summary)\n");
121
+ }
122
+ console.log("\n Connecting project to your codebase...");
123
+ const mapsRes = await apiFetch(
124
+ `/api/storymapper/project/${projectId}`,
125
+ token
126
+ );
127
+ let syncToken = null;
128
+ if (mapsRes.ok) {
129
+ const mapData = await mapsRes.json();
130
+ if (mapData.mapId) {
131
+ const tokenRes = await apiFetch(
132
+ `/api/storymapper/${mapData.mapId}/sync-token`,
133
+ token,
134
+ { method: "POST" }
135
+ );
136
+ if (tokenRes.ok) {
137
+ const tokenData = await tokenRes.json();
138
+ syncToken = tokenData.sync_token;
139
+ }
140
+ }
141
+ }
142
+ if (syncToken) {
143
+ writeConfig({ syncToken, apiBase: API_BASE });
144
+ console.log(" \u2713 Connected\n");
145
+ try {
146
+ const config = { syncToken, apiBase: API_BASE };
147
+ const md = await getContext(config);
148
+ writeFileSync(join(process.cwd(), "INTENT.md"), md);
149
+ console.log(" \u2713 Downloaded INTENT.md\n");
150
+ } catch {
151
+ console.log(" \u26A0 Could not download INTENT.md\n");
152
+ }
153
+ }
154
+ const wantMockup = await confirm(
155
+ " Want to generate an HTML mockup of your product?"
156
+ );
157
+ if (wantMockup) {
158
+ const designSystem = await ask(
159
+ "\n Any existing design system? (e.g. Tailwind + shadcn, Material UI, or 'none')\n > "
160
+ );
161
+ const inspiration = await ask(
162
+ "\n Any products or sites that inspire the look & feel?\n > "
163
+ );
164
+ const mockupPrompt = buildMockupPrompt(projectId, designSystem, inspiration);
165
+ const mockupPath = join(process.cwd(), "MOCKUP_PROMPT.md");
166
+ writeFileSync(mockupPath, mockupPrompt);
167
+ console.log(`
168
+ \u2713 Saved MOCKUP_PROMPT.md
169
+ `);
170
+ console.log(" To generate the mockup, tell your AI coding tool:");
171
+ console.log(" 'Read MOCKUP_PROMPT.md and generate the HTML mockup'\n");
172
+ }
173
+ const hasGit = existsSync(join(process.cwd(), ".git"));
174
+ if (!hasGit) {
175
+ const wantRepo = await confirm(" Initialize a git repo for this project?");
176
+ if (wantRepo) {
177
+ const { execSync } = await import("child_process");
178
+ try {
179
+ execSync("git init", { stdio: "ignore" });
180
+ execSync("git add INTENT.md", { stdio: "ignore" });
181
+ if (existsSync(join(process.cwd(), "MOCKUP_PROMPT.md"))) {
182
+ execSync("git add MOCKUP_PROMPT.md", { stdio: "ignore" });
183
+ }
184
+ execSync('git commit -m "Initial commit \u2014 intentdocs project context"', {
185
+ stdio: "ignore"
186
+ });
187
+ console.log(" \u2713 Git repo initialized with first commit\n");
188
+ } catch {
189
+ console.log(" \u26A0 Git init failed \u2014 do it manually\n");
190
+ }
191
+ }
192
+ }
193
+ const { confirm: confirmSetup } = await import("./prompt-WAWCRGHN.js");
194
+ const wantSetup = await confirmSetup(
195
+ " Configure your AI tool (Claude Code / Cursor) with intentdocs agent workflows?"
196
+ );
197
+ if (wantSetup) {
198
+ const { setup } = await import("./setup-SOI2HQAL.js");
199
+ await setup();
200
+ return;
201
+ }
202
+ console.log(`
203
+ \u2705 Your project is scoped and ready to build!
204
+
205
+ Next steps:
206
+ \u2022 intent setup \u2014 configure your AI tool
207
+ \u2022 intent status \u2014 see your story map progress
208
+ \u2022 intent next \u2014 see what to build first
209
+ \u2022 intent done <id> \u2014 mark stories as you complete them
210
+
211
+ Your story map is also live at ${API_BASE}
212
+ Seek advice from the community and our resources.
213
+ `);
214
+ closePrompt();
215
+ }
216
+ function buildMockupPrompt(projectId, designSystem, inspiration) {
217
+ return `# Mockup Generation Prompt
218
+
219
+ Read INTENT.md for the full product context (personas, stories, acceptance criteria).
220
+
221
+ ## Task
222
+ Generate a single-file HTML mockup that demonstrates the core user journey.
223
+ The mockup should be a clickable prototype \u2014 not production code, but enough
224
+ to validate the UX with real users.
225
+
226
+ ## Requirements
227
+ - Single HTML file with inline CSS and minimal JS
228
+ - Mobile-first responsive design
229
+ - All key screens from the user journey (see INTENT.md)
230
+ - Realistic placeholder content (not lorem ipsum)
231
+ - Navigation between screens via anchor links or simple JS
232
+
233
+ ## Design
234
+ ${designSystem && designSystem !== "none" ? `- Design system: ${designSystem}` : "- Use a clean, modern design with system fonts"}
235
+ ${inspiration ? `- Inspiration: ${inspiration}` : "- Keep it simple and functional"}
236
+ - Dark mode preferred
237
+ - Accessible (proper contrast, semantic HTML)
238
+
239
+ ## Output
240
+ Save as \`mockup.html\` in the project root.
241
+
242
+ ## Project ID
243
+ ${projectId}
244
+ `;
245
+ }
246
+
247
+ export {
248
+ scope
249
+ };
@@ -0,0 +1,140 @@
1
+ // src/lib/story-context.ts
2
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ // src/lib/criteria.ts
6
+ function criterionPriority(ac) {
7
+ return ac.priority ?? "should";
8
+ }
9
+ function findBlockingMusts(criteria) {
10
+ return criteria.filter(
11
+ (ac) => criterionPriority(ac) === "must" && ac.last_result?.status !== "pass"
12
+ );
13
+ }
14
+ function criterionStatusLabel(ac) {
15
+ return ac.last_result?.status ?? "unreported";
16
+ }
17
+ var METHOD_LABELS = {
18
+ executable: "executable",
19
+ agent_self_check: "self-check",
20
+ visual_render: "visual",
21
+ founder_confirm: "founder"
22
+ };
23
+ function renderCriterionLines(ac) {
24
+ const checkbox = ac.done ? "x" : " ";
25
+ const isLegacy = ac.priority === void 0 && ac.verification === void 0;
26
+ if (isLegacy) {
27
+ return [`- [${checkbox}] ${ac.text}`];
28
+ }
29
+ const priority = criterionPriority(ac);
30
+ const verification = ac.verification ?? { method: "founder_confirm", check_prompt: ac.text };
31
+ const methodLabel = METHOD_LABELS[verification.method];
32
+ const lines = [`- [${checkbox}] [${priority}][${methodLabel}] ${ac.text}`];
33
+ if (verification.method === "executable") {
34
+ const expectSuffix = verification.expected ? ` \u2192 expect: ${verification.expected}` : "";
35
+ lines.push(` check: ${verification.command ?? ""}${expectSuffix}`);
36
+ } else {
37
+ lines.push(` check: ${verification.check_prompt ?? ac.text}`);
38
+ }
39
+ return lines;
40
+ }
41
+
42
+ // src/lib/story-context.ts
43
+ var CONTEXT_DIR = ".intent/stories";
44
+ function storyContextPath(storyId) {
45
+ return join(process.cwd(), CONTEXT_DIR, `${storyId}.md`);
46
+ }
47
+ function writeStoryContext(story) {
48
+ const dir = join(process.cwd(), CONTEXT_DIR);
49
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
50
+ const acLines = story.acceptance_criteria.length ? story.acceptance_criteria.flatMap((ac) => renderCriterionLines(ac)).join("\n") : "_No acceptance criteria defined._";
51
+ const specSection = story.notes?.trim() ? [`## Spec`, ``, story.notes.trim(), ``] : [];
52
+ const plan = story.implementation_plan;
53
+ const hasPlan = plan?.status === "ready" && plan.markdown;
54
+ const approachSection = hasPlan ? [
55
+ `## Implementation Plan`,
56
+ ``,
57
+ `_Generated by IntentDocs.com${plan.generated_at ? ` on ${plan.generated_at.slice(0, 10)}` : ""}_`,
58
+ ``,
59
+ plan.markdown,
60
+ ``,
61
+ ...plan.files_to_create?.length ? [`### Files to create`, ``, ...plan.files_to_create.map((f) => `- \`${f}\``), ``] : [],
62
+ ...plan.files_to_modify?.length ? [`### Files to modify`, ``, ...plan.files_to_modify.map((f) => `- \`${f}\``), ``] : []
63
+ ] : [
64
+ `## Approach`,
65
+ ``,
66
+ `_Plan your approach here before writing code._`,
67
+ ``
68
+ ];
69
+ const md = [
70
+ `# ${story.title}`,
71
+ ``,
72
+ `**ID:** \`${story.id}\``,
73
+ `**Activity:** ${story.activity}`,
74
+ `**Priority:** ${story.priority} | **Effort:** ${story.effort} | **Release:** ${story.release}`,
75
+ `**Status:** ${story.status}`,
76
+ ``,
77
+ `## User story`,
78
+ ``,
79
+ story.user_story ?? "_No user story defined._",
80
+ ``,
81
+ `## Done when`,
82
+ ``,
83
+ acLines,
84
+ ``,
85
+ ...specSection,
86
+ ...approachSection,
87
+ `## Implementation notes`,
88
+ ``,
89
+ `_Notes added via \`intent note ${story.id.slice(0, 8)} "..."\`_`,
90
+ ``,
91
+ `## Decisions`,
92
+ ``,
93
+ `_Log decisions via \`intent decide "..." -s ${story.id.slice(0, 8)}\`_`,
94
+ ``,
95
+ `## Relevant files`,
96
+ ``,
97
+ `_Add file paths here as you explore the codebase._`,
98
+ ``,
99
+ `---`,
100
+ ``,
101
+ `Mark done: \`intent done ${story.id.slice(0, 8)}\``
102
+ ].join("\n");
103
+ const path = storyContextPath(story.id);
104
+ writeFileSync(path, md);
105
+ return path;
106
+ }
107
+ function appendNoteToContext(storyId, note) {
108
+ const path = storyContextPath(storyId);
109
+ if (!existsSync(path)) return false;
110
+ const content = readFileSync(path, "utf-8");
111
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
112
+ const noteEntry = `- [${timestamp}] ${note}`;
113
+ const marker = "## Implementation notes";
114
+ const markerIdx = content.indexOf(marker);
115
+ if (markerIdx === -1) return false;
116
+ const placeholder = "_Notes added via";
117
+ if (content.includes(placeholder)) {
118
+ writeFileSync(path, content.replace(
119
+ /^_Notes added via.*$/m,
120
+ `${noteEntry}`
121
+ ));
122
+ } else {
123
+ const nextSection = content.indexOf("\n## ", markerIdx + marker.length);
124
+ const insertAt = nextSection === -1 ? content.length : nextSection;
125
+ writeFileSync(
126
+ path,
127
+ content.slice(0, insertAt).trimEnd() + "\n" + noteEntry + "\n" + content.slice(insertAt)
128
+ );
129
+ }
130
+ return true;
131
+ }
132
+
133
+ export {
134
+ criterionPriority,
135
+ findBlockingMusts,
136
+ criterionStatusLabel,
137
+ storyContextPath,
138
+ writeStoryContext,
139
+ appendNoteToContext
140
+ };