@os-eco/seeds-cli 0.2.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 +226 -0
- package/package.json +42 -0
- package/src/commands/blocked.ts +31 -0
- package/src/commands/close.ts +80 -0
- package/src/commands/create.test.ts +507 -0
- package/src/commands/create.ts +103 -0
- package/src/commands/dep.test.ts +215 -0
- package/src/commands/dep.ts +108 -0
- package/src/commands/doctor.test.ts +524 -0
- package/src/commands/doctor.ts +732 -0
- package/src/commands/init.test.ts +86 -0
- package/src/commands/init.ts +49 -0
- package/src/commands/list.ts +69 -0
- package/src/commands/migrate.ts +117 -0
- package/src/commands/onboard.test.ts +155 -0
- package/src/commands/onboard.ts +140 -0
- package/src/commands/prime.test.ts +94 -0
- package/src/commands/prime.ts +146 -0
- package/src/commands/ready.ts +31 -0
- package/src/commands/show.ts +20 -0
- package/src/commands/stats.ts +58 -0
- package/src/commands/sync.ts +76 -0
- package/src/commands/tpl.test.ts +330 -0
- package/src/commands/tpl.ts +279 -0
- package/src/commands/update.ts +97 -0
- package/src/config.ts +39 -0
- package/src/id.test.ts +55 -0
- package/src/id.ts +22 -0
- package/src/index.ts +122 -0
- package/src/markers.test.ts +73 -0
- package/src/markers.ts +19 -0
- package/src/output.ts +63 -0
- package/src/store.test.ts +173 -0
- package/src/store.ts +143 -0
- package/src/types.ts +61 -0
- package/src/yaml.test.ts +79 -0
- package/src/yaml.ts +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Seeds
|
|
2
|
+
|
|
3
|
+
Git-native issue tracker for AI agent workflows. Zero dependencies, JSONL storage, Bun runtime.
|
|
4
|
+
|
|
5
|
+
Replaces [beads](https://github.com/steveyegge/beads) in the [overstory](https://github.com/jayminwest/overstory)/[mulch](https://github.com/jayminwest/mulch) ecosystem. No Dolt, no daemon, no binary DB files. **The JSONL file IS the database.**
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Beads works but carries baggage overstory doesn't need:
|
|
10
|
+
|
|
11
|
+
| Problem | Beads | Seeds |
|
|
12
|
+
|---------|-------|-------|
|
|
13
|
+
| Storage | 2.8MB binary `beads.db` (can't diff/merge) | JSONL (diffable, mergeable) |
|
|
14
|
+
| Sync | 286 export-state tracking files | No sync — file IS the DB |
|
|
15
|
+
| Concurrency | `beads.db` lock contention | Advisory locks + atomic writes |
|
|
16
|
+
| Dependencies | Dolt embedded | Zero runtime deps |
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/jayminwest/seeds
|
|
22
|
+
cd seeds
|
|
23
|
+
bun install
|
|
24
|
+
bun link # Makes 'sd' available globally
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires [Bun](https://bun.sh) v1.0+.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Initialize in your project
|
|
33
|
+
sd init
|
|
34
|
+
|
|
35
|
+
# Create an issue
|
|
36
|
+
sd create --title "Add retry logic to mail client" --type task --priority 1
|
|
37
|
+
|
|
38
|
+
# List open issues
|
|
39
|
+
sd list
|
|
40
|
+
|
|
41
|
+
# Find work (open, unblocked)
|
|
42
|
+
sd ready
|
|
43
|
+
|
|
44
|
+
# Claim and complete
|
|
45
|
+
sd update seeds-a1b2 --status in_progress
|
|
46
|
+
sd close seeds-a1b2 --reason "Implemented with exponential backoff"
|
|
47
|
+
|
|
48
|
+
# Commit .seeds/ changes to git
|
|
49
|
+
sd sync
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## CLI Reference
|
|
53
|
+
|
|
54
|
+
Every command supports `--json` for structured output. ANSI colors respect `NO_COLOR`.
|
|
55
|
+
|
|
56
|
+
### Issue Commands
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
sd init Initialize .seeds/ in current directory
|
|
60
|
+
|
|
61
|
+
sd create Create a new issue
|
|
62
|
+
--title <text> (required)
|
|
63
|
+
--type <type> task|bug|feature|epic (default: task)
|
|
64
|
+
--priority <n> 0-4 or P0-P4 (default: 2)
|
|
65
|
+
--description <text>
|
|
66
|
+
--assignee <name>
|
|
67
|
+
|
|
68
|
+
sd show <id> Show issue details
|
|
69
|
+
|
|
70
|
+
sd list List issues with filters
|
|
71
|
+
--status <status> open|in_progress|closed
|
|
72
|
+
--type <type> task|bug|feature|epic
|
|
73
|
+
--assignee <name>
|
|
74
|
+
--limit <n> Max results (default: 50)
|
|
75
|
+
|
|
76
|
+
sd ready Open issues with no unresolved blockers
|
|
77
|
+
|
|
78
|
+
sd update <id> Update issue fields
|
|
79
|
+
--status --title --priority --assignee --description
|
|
80
|
+
|
|
81
|
+
sd close <id> [<id2> ...] Close one or more issues
|
|
82
|
+
--reason <text> Closure summary
|
|
83
|
+
|
|
84
|
+
sd dep add <issue> <depends-on> Add dependency
|
|
85
|
+
sd dep remove <issue> <depends-on> Remove dependency
|
|
86
|
+
sd dep list <issue> Show deps for an issue
|
|
87
|
+
|
|
88
|
+
sd blocked Show all blocked issues
|
|
89
|
+
|
|
90
|
+
sd stats Project statistics
|
|
91
|
+
|
|
92
|
+
sd sync Stage and commit .seeds/ changes
|
|
93
|
+
--status Check without committing
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Template (Molecule) Commands
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
sd tpl create --name <text> Create a template
|
|
100
|
+
sd tpl step add <id> --title <text> Add step (supports {prefix} interpolation)
|
|
101
|
+
sd tpl list List all templates
|
|
102
|
+
sd tpl show <id> Show template with steps
|
|
103
|
+
sd tpl pour <id> --prefix <text> Instantiate template into issues
|
|
104
|
+
sd tpl status <id> Show convoy completion status
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Project Health
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
sd doctor Check project health and data integrity
|
|
111
|
+
--fix Fix auto-fixable issues
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Agent Integration
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
sd prime Output AI agent context (PRIME.md or built-in)
|
|
118
|
+
--compact Condensed quick-reference output
|
|
119
|
+
sd onboard Add seeds section to CLAUDE.md / AGENTS.md
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Migration
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
sd migrate-from-beads # Import .beads/issues.jsonl → .seeds/issues.jsonl
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Priority Scale
|
|
129
|
+
|
|
130
|
+
| Value | Label | Use |
|
|
131
|
+
|-------|----------|-----|
|
|
132
|
+
| 0 | Critical | System-breaking, drop everything |
|
|
133
|
+
| 1 | High | Core functionality |
|
|
134
|
+
| 2 | Medium | Default — important but not urgent |
|
|
135
|
+
| 3 | Low | Nice-to-have |
|
|
136
|
+
| 4 | Backlog | Future consideration |
|
|
137
|
+
|
|
138
|
+
## On-Disk Format
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
.seeds/
|
|
142
|
+
config.yaml # Project config: project name, version
|
|
143
|
+
issues.jsonl # All issues, one JSON object per line
|
|
144
|
+
templates.jsonl # Template definitions
|
|
145
|
+
.gitignore # Ignores *.lock files
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Add to your `.gitattributes` (done automatically by `sd init`):
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
.seeds/issues.jsonl merge=union
|
|
152
|
+
.seeds/templates.jsonl merge=union
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The `merge=union` strategy handles parallel agent branch merges. Seeds deduplicates by ID on read (last occurrence wins), so conflicts resolve automatically.
|
|
156
|
+
|
|
157
|
+
## JSON Output
|
|
158
|
+
|
|
159
|
+
Success:
|
|
160
|
+
```json
|
|
161
|
+
{ "success": true, "command": "create", "id": "myproject-a1b2" }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Error:
|
|
165
|
+
```json
|
|
166
|
+
{ "success": false, "command": "create", "error": "Title is required" }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Concurrency
|
|
170
|
+
|
|
171
|
+
Seeds is safe for concurrent multi-agent use:
|
|
172
|
+
|
|
173
|
+
- **Advisory file locks** — `O_CREAT | O_EXCL`, 30s stale threshold, 50ms retry, 5s timeout
|
|
174
|
+
- **Atomic writes** — temp file + rename under lock
|
|
175
|
+
- **Dedup on read** — last occurrence wins after `merge=union` git merges
|
|
176
|
+
|
|
177
|
+
## Integration with Overstory
|
|
178
|
+
|
|
179
|
+
Overstory wraps `sd` via `Bun.spawn(["sd", ...])` with `--json` parsing, identical to how it wraps `bd`:
|
|
180
|
+
|
|
181
|
+
| BeadsClient method | sd command |
|
|
182
|
+
|--------------------|------------|
|
|
183
|
+
| `ready()` | `sd ready --json` |
|
|
184
|
+
| `show(id)` | `sd show <id> --json` |
|
|
185
|
+
| `create(title, opts)` | `sd create --title "..." --json` |
|
|
186
|
+
| `claim(id)` | `sd update <id> --status=in_progress --json` |
|
|
187
|
+
| `close(id, reason)` | `sd close <id> --reason "..." --json` |
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
bun test # Run all tests
|
|
193
|
+
bun test src/store.test.ts # Single test file
|
|
194
|
+
bun run lint # Biome check
|
|
195
|
+
bun run typecheck # tsc --noEmit
|
|
196
|
+
|
|
197
|
+
# Quality gates (run before committing)
|
|
198
|
+
bun test && bun run lint && bun run typecheck
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Version Bump
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
bun run version:bump patch # 0.1.0 → 0.1.1
|
|
205
|
+
bun run version:bump minor # 0.1.0 → 0.2.0
|
|
206
|
+
bun run version:bump major # 0.1.0 → 1.0.0
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Updates both `package.json` and `src/index.ts` atomically.
|
|
210
|
+
|
|
211
|
+
## Tech Stack
|
|
212
|
+
|
|
213
|
+
| Concern | Choice |
|
|
214
|
+
|---------|--------|
|
|
215
|
+
| Runtime | Bun (runs TS directly, no build) |
|
|
216
|
+
| Language | TypeScript strict (`noUncheckedIndexedAccess`, no `any`) |
|
|
217
|
+
| Storage | JSONL (git-native) |
|
|
218
|
+
| Config | YAML (minimal built-in parser) |
|
|
219
|
+
| Locking | Advisory file locks |
|
|
220
|
+
| Formatting | Biome (tabs, 100 char width) |
|
|
221
|
+
| Testing | `bun test` with real I/O, temp dirs |
|
|
222
|
+
| Dependencies | Zero runtime |
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@os-eco/seeds-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Git-native issue tracker for AI agent workflows",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sd": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.ts",
|
|
10
|
+
"files": ["src"],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"bun": ">=1.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"issue-tracking",
|
|
19
|
+
"git",
|
|
20
|
+
"ai",
|
|
21
|
+
"agents",
|
|
22
|
+
"cli",
|
|
23
|
+
"developer-tools"
|
|
24
|
+
],
|
|
25
|
+
"author": "Jaymin West",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/jayminwest/seeds.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "bun test",
|
|
33
|
+
"lint": "bunx biome check .",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"version:bump": "bun run scripts/version-bump.ts"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@biomejs/biome": "^1.9.0",
|
|
39
|
+
"@types/bun": "latest",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { findSeedsDir } from "../config.ts";
|
|
2
|
+
import { outputJson, printIssueOneLine } from "../output.ts";
|
|
3
|
+
import { readIssues } from "../store.ts";
|
|
4
|
+
import type { Issue } from "../types.ts";
|
|
5
|
+
|
|
6
|
+
export async function run(args: string[], seedsDir?: string): Promise<void> {
|
|
7
|
+
const jsonMode = args.includes("--json");
|
|
8
|
+
const dir = seedsDir ?? (await findSeedsDir());
|
|
9
|
+
const issues = await readIssues(dir);
|
|
10
|
+
|
|
11
|
+
const closedIds = new Set(issues.filter((i: Issue) => i.status === "closed").map((i) => i.id));
|
|
12
|
+
|
|
13
|
+
const blocked = issues.filter((i: Issue) => {
|
|
14
|
+
if (i.status === "closed") return false;
|
|
15
|
+
const blockers = i.blockedBy ?? [];
|
|
16
|
+
return blockers.some((bid) => !closedIds.has(bid));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (jsonMode) {
|
|
20
|
+
outputJson({ success: true, command: "blocked", issues: blocked, count: blocked.length });
|
|
21
|
+
} else {
|
|
22
|
+
if (blocked.length === 0) {
|
|
23
|
+
console.log("No blocked issues.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const issue of blocked) {
|
|
27
|
+
printIssueOneLine(issue);
|
|
28
|
+
}
|
|
29
|
+
console.log(`\n${blocked.length} blocked issue(s)`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { findSeedsDir } from "../config.ts";
|
|
2
|
+
import { outputJson, printSuccess } from "../output.ts";
|
|
3
|
+
import { issuesPath, readIssues, withLock, writeIssues } from "../store.ts";
|
|
4
|
+
import type { Issue } from "../types.ts";
|
|
5
|
+
|
|
6
|
+
function parseArgs(args: string[]) {
|
|
7
|
+
const flags: Record<string, string | boolean> = {};
|
|
8
|
+
const positional: string[] = [];
|
|
9
|
+
let i = 0;
|
|
10
|
+
while (i < args.length) {
|
|
11
|
+
const arg = args[i];
|
|
12
|
+
if (!arg) {
|
|
13
|
+
i++;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (arg.startsWith("--")) {
|
|
17
|
+
const key = arg.slice(2);
|
|
18
|
+
const eqIdx = key.indexOf("=");
|
|
19
|
+
if (eqIdx !== -1) {
|
|
20
|
+
flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
|
|
21
|
+
i++;
|
|
22
|
+
} else {
|
|
23
|
+
const next = args[i + 1];
|
|
24
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
25
|
+
flags[key] = next;
|
|
26
|
+
i += 2;
|
|
27
|
+
} else {
|
|
28
|
+
flags[key] = true;
|
|
29
|
+
i++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
positional.push(arg);
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { flags, positional };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function run(args: string[], seedsDir?: string): Promise<void> {
|
|
41
|
+
const jsonMode = args.includes("--json");
|
|
42
|
+
const { flags, positional } = parseArgs(args);
|
|
43
|
+
|
|
44
|
+
if (positional.length === 0) throw new Error("Usage: sd close <id> [ids...] [--reason text]");
|
|
45
|
+
|
|
46
|
+
const reason = typeof flags.reason === "string" ? flags.reason : undefined;
|
|
47
|
+
const ids = positional;
|
|
48
|
+
|
|
49
|
+
const dir = seedsDir ?? (await findSeedsDir());
|
|
50
|
+
const closed: string[] = [];
|
|
51
|
+
|
|
52
|
+
await withLock(issuesPath(dir), async () => {
|
|
53
|
+
const issues = await readIssues(dir);
|
|
54
|
+
const now = new Date().toISOString();
|
|
55
|
+
|
|
56
|
+
for (const id of ids) {
|
|
57
|
+
const idx = issues.findIndex((i) => i.id === id);
|
|
58
|
+
if (idx === -1) throw new Error(`Issue not found: ${id}`);
|
|
59
|
+
const issue = issues[idx]!;
|
|
60
|
+
const updated: Issue = {
|
|
61
|
+
...issue,
|
|
62
|
+
status: "closed",
|
|
63
|
+
closedAt: now,
|
|
64
|
+
updatedAt: now,
|
|
65
|
+
...(reason ? { closeReason: reason } : {}),
|
|
66
|
+
};
|
|
67
|
+
issues[idx] = updated;
|
|
68
|
+
closed.push(id);
|
|
69
|
+
}
|
|
70
|
+
await writeIssues(dir, issues);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (jsonMode) {
|
|
74
|
+
outputJson({ success: true, command: "close", closed });
|
|
75
|
+
} else {
|
|
76
|
+
for (const id of closed) {
|
|
77
|
+
printSuccess(`Closed ${id}${reason ? ` — ${reason}` : ""}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|