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