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.
- package/README.md +504 -0
- package/bin/sedd.js +6 -0
- package/commands/sedd.clarify.md +435 -0
- package/commands/sedd.dashboard.md +145 -0
- package/commands/sedd.implement.md +326 -0
- package/commands/sedd.migrate.md +249 -0
- package/commands/sedd.specify.md +198 -0
- package/commands/sedd.tasks.md +176 -0
- package/dist/cli/check.d.ts +6 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +134 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/clarify.d.ts +2 -0
- package/dist/cli/clarify.d.ts.map +1 -0
- package/dist/cli/clarify.js +116 -0
- package/dist/cli/clarify.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +175 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +9 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +236 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/migrate.d.ts +7 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +197 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/specify.d.ts +7 -0
- package/dist/cli/specify.d.ts.map +1 -0
- package/dist/cli/specify.js +131 -0
- package/dist/cli/specify.js.map +1 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +118 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/tasks.d.ts +7 -0
- package/dist/cli/tasks.d.ts.map +1 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/changelog.d.ts +30 -0
- package/dist/core/changelog.d.ts.map +1 -0
- package/dist/core/changelog.js +97 -0
- package/dist/core/changelog.js.map +1 -0
- package/dist/core/file-splitter.d.ts +39 -0
- package/dist/core/file-splitter.d.ts.map +1 -0
- package/dist/core/file-splitter.js +162 -0
- package/dist/core/file-splitter.js.map +1 -0
- package/dist/core/migration-manager.d.ts +76 -0
- package/dist/core/migration-manager.d.ts.map +1 -0
- package/dist/core/migration-manager.js +230 -0
- package/dist/core/migration-manager.js.map +1 -0
- package/dist/core/timestamps.d.ts +17 -0
- package/dist/core/timestamps.d.ts.map +1 -0
- package/dist/core/timestamps.js +37 -0
- package/dist/core/timestamps.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +102 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +83 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/git.d.ts +63 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +179 -0
- package/dist/utils/git.js.map +1 -0
- package/hooks/README.md +220 -0
- package/hooks/check-roadmap.js +231 -0
- package/hooks/check-roadmap.ps1 +343 -0
- package/package.json +60 -0
- package/scripts/bash/sedd-clarify.sh +142 -0
- package/scripts/bash/sedd-complete-task.sh +108 -0
- package/scripts/bash/sedd-specify.sh +147 -0
- package/scripts/powershell/sedd-clarify.ps1 +222 -0
- package/scripts/powershell/sedd-complete-task.ps1 +143 -0
- package/scripts/powershell/sedd-specify.ps1 +192 -0
- package/scripts/powershell/sedd-status.ps1 +153 -0
- package/scripts/powershell/sedd-tasks.ps1 +176 -0
- package/templates/changelog-template.md +6 -0
- package/templates/clarify-template.md +66 -0
- package/templates/config-template.json +20 -0
- package/templates/decisions-template.md +56 -0
- package/templates/interfaces-template.ts +131 -0
- package/templates/meta-template.json +12 -0
- package/templates/progress-template.md +61 -0
- package/templates/sedd.schema.json +95 -0
- package/templates/spec-template.md +114 -0
- 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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"}
|
package/hooks/README.md
ADDED
|
@@ -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();
|