sedd 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 (90) hide show
  1. package/README.md +504 -0
  2. package/bin/sedd.js +6 -0
  3. package/commands/sedd.clarify.md +435 -0
  4. package/commands/sedd.dashboard.md +145 -0
  5. package/commands/sedd.implement.md +326 -0
  6. package/commands/sedd.migrate.md +249 -0
  7. package/commands/sedd.specify.md +198 -0
  8. package/commands/sedd.tasks.md +176 -0
  9. package/dist/cli/check.d.ts +6 -0
  10. package/dist/cli/check.d.ts.map +1 -0
  11. package/dist/cli/check.js +134 -0
  12. package/dist/cli/check.js.map +1 -0
  13. package/dist/cli/clarify.d.ts +2 -0
  14. package/dist/cli/clarify.d.ts.map +1 -0
  15. package/dist/cli/clarify.js +116 -0
  16. package/dist/cli/clarify.js.map +1 -0
  17. package/dist/cli/index.d.ts +8 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +175 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/init.d.ts +9 -0
  22. package/dist/cli/init.d.ts.map +1 -0
  23. package/dist/cli/init.js +236 -0
  24. package/dist/cli/init.js.map +1 -0
  25. package/dist/cli/migrate.d.ts +7 -0
  26. package/dist/cli/migrate.d.ts.map +1 -0
  27. package/dist/cli/migrate.js +197 -0
  28. package/dist/cli/migrate.js.map +1 -0
  29. package/dist/cli/specify.d.ts +7 -0
  30. package/dist/cli/specify.d.ts.map +1 -0
  31. package/dist/cli/specify.js +131 -0
  32. package/dist/cli/specify.js.map +1 -0
  33. package/dist/cli/status.d.ts +6 -0
  34. package/dist/cli/status.d.ts.map +1 -0
  35. package/dist/cli/status.js +118 -0
  36. package/dist/cli/status.js.map +1 -0
  37. package/dist/cli/tasks.d.ts +7 -0
  38. package/dist/cli/tasks.d.ts.map +1 -0
  39. package/dist/cli/tasks.js +165 -0
  40. package/dist/cli/tasks.js.map +1 -0
  41. package/dist/core/changelog.d.ts +30 -0
  42. package/dist/core/changelog.d.ts.map +1 -0
  43. package/dist/core/changelog.js +97 -0
  44. package/dist/core/changelog.js.map +1 -0
  45. package/dist/core/file-splitter.d.ts +39 -0
  46. package/dist/core/file-splitter.d.ts.map +1 -0
  47. package/dist/core/file-splitter.js +162 -0
  48. package/dist/core/file-splitter.js.map +1 -0
  49. package/dist/core/migration-manager.d.ts +76 -0
  50. package/dist/core/migration-manager.d.ts.map +1 -0
  51. package/dist/core/migration-manager.js +230 -0
  52. package/dist/core/migration-manager.js.map +1 -0
  53. package/dist/core/timestamps.d.ts +17 -0
  54. package/dist/core/timestamps.d.ts.map +1 -0
  55. package/dist/core/timestamps.js +37 -0
  56. package/dist/core/timestamps.js.map +1 -0
  57. package/dist/index.d.ts +8 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +7 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/types/index.d.ts +102 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/types/index.js +83 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/utils/git.d.ts +63 -0
  66. package/dist/utils/git.d.ts.map +1 -0
  67. package/dist/utils/git.js +179 -0
  68. package/dist/utils/git.js.map +1 -0
  69. package/hooks/README.md +220 -0
  70. package/hooks/check-roadmap.js +231 -0
  71. package/hooks/check-roadmap.ps1 +343 -0
  72. package/package.json +60 -0
  73. package/scripts/bash/sedd-clarify.sh +142 -0
  74. package/scripts/bash/sedd-complete-task.sh +108 -0
  75. package/scripts/bash/sedd-specify.sh +147 -0
  76. package/scripts/powershell/sedd-clarify.ps1 +222 -0
  77. package/scripts/powershell/sedd-complete-task.ps1 +143 -0
  78. package/scripts/powershell/sedd-specify.ps1 +192 -0
  79. package/scripts/powershell/sedd-status.ps1 +153 -0
  80. package/scripts/powershell/sedd-tasks.ps1 +176 -0
  81. package/templates/changelog-template.md +6 -0
  82. package/templates/clarify-template.md +66 -0
  83. package/templates/config-template.json +20 -0
  84. package/templates/decisions-template.md +56 -0
  85. package/templates/interfaces-template.ts +131 -0
  86. package/templates/meta-template.json +12 -0
  87. package/templates/progress-template.md +61 -0
  88. package/templates/sedd.schema.json +95 -0
  89. package/templates/spec-template.md +114 -0
  90. package/templates/tasks-template.md +58 -0
@@ -0,0 +1,179 @@
1
+ import { execSync } from 'node:child_process';
2
+ export class GitOperations {
3
+ cwd;
4
+ constructor(cwd = process.cwd()) {
5
+ this.cwd = cwd;
6
+ }
7
+ /**
8
+ * Check if git is available and we're in a repo
9
+ */
10
+ hasGit() {
11
+ try {
12
+ execSync('git rev-parse --show-toplevel', { cwd: this.cwd, stdio: 'pipe' });
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ /**
20
+ * Get repository root
21
+ */
22
+ getRepoRoot() {
23
+ try {
24
+ return execSync('git rev-parse --show-toplevel', { cwd: this.cwd, encoding: 'utf-8' }).trim();
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ /**
31
+ * Get current branch name
32
+ */
33
+ getCurrentBranch() {
34
+ try {
35
+ return execSync('git rev-parse --abbrev-ref HEAD', { cwd: this.cwd, encoding: 'utf-8' }).trim();
36
+ }
37
+ catch {
38
+ return 'main';
39
+ }
40
+ }
41
+ /**
42
+ * Create and checkout a new branch
43
+ */
44
+ createBranch(branchName) {
45
+ try {
46
+ execSync(`git checkout -b ${branchName}`, { cwd: this.cwd, stdio: 'pipe' });
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ /**
54
+ * Get git status
55
+ */
56
+ getStatus() {
57
+ if (!this.hasGit()) {
58
+ return {
59
+ hasGit: false,
60
+ branch: 'main',
61
+ isClean: true,
62
+ stagedFiles: [],
63
+ unstagedFiles: [],
64
+ };
65
+ }
66
+ const branch = this.getCurrentBranch();
67
+ let stagedFiles = [];
68
+ let unstagedFiles = [];
69
+ try {
70
+ const staged = execSync('git diff --cached --name-only', { cwd: this.cwd, encoding: 'utf-8' });
71
+ stagedFiles = staged.split('\n').filter(Boolean);
72
+ }
73
+ catch { }
74
+ try {
75
+ const unstaged = execSync('git diff --name-only', { cwd: this.cwd, encoding: 'utf-8' });
76
+ unstagedFiles = unstaged.split('\n').filter(Boolean);
77
+ }
78
+ catch { }
79
+ return {
80
+ hasGit: true,
81
+ branch,
82
+ isClean: stagedFiles.length === 0 && unstagedFiles.length === 0,
83
+ stagedFiles,
84
+ unstagedFiles,
85
+ };
86
+ }
87
+ /**
88
+ * Stage files
89
+ */
90
+ stageFiles(files) {
91
+ try {
92
+ execSync(`git add ${files.join(' ')}`, { cwd: this.cwd, stdio: 'pipe' });
93
+ return true;
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
99
+ /**
100
+ * Stage all changes in a directory
101
+ */
102
+ stageDirectory(dir) {
103
+ try {
104
+ execSync(`git add "${dir}"`, { cwd: this.cwd, stdio: 'pipe' });
105
+ return true;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ /**
112
+ * Create a commit
113
+ */
114
+ commit(message) {
115
+ try {
116
+ execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: this.cwd, stdio: 'pipe' });
117
+ const hash = execSync('git rev-parse --short HEAD', { cwd: this.cwd, encoding: 'utf-8' }).trim();
118
+ return hash;
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Get highest feature number from branches
126
+ */
127
+ getHighestBranchNumber() {
128
+ try {
129
+ const branches = execSync('git branch -a', { cwd: this.cwd, encoding: 'utf-8' });
130
+ let highest = 0;
131
+ for (const line of branches.split('\n')) {
132
+ const clean = line.trim().replace(/^\*?\s+/, '').replace(/^remotes\/[^/]+\//, '');
133
+ const match = clean.match(/^(\d+)-/);
134
+ if (match) {
135
+ const num = parseInt(match[1]);
136
+ if (num > highest) {
137
+ highest = num;
138
+ }
139
+ }
140
+ }
141
+ return highest;
142
+ }
143
+ catch {
144
+ return 0;
145
+ }
146
+ }
147
+ /**
148
+ * Fetch all remotes
149
+ */
150
+ fetchAll() {
151
+ try {
152
+ execSync('git fetch --all --prune', { cwd: this.cwd, stdio: 'pipe' });
153
+ return true;
154
+ }
155
+ catch {
156
+ return false;
157
+ }
158
+ }
159
+ /**
160
+ * Check if branch name is valid feature branch (###-name)
161
+ */
162
+ isFeatureBranch(branch) {
163
+ return /^\d{3}-/.test(branch);
164
+ }
165
+ /**
166
+ * Parse feature info from branch name
167
+ */
168
+ parseFeatureBranch(branch) {
169
+ const match = branch.match(/^(\d{3})-(.+)$/);
170
+ if (!match) {
171
+ return null;
172
+ }
173
+ return {
174
+ id: match[1],
175
+ name: match[2],
176
+ };
177
+ }
178
+ }
179
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,220 @@
1
+ # SEDD Hooks
2
+
3
+ Assertive hooks for SEDD (Spec & Expectation Driven Development).
4
+
5
+ ## Configuration
6
+
7
+ Hooks read from `sedd.config.json` at project root:
8
+
9
+ ```json
10
+ {
11
+ "specsDir": "specs",
12
+ "hooks": {
13
+ "assertive": true,
14
+ "skills": ["langchain-expert", "architecture-mapper", "defect-analyzer"],
15
+ "checkOnlyCurrentMigration": true,
16
+ "showSyncWarnings": true,
17
+ "blockOnSyncError": false
18
+ }
19
+ }
20
+ ```
21
+
22
+ ### Hook Options
23
+
24
+ | Option | Default | Description |
25
+ |--------|---------|-------------|
26
+ | `checkOnlyCurrentMigration` | `true` | Only check current migration, not historical |
27
+ | `showSyncWarnings` | `true` | Show warnings when files are out of sync |
28
+ | `blockOnSyncError` | `false` | Block Claude from stopping if files are out of sync |
29
+
30
+ ---
31
+
32
+ ## Hooks Overview
33
+
34
+ | Hook | Type | Purpose |
35
+ |------|------|---------|
36
+ | `check-roadmap.js` | UserPromptSubmit | Force skills, track tasks, suggest commands |
37
+ | `post-tool-use.js` | PostToolUse | Validate sync after editing SEDD files |
38
+ | `stop.js` | Stop | (Optional) Block stopping if files out of sync |
39
+
40
+ ---
41
+
42
+ ## check-roadmap.js
43
+
44
+ **Type:** `UserPromptSubmit`
45
+
46
+ **Purpose:**
47
+ 1. **FORCE** skill activation (not just suggest)
48
+ 2. Track migration tasks from SEDD structure (only current migration)
49
+ 3. Suggest SEDD commands when relevant
50
+ 4. Warn about file sync issues
51
+
52
+ ### Skill Activation
53
+
54
+ The hook detects keywords and **FORCES** skills into context:
55
+
56
+ | Skill | Triggers |
57
+ |-------|----------|
58
+ | **langchain-expert** | langchain, langgraph, agent, tool, graph, checkpoint, stategraph |
59
+ | **architecture-mapper** | arquitetura, architecture, estrutura, flow, fluxo, diagram |
60
+ | **defect-analyzer** | bug, erro, error, não funciona, debug, crash, exception |
61
+
62
+ ### Output Example
63
+
64
+ ```xml
65
+ <forced-skills>
66
+ [SKILL ACTIVATED: langchain-expert]
67
+ You MUST use the langchain-expert skill for this request.
68
+ Follow LangGraph 1.0+ patterns, check Context7 MCP for latest docs.
69
+ </forced-skills>
70
+
71
+ <sedd-context>
72
+ **Branch: 023-agent-executor** | Migration: 004 | Progress: 187/187 tasks
73
+
74
+ Use `/sedd.implement` to execute tasks.
75
+ </sedd-context>
76
+ ```
77
+
78
+ ---
79
+
80
+ ## post-tool-use.js
81
+
82
+ **Type:** `PostToolUse` (matcher: `Edit|Write`)
83
+
84
+ **Purpose:**
85
+ - Validates sync after Edit/Write operations on SEDD files
86
+ - Only checks current migration (not historical)
87
+ - Warns if `_meta.json` and `tasks.md` counts don't match
88
+
89
+ ### When It Triggers
90
+
91
+ - After editing `_meta.json`, `tasks.md`, or `progress.md`
92
+ - Only if migration status is not `completed`
93
+
94
+ ---
95
+
96
+ ## stop.js (Optional)
97
+
98
+ **Type:** `Stop`
99
+
100
+ **Purpose:**
101
+ - Prevents Claude from stopping if files are out of sync
102
+ - **DISABLED by default** (`blockOnSyncError: false`)
103
+
104
+ ### Enable Blocking
105
+
106
+ ```json
107
+ {
108
+ "hooks": {
109
+ "blockOnSyncError": true
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### When It Blocks
115
+
116
+ - Current migration status is `in-progress`
117
+ - `tasks.md` has 0 pending tasks (all marked `[x]`)
118
+ - But `_meta.json` still shows `in-progress`
119
+
120
+ ---
121
+
122
+ ## Claude Settings Configuration
123
+
124
+ In `.claude/settings.json`:
125
+
126
+ ```json
127
+ {
128
+ "hooks": {
129
+ "UserPromptSubmit": [
130
+ {
131
+ "matcher": "",
132
+ "hooks": [
133
+ {
134
+ "type": "command",
135
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/check-roadmap.js\"",
136
+ "timeout": 10
137
+ }
138
+ ]
139
+ }
140
+ ],
141
+ "PostToolUse": [
142
+ {
143
+ "matcher": "Edit|Write",
144
+ "hooks": [
145
+ {
146
+ "type": "command",
147
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use.js\"",
148
+ "timeout": 5
149
+ }
150
+ ]
151
+ }
152
+ ],
153
+ "Stop": [
154
+ {
155
+ "matcher": "",
156
+ "hooks": [
157
+ {
158
+ "type": "command",
159
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop.js\"",
160
+ "timeout": 5
161
+ }
162
+ ]
163
+ }
164
+ ]
165
+ }
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## SEDD Structure Support
172
+
173
+ The hooks understand this structure:
174
+
175
+ ```
176
+ specs/023-feature/
177
+ ├── _meta.json ← Hook reads migration status
178
+ ├── spec.md
179
+ ├── interfaces.ts
180
+ ├── 001_timestamp/
181
+ │ └── tasks.md ← Hook reads tasks (if migration 001)
182
+ ├── 002_timestamp/
183
+ │ └── tasks.md ← Hook reads tasks (if migration 002)
184
+ ├── 003_timestamp/
185
+ │ └── tasks.md ← Hook reads tasks (if current migration)
186
+ └── progress.md
187
+ ```
188
+
189
+ ### Key Behavior
190
+
191
+ - **Only checks current migration** (set in `_meta.json.currentMigration`)
192
+ - **Skips completed migrations** (no warnings for historical tasks)
193
+ - **Uses `_meta.json` as source of truth** for completion counts
194
+
195
+ ---
196
+
197
+ ## Prompts Ignored
198
+
199
+ The hook skips:
200
+ - Slash commands (`/commit`, `/help`, `/sedd.*`)
201
+ - Greetings (`oi`, `hello`, `hey`)
202
+ - Confirmations (`sim`, `ok`, `yes`, `no`)
203
+ - Answers (`Q1: A`, `B`, `C`)
204
+ - `continue`, `prossiga`
205
+
206
+ ---
207
+
208
+ ## Disable Hooks
209
+
210
+ ```bash
211
+ # Temporarily disable all hooks
212
+ mv .claude/settings.json .claude/settings.json.disabled
213
+
214
+ # Or disable sync warnings only (in sedd.config.json)
215
+ {
216
+ "hooks": {
217
+ "showSyncWarnings": false
218
+ }
219
+ }
220
+ ```
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SEDD Hook - UserPromptSubmit
4
+ * Simple task count display for feature branches
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { execSync } = require('child_process');
10
+
11
+ const DEFAULT_CONFIG = {
12
+ specsDir: '.sedd',
13
+ };
14
+
15
+ const IGNORE_PATTERNS = [
16
+ /^\/\w+/,
17
+ /^\s*(oi|hi|hello|hey)\s*$/i,
18
+ /^\s*(obrigado|thanks?|thx)\s*$/i,
19
+ /^(sim|yes|no|não|ok|okay)\s*$/i,
20
+ /^\s*\?\s*$/,
21
+ /^q\d+:/i,
22
+ /^(a|b|c|d|e)\s*$/i,
23
+ /^continue\s*$/i,
24
+ /^prossiga\s*$/i,
25
+ ];
26
+
27
+ /** @param {string} cwd */
28
+ const loadConfig = (cwd) => {
29
+ const configPath = path.join(cwd, 'sedd.config.json');
30
+ if (!fs.existsSync(configPath)) return DEFAULT_CONFIG;
31
+
32
+ try {
33
+ const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
34
+ return { ...DEFAULT_CONFIG, ...userConfig };
35
+ } catch {
36
+ return DEFAULT_CONFIG;
37
+ }
38
+ };
39
+
40
+ /** @param {string} cwd */
41
+ const getCurrentBranch = (cwd) => {
42
+ try {
43
+ return execSync('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf8' }).trim();
44
+ } catch {
45
+ return '';
46
+ }
47
+ };
48
+
49
+ /** @param {string} branch */
50
+ const isFeatureBranch = (branch) => /^\d{3}-/.test(branch);
51
+
52
+ /** @param {string} prompt */
53
+ const shouldIgnorePrompt = (prompt) => IGNORE_PATTERNS.some((p) => p.test(prompt));
54
+
55
+ /**
56
+ * @param {string} content
57
+ * @param {string|null} migrationId
58
+ */
59
+ const parseTasksFromContent = (content, migrationId) => {
60
+ const pending = [];
61
+ let completed = 0;
62
+
63
+ for (const line of content.split('\n')) {
64
+ const pendingMatch = line.match(/^\s*-\s*\[\s*\]\s*(T\d{3}-\d{3})\s+(.+)/);
65
+ if (pendingMatch) {
66
+ pending.push({
67
+ id: pendingMatch[1],
68
+ migration: migrationId,
69
+ text: pendingMatch[2].replace(/`[^`]+`/g, '').trim(),
70
+ });
71
+ continue;
72
+ }
73
+
74
+ if (/^\s*-\s*\[x\]\s*T\d{3}-\d{3}/i.test(line)) {
75
+ completed++;
76
+ }
77
+ }
78
+
79
+ return { pending, completed };
80
+ };
81
+
82
+ /**
83
+ * @param {string} featureDir
84
+ * @param {object} metaData
85
+ */
86
+ const parseTasksFromMigrations = (featureDir, metaData) => {
87
+ const currentMigration = metaData.currentMigration;
88
+
89
+ if (currentMigration) {
90
+ const migInfo = metaData.migrations?.[currentMigration];
91
+ if (migInfo?.status === 'completed') {
92
+ let totalCompleted = 0;
93
+ for (const m of Object.values(metaData.migrations || {})) {
94
+ totalCompleted += m.tasksCompleted || 0;
95
+ }
96
+ return { pending: [], completed: totalCompleted };
97
+ }
98
+
99
+ const tasksFile = path.join(featureDir, migInfo?.folder || '', 'tasks.md');
100
+ if (fs.existsSync(tasksFile)) {
101
+ const content = fs.readFileSync(tasksFile, 'utf8');
102
+ const { pending, completed } = parseTasksFromContent(content, currentMigration);
103
+
104
+ let prevCompleted = 0;
105
+ for (const [migId, m] of Object.entries(metaData.migrations || {})) {
106
+ if (migId !== currentMigration && m.status === 'completed') {
107
+ prevCompleted += m.tasksCompleted || 0;
108
+ }
109
+ }
110
+
111
+ return { pending, completed: completed + prevCompleted };
112
+ }
113
+ }
114
+
115
+ const allPending = [];
116
+ let totalCompleted = 0;
117
+
118
+ for (const [migId, migInfo] of Object.entries(metaData.migrations || {})) {
119
+ const tasksFile = path.join(featureDir, migInfo.folder, 'tasks.md');
120
+ if (!fs.existsSync(tasksFile)) continue;
121
+
122
+ const content = fs.readFileSync(tasksFile, 'utf8');
123
+ const { pending, completed } = parseTasksFromContent(content, migId);
124
+ allPending.push(...pending);
125
+ totalCompleted += completed;
126
+ }
127
+
128
+ return { pending: allPending, completed: totalCompleted };
129
+ };
130
+
131
+ /** @param {string} featureDir */
132
+ const parseTasksFromLegacy = (featureDir) => {
133
+ const tasksFile = path.join(featureDir, 'tasks.md');
134
+ if (!fs.existsSync(tasksFile)) return { pending: [], completed: 0 };
135
+
136
+ const content = fs.readFileSync(tasksFile, 'utf8');
137
+ return parseTasksFromContent(content, null);
138
+ };
139
+
140
+ /**
141
+ * @param {string} cwd
142
+ * @param {string} specsDir
143
+ * @param {string} branch
144
+ */
145
+ const findFeatureDir = (cwd, specsDir, branch) => {
146
+ const primaryDir = path.join(cwd, specsDir, branch);
147
+ if (fs.existsSync(primaryDir)) return primaryDir;
148
+
149
+ const legacyDir = path.join(cwd, 'specs', branch);
150
+ if (fs.existsSync(legacyDir)) return legacyDir;
151
+
152
+ return null;
153
+ };
154
+
155
+ /**
156
+ * @param {string} branch
157
+ * @param {string|null} currentMigration
158
+ * @param {number} completed
159
+ * @param {Array} pending
160
+ */
161
+ const buildSeddContext = (branch, currentMigration, completed, pending) => {
162
+ if (pending.length === 0) return null;
163
+
164
+ const total = completed + pending.length;
165
+ const migrationInfo = currentMigration ? ` | Migration: ${currentMigration}` : '';
166
+
167
+ const tasksList = pending.slice(0, 5).map((t) => {
168
+ const truncated = t.text.length > 60 ? `${t.text.substring(0, 60)}...` : t.text;
169
+ return `- ${t.id}: ${truncated}`;
170
+ }).join('\n');
171
+
172
+ const moreText = pending.length > 5 ? `\n... and ${pending.length - 5} more` : '';
173
+
174
+ return `<sedd-context>
175
+ **Branch: ${branch}**${migrationInfo} | Progress: ${completed}/${total} tasks
176
+
177
+ Pending tasks:
178
+ ${tasksList}${moreText}
179
+ </sedd-context>`;
180
+ };
181
+
182
+ const main = () => {
183
+ let inputData = '';
184
+
185
+ process.stdin.setEncoding('utf8');
186
+ process.stdin.on('data', (chunk) => { inputData += chunk; });
187
+ process.stdin.on('end', () => {
188
+ try {
189
+ const data = JSON.parse(inputData);
190
+ const prompt = data.prompt || '';
191
+ const cwd = data.cwd || process.cwd();
192
+
193
+ if (!prompt || shouldIgnorePrompt(prompt)) return;
194
+
195
+ const config = loadConfig(cwd);
196
+ const specsDir = config.specsDir || '.sedd';
197
+
198
+ const branch = getCurrentBranch(cwd);
199
+ if (!isFeatureBranch(branch)) return;
200
+
201
+ const featureDir = findFeatureDir(cwd, specsDir, branch);
202
+ if (!featureDir) return;
203
+
204
+ const metaFile = path.join(featureDir, '_meta.json');
205
+ let pending = [];
206
+ let completed = 0;
207
+ let currentMigration = null;
208
+
209
+ if (fs.existsSync(metaFile)) {
210
+ const metaData = JSON.parse(fs.readFileSync(metaFile, 'utf8'));
211
+ currentMigration = metaData.currentMigration;
212
+ const result = parseTasksFromMigrations(featureDir, metaData);
213
+ pending = result.pending;
214
+ completed = result.completed;
215
+ } else {
216
+ const result = parseTasksFromLegacy(featureDir);
217
+ pending = result.pending;
218
+ completed = result.completed;
219
+ }
220
+
221
+ const context = buildSeddContext(branch, currentMigration, completed, pending);
222
+ if (context) {
223
+ console.log(JSON.stringify({ systemMessage: '\n' + context + '\n' }));
224
+ }
225
+ } catch {
226
+ // Silent exit on parse errors
227
+ }
228
+ });
229
+ };
230
+
231
+ main();