trekoon 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/.agents/skills/trekoon/SKILL.md +91 -0
- package/AGENTS.md +54 -0
- package/CONTRIBUTING.md +18 -0
- package/README.md +151 -0
- package/bin/trekoon +5 -0
- package/bun.lock +28 -0
- package/package.json +24 -0
- package/src/commands/arg-parser.ts +93 -0
- package/src/commands/dep.ts +105 -0
- package/src/commands/epic.ts +539 -0
- package/src/commands/help.ts +61 -0
- package/src/commands/init.ts +24 -0
- package/src/commands/quickstart.ts +61 -0
- package/src/commands/subtask.ts +187 -0
- package/src/commands/sync.ts +128 -0
- package/src/commands/task.ts +554 -0
- package/src/commands/wipe.ts +39 -0
- package/src/domain/tracker-domain.ts +576 -0
- package/src/domain/types.ts +99 -0
- package/src/index.ts +21 -0
- package/src/io/human-table.ts +191 -0
- package/src/io/output.ts +70 -0
- package/src/runtime/cli-shell.ts +158 -0
- package/src/runtime/command-types.ts +33 -0
- package/src/storage/database.ts +35 -0
- package/src/storage/migrations.ts +46 -0
- package/src/storage/path.ts +22 -0
- package/src/storage/schema.ts +116 -0
- package/src/storage/types.ts +15 -0
- package/src/sync/branch-db.ts +49 -0
- package/src/sync/event-writes.ts +49 -0
- package/src/sync/git-context.ts +67 -0
- package/src/sync/service.ts +654 -0
- package/src/sync/types.ts +31 -0
- package/tests/commands/dep.test.ts +101 -0
- package/tests/commands/epic.test.ts +383 -0
- package/tests/commands/subtask.test.ts +132 -0
- package/tests/commands/sync/sync-command.test.ts +1 -0
- package/tests/commands/sync.test.ts +199 -0
- package/tests/commands/task.test.ts +474 -0
- package/tests/integration/sync-workflow.test.ts +279 -0
- package/tests/io/human-table.test.ts +81 -0
- package/tests/runtime/output-mode.test.ts +54 -0
- package/tests/storage/database.test.ts +91 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trekoon
|
|
3
|
+
description: Use Trekoon to create issues/tasks, plan backlog and sprints, create epics, update status, track progress, and manage dependencies/sync across repository workflows.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Trekoon Skill
|
|
7
|
+
|
|
8
|
+
Trekoon is a local-first issue tracker for epics, tasks, and subtasks.
|
|
9
|
+
|
|
10
|
+
Use long flags (`--status`, `--description`, etc.) and ALWAYS prefer `--toon` for machine-readable output.
|
|
11
|
+
|
|
12
|
+
## 1) Load existing work first
|
|
13
|
+
|
|
14
|
+
Before creating or changing anything, inspect current context:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
trekoon epic list --toon
|
|
18
|
+
trekoon task list --toon
|
|
19
|
+
trekoon epic show <id> --all --toon
|
|
20
|
+
trekoon task show <id> --all --toon
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `epic list` / `task list` defaults:
|
|
24
|
+
- open work only (`in_progress`, `in-progress`, `todo`)
|
|
25
|
+
- prioritized as `in_progress`/`in-progress` first, then `todo`
|
|
26
|
+
- default limit `10`
|
|
27
|
+
- Filter list explicitly when needed:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
trekoon task list --status in_progress,todo --limit 20 --toon
|
|
31
|
+
trekoon epic list --status done --toon
|
|
32
|
+
trekoon task list --all --toon
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `--all` cannot be combined with `--status` or `--limit`.
|
|
36
|
+
- `epic show <id> --all --toon`: full epic tree (tasks + subtasks)
|
|
37
|
+
- `task show <id> --all --toon`: task plus its subtasks
|
|
38
|
+
|
|
39
|
+
## 2) Create work (epic/task/subtask)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
trekoon epic create --title "..." --description "..." --status todo --toon
|
|
43
|
+
trekoon task create --epic <epic-id> --title "..." --description "..." --status todo --toon
|
|
44
|
+
trekoon subtask create --task <task-id> --title "..." --description "..." --status todo --toon
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Notes:
|
|
48
|
+
- `description` is required for epic/task create and it must be well written.
|
|
49
|
+
- `status` defaults to `todo` if omitted.
|
|
50
|
+
|
|
51
|
+
## 3) Update work
|
|
52
|
+
|
|
53
|
+
### Single-item update
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
trekoon epic update <epic-id> --title "..." --description "..." --status in_progress --toon
|
|
57
|
+
trekoon task update <task-id> --title "..." --description "..." --status in_progress --toon
|
|
58
|
+
trekoon subtask update <subtask-id> --title "..." --description "..." --status in_progress --toon
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Bulk update (task/epic)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
trekoon task update --all --append "..." --status in_progress --toon
|
|
65
|
+
trekoon task update --ids id1,id2 --append "..." --status in_progress --toon
|
|
66
|
+
|
|
67
|
+
trekoon epic update --all --append "..." --status in_progress --toon
|
|
68
|
+
trekoon epic update --ids id1,id2 --append "..." --status in_progress --toon
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Rules:
|
|
72
|
+
- `--all` and `--ids` are mutually exclusive.
|
|
73
|
+
- In bulk mode, do not pass a positional ID.
|
|
74
|
+
- Bulk update supports `--append` and/or `--status`.
|
|
75
|
+
|
|
76
|
+
## 4) Setup/install/init (if `trekoon` is unavailable)
|
|
77
|
+
|
|
78
|
+
1. Install Trekoon (or make sure it is on `PATH`).
|
|
79
|
+
2. In the target repository/worktree, initialize tracker state:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
trekoon init
|
|
83
|
+
```
|
|
84
|
+
3. You can always run `trekoon quickstart` or `trekoon --help` to get more information.
|
|
85
|
+
|
|
86
|
+
If `.trekoon/trekoon.db` is missing, initialize before any create/update commands.
|
|
87
|
+
|
|
88
|
+
## 5) Safety
|
|
89
|
+
|
|
90
|
+
- Never edit `.trekoon/trekoon.db` directly.
|
|
91
|
+
- `trekoon wipe --yes` is prohibited unless the user explicitly confirms they want a destructive wipe.
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file contains guidelines for agents.
|
|
4
|
+
|
|
5
|
+
## Mandatory: Atomic Commit Policy
|
|
6
|
+
|
|
7
|
+
Every code change MUST be followed by an immediate commit.
|
|
8
|
+
|
|
9
|
+
**Commit format**:
|
|
10
|
+
```
|
|
11
|
+
<imperative verb> <what changed> ← Line 1: max 50 chars
|
|
12
|
+
<blank line> ← Line 2: blank
|
|
13
|
+
<why/context, one point per line> ← Body: max 72 chars per line
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Rules**:
|
|
17
|
+
1. One commit per logical change
|
|
18
|
+
2. Small, atomic commits - one file per commit preferred
|
|
19
|
+
3. Never batch unrelated changes
|
|
20
|
+
|
|
21
|
+
**Enforcement**:
|
|
22
|
+
- After any file modification, stop and commit before modifying another file
|
|
23
|
+
- Run `git status --short` after each commit to verify clean tree
|
|
24
|
+
- At milestones: run `bun run build && bun run lint && bun run test`
|
|
25
|
+
|
|
26
|
+
**Anti-patterns**:
|
|
27
|
+
- ❌ Multiple unrelated files in one commit
|
|
28
|
+
- ❌ Generic messages like "Update file", "WIP", "Fix stuff"
|
|
29
|
+
- ❌ Commit message over 50 chars on first line
|
|
30
|
+
|
|
31
|
+
## Coding Conventions
|
|
32
|
+
|
|
33
|
+
For Bun/TypeScript code:
|
|
34
|
+
- **Imports**: Group order (stdlib → third-party → local), explicit named imports, remove unused
|
|
35
|
+
- **Formatting**: Consistent quotes, avoid mixed tabs/spaces
|
|
36
|
+
- **Types**: Prefer explicit types at API boundaries, avoid `any` unless justified
|
|
37
|
+
- **Naming**: `camelCase` (vars/functions), `PascalCase` (types), `UPPER_SNAKE_CASE` (constants)
|
|
38
|
+
|
|
39
|
+
**Error handling**:
|
|
40
|
+
- Never silently swallow errors
|
|
41
|
+
- Include actionable context (operation + endpoint + status code)
|
|
42
|
+
- Redact secrets from errors and logs
|
|
43
|
+
|
|
44
|
+
## Agent Behavior
|
|
45
|
+
|
|
46
|
+
- Prefer minimal, targeted edits; avoid broad rewrites
|
|
47
|
+
- Preserve existing examples unless fixing factual issues
|
|
48
|
+
- For CLI changes, prioritize startup speed and low-latency UX
|
|
49
|
+
- Keep commands compatible with macOS and Linux shells
|
|
50
|
+
|
|
51
|
+
## Security
|
|
52
|
+
|
|
53
|
+
- Never commit secrets (tokens, credentials)
|
|
54
|
+
- Redact secrets from errors and logs
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Contributing to Trekoon
|
|
2
|
+
|
|
3
|
+
## No-copy implementation policy
|
|
4
|
+
|
|
5
|
+
Trekoon is implemented in this repository root. The `trekker/` directory is
|
|
6
|
+
reference-only.
|
|
7
|
+
|
|
8
|
+
- Do not copy code or files from `trekker/` into root `src/`.
|
|
9
|
+
- Do not mirror file layout one-to-one from `trekker/`.
|
|
10
|
+
- Write root implementation code directly, with Trekoon-native structure.
|
|
11
|
+
|
|
12
|
+
## PR checklist
|
|
13
|
+
|
|
14
|
+
- [ ] Any new logic was written directly in root project files.
|
|
15
|
+
- [ ] Changes were reviewed for suspiciously identical blocks/comments versus
|
|
16
|
+
`trekker/` reference code.
|
|
17
|
+
- [ ] Sync-related writes preserve git context (`branch`, `head`, `worktree`).
|
|
18
|
+
- [ ] README command/flag examples match actual implemented CLI behavior.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Trekoon
|
|
2
|
+
|
|
3
|
+
AI-first issue tracking for humans and agents.
|
|
4
|
+
|
|
5
|
+
Trekoon is a Bun-powered CLI focused on execution workflows where AI agents and humans share the same task graph.
|
|
6
|
+
|
|
7
|
+
## What Trekoon is
|
|
8
|
+
|
|
9
|
+
- Local-first CLI issue tracker
|
|
10
|
+
- Structured hierarchy: **epic → task → subtask**
|
|
11
|
+
- UUID-based references for durable linking across branches/worktrees
|
|
12
|
+
- Dependency-aware planning and execution
|
|
13
|
+
- Output modes:
|
|
14
|
+
- **Human mode** for terminal users
|
|
15
|
+
- **JSON mode** for stable machine parsing
|
|
16
|
+
- **TOON mode** for true TOON-encoded payloads
|
|
17
|
+
|
|
18
|
+
## What Trekoon aims to accomplish
|
|
19
|
+
|
|
20
|
+
1. Make issue tracking fast enough for daily terminal use.
|
|
21
|
+
2. Make issue data deterministic and machine-readable for AI automation.
|
|
22
|
+
3. Keep branch/worktree-aware state so parallel execution can be coordinated safely.
|
|
23
|
+
4. Stay minimal in code size while preserving robustness and clear boundaries.
|
|
24
|
+
|
|
25
|
+
## Command surface
|
|
26
|
+
|
|
27
|
+
- `trekoon init`
|
|
28
|
+
- `trekoon quickstart`
|
|
29
|
+
- `trekoon epic <create|list|show|update|delete>`
|
|
30
|
+
- `trekoon task <create|list|show|update|delete>`
|
|
31
|
+
- `trekoon subtask <create|list|update|delete>`
|
|
32
|
+
- `trekoon dep <add|remove|list>`
|
|
33
|
+
- `trekoon sync <status|pull|resolve>`
|
|
34
|
+
- `trekoon wipe --yes`
|
|
35
|
+
|
|
36
|
+
Global output modes:
|
|
37
|
+
|
|
38
|
+
- `--json` for structured JSON output
|
|
39
|
+
- `--toon` for true TOON-encoded output (not JSON text)
|
|
40
|
+
- `--help` for root and command help
|
|
41
|
+
- `--version` for CLI version
|
|
42
|
+
|
|
43
|
+
Global options can be used before or after the command:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
trekoon --toon quickstart
|
|
47
|
+
trekoon quickstart --toon
|
|
48
|
+
trekoon --json quickstart
|
|
49
|
+
trekoon quickstart --json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Trekoon currently accepts long option form (`--option`).
|
|
53
|
+
|
|
54
|
+
Human view options:
|
|
55
|
+
|
|
56
|
+
- List and show commands default to table output in human mode.
|
|
57
|
+
- Use `--view compact` to restore compact pipe output.
|
|
58
|
+
- `epic list` and `task list` support `--view table|compact`.
|
|
59
|
+
- `epic show` and `task show` support `--view table|compact|tree|detail`.
|
|
60
|
+
|
|
61
|
+
List defaults and filters (`epic list`, `task list`):
|
|
62
|
+
|
|
63
|
+
- Default scope: open work only (`in_progress`, `in-progress`, `todo`)
|
|
64
|
+
- Default limit: `10`
|
|
65
|
+
- Status filter: `--status in_progress,todo` (CSV)
|
|
66
|
+
- Custom limit: `--limit <n>`
|
|
67
|
+
- All rows and statuses: `--all`
|
|
68
|
+
- `--all` is mutually exclusive with `--status` and `--limit`
|
|
69
|
+
|
|
70
|
+
Bulk updates (`epic update`, `task update`):
|
|
71
|
+
|
|
72
|
+
- Target all rows: `--all`
|
|
73
|
+
- Target specific rows: `--ids <id1,id2,...>`
|
|
74
|
+
- Bulk updates support only `--append <text>` and/or `--status <status>`
|
|
75
|
+
- In bulk mode, do not pass a positional id
|
|
76
|
+
- `--all` and `--ids` are mutually exclusive
|
|
77
|
+
- `--append` and `--description` are mutually exclusive
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
trekoon task update --all --status in_progress
|
|
83
|
+
trekoon task update --ids <task-1>,<task-2> --append "\nFollow-up note"
|
|
84
|
+
trekoon epic update --ids <epic-1>,<epic-2> --status done
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quickstart
|
|
88
|
+
|
|
89
|
+
Trekoon is local-first: each worktree uses its own `.trekoon/trekoon.db`.
|
|
90
|
+
Git does not merge this DB file; Trekoon sync commands merge tracker state.
|
|
91
|
+
|
|
92
|
+
### 1) Initialize
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
trekoon init
|
|
96
|
+
trekoon --version
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2) Create epic → task → subtask
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
trekoon epic create --title "Agent backlog stabilization" --description "Track stabilization work" --status todo
|
|
103
|
+
trekoon task create --title "Implement sync status" --description "Add status reporting" --epic <epic-id> --status todo
|
|
104
|
+
trekoon subtask create --task <task-id> --title "Add cursor model" --status todo
|
|
105
|
+
trekoon task list
|
|
106
|
+
trekoon task list --status done
|
|
107
|
+
trekoon task list --limit 25
|
|
108
|
+
trekoon task list --all --view compact
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3) Add dependencies
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
trekoon dep add <task-id> <depends-on-id>
|
|
115
|
+
trekoon dep list <task-id>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4) Use JSON or TOON output for agents
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
trekoon --json epic show <epic-id>
|
|
122
|
+
trekoon --json task show <task-id>
|
|
123
|
+
trekoon --toon epic show <epic-id>
|
|
124
|
+
trekoon --toon task show <task-id>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 5) Sync workflow for worktrees
|
|
128
|
+
|
|
129
|
+
- Run `trekoon sync status` at session start and before PR/merge.
|
|
130
|
+
- Run `trekoon sync pull --from main` before merge to align tracker state.
|
|
131
|
+
- If conflicts exist, resolve explicitly:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
trekoon sync status
|
|
135
|
+
trekoon sync pull --from main
|
|
136
|
+
trekoon sync resolve <conflict-id> --use ours
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 6) Pre-merge checklist
|
|
140
|
+
|
|
141
|
+
- [ ] `trekoon sync status` shows no unresolved conflicts
|
|
142
|
+
- [ ] done tasks/subtasks are marked completed
|
|
143
|
+
- [ ] dependency graph has no stale blockers
|
|
144
|
+
- [ ] final AI check: `trekoon --json epic show <epic-id>`
|
|
145
|
+
|
|
146
|
+
## Implementation principles
|
|
147
|
+
|
|
148
|
+
- Minimal, composable modules
|
|
149
|
+
- Strict validation at command boundaries
|
|
150
|
+
- Stable automation envelope for JSON/TOON modes
|
|
151
|
+
- No unnecessary feature sprawl
|
package/bin/trekoon
ADDED
package/bun.lock
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "trekoon",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@toon-format/toon": "^2.1.0",
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/bun": "^1.3.0",
|
|
11
|
+
"typescript": "^5.9.2",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
"packages": {
|
|
16
|
+
"@toon-format/toon": ["@toon-format/toon@2.1.0", "", {}, "sha512-JwWptdF5eOA0HaQxbKAzkpQtR4wSWTEfDlEy/y3/4okmOAX1qwnpLZMmtEWr+ncAhTTY1raCKH0kteHhSXnQqg=="],
|
|
17
|
+
|
|
18
|
+
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
|
19
|
+
|
|
20
|
+
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
|
21
|
+
|
|
22
|
+
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
|
23
|
+
|
|
24
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
25
|
+
|
|
26
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
27
|
+
}
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trekoon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-first local issue tracker CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"trekoon": "./bin/trekoon"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"run": "bun run ./src/index.ts",
|
|
11
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
12
|
+
"test": "bun test ./tests"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "^1.3.9",
|
|
16
|
+
"typescript": "^5.9.3"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@toon-format/toon": "^2.1.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"bun": ">=1.2.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export interface ParsedArgs {
|
|
2
|
+
readonly positional: readonly string[];
|
|
3
|
+
readonly options: ReadonlyMap<string, string>;
|
|
4
|
+
readonly flags: ReadonlySet<string>;
|
|
5
|
+
readonly missingOptionValues: ReadonlySet<string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const LONG_PREFIX = "--";
|
|
9
|
+
|
|
10
|
+
export function parseArgs(args: readonly string[]): ParsedArgs {
|
|
11
|
+
const positional: string[] = [];
|
|
12
|
+
const options = new Map<string, string>();
|
|
13
|
+
const flags = new Set<string>();
|
|
14
|
+
const missingOptionValues = new Set<string>();
|
|
15
|
+
|
|
16
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
17
|
+
const token: string | undefined = args[index];
|
|
18
|
+
if (!token) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!token.startsWith(LONG_PREFIX)) {
|
|
23
|
+
positional.push(token);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const key = token.slice(LONG_PREFIX.length);
|
|
28
|
+
const value = args[index + 1];
|
|
29
|
+
if (!value || value.startsWith(LONG_PREFIX)) {
|
|
30
|
+
flags.add(key);
|
|
31
|
+
missingOptionValues.add(key);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
options.set(key, value);
|
|
36
|
+
index += 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
positional,
|
|
41
|
+
options,
|
|
42
|
+
flags,
|
|
43
|
+
missingOptionValues,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readOption(options: ReadonlyMap<string, string>, ...keys: string[]): string | undefined {
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const value: string | undefined = options.get(key);
|
|
50
|
+
if (value !== undefined) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hasFlag(flags: ReadonlySet<string>, ...keys: string[]): boolean {
|
|
59
|
+
return keys.some((key) => flags.has(key));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function readMissingOptionValue(
|
|
63
|
+
missingOptionValues: ReadonlySet<string>,
|
|
64
|
+
...keys: string[]
|
|
65
|
+
): string | undefined {
|
|
66
|
+
return keys.find((key) => missingOptionValues.has(key));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function parseStrictPositiveInt(rawValue: string | undefined): number | undefined {
|
|
70
|
+
if (rawValue === undefined) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
75
|
+
if (!Number.isInteger(parsed) || parsed < 1 || `${parsed}` !== rawValue.trim()) {
|
|
76
|
+
return Number.NaN;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return parsed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function readEnumOption<const T extends readonly string[]>(
|
|
83
|
+
options: ReadonlyMap<string, string>,
|
|
84
|
+
allowed: T,
|
|
85
|
+
...keys: string[]
|
|
86
|
+
): T[number] | undefined {
|
|
87
|
+
const value: string | undefined = readOption(options, ...keys);
|
|
88
|
+
if (value === undefined) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return allowed.includes(value) ? value : undefined;
|
|
93
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { parseArgs } from "./arg-parser";
|
|
2
|
+
|
|
3
|
+
import { DomainError } from "../domain/types";
|
|
4
|
+
import { TrackerDomain } from "../domain/tracker-domain";
|
|
5
|
+
import { failResult, okResult } from "../io/output";
|
|
6
|
+
import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
7
|
+
import { openTrekoonDatabase } from "../storage/database";
|
|
8
|
+
|
|
9
|
+
function failFromError(error: unknown): CliResult {
|
|
10
|
+
if (error instanceof DomainError) {
|
|
11
|
+
return failResult({
|
|
12
|
+
command: "dep",
|
|
13
|
+
human: error.message,
|
|
14
|
+
data: {
|
|
15
|
+
code: error.code,
|
|
16
|
+
...(error.details ?? {}),
|
|
17
|
+
},
|
|
18
|
+
error: {
|
|
19
|
+
code: error.code,
|
|
20
|
+
message: error.message,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return failResult({
|
|
26
|
+
command: "dep",
|
|
27
|
+
human: "Unexpected dep command failure",
|
|
28
|
+
data: {},
|
|
29
|
+
error: {
|
|
30
|
+
code: "internal_error",
|
|
31
|
+
message: "Unexpected dep command failure",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function runDep(context: CliContext): Promise<CliResult> {
|
|
37
|
+
const database = openTrekoonDatabase(context.cwd);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const parsed = parseArgs(context.args);
|
|
41
|
+
const subcommand: string | undefined = parsed.positional[0];
|
|
42
|
+
const sourceId: string = parsed.positional[1] ?? "";
|
|
43
|
+
const dependsOnId: string = parsed.positional[2] ?? "";
|
|
44
|
+
const domain = new TrackerDomain(database.db);
|
|
45
|
+
|
|
46
|
+
switch (subcommand) {
|
|
47
|
+
case "add": {
|
|
48
|
+
const dependency = domain.addDependency(sourceId, dependsOnId);
|
|
49
|
+
|
|
50
|
+
return okResult({
|
|
51
|
+
command: "dep.add",
|
|
52
|
+
human: `Added dependency ${dependency.sourceId} -> ${dependency.dependsOnId}`,
|
|
53
|
+
data: { dependency },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
case "remove": {
|
|
57
|
+
const removed: number = domain.removeDependency(sourceId, dependsOnId);
|
|
58
|
+
|
|
59
|
+
return okResult({
|
|
60
|
+
command: "dep.remove",
|
|
61
|
+
human:
|
|
62
|
+
removed > 0
|
|
63
|
+
? `Removed dependency ${sourceId} -> ${dependsOnId}`
|
|
64
|
+
: `No dependency found for ${sourceId} -> ${dependsOnId}`,
|
|
65
|
+
data: {
|
|
66
|
+
sourceId,
|
|
67
|
+
dependsOnId,
|
|
68
|
+
removed,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
case "list": {
|
|
73
|
+
const dependencies = domain.listDependencies(sourceId);
|
|
74
|
+
|
|
75
|
+
return okResult({
|
|
76
|
+
command: "dep.list",
|
|
77
|
+
human:
|
|
78
|
+
dependencies.length === 0
|
|
79
|
+
? `No dependencies for ${sourceId}`
|
|
80
|
+
: dependencies.map((item) => `${item.sourceId} -> ${item.dependsOnId}`).join("\n"),
|
|
81
|
+
data: {
|
|
82
|
+
sourceId,
|
|
83
|
+
dependencies,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
return failResult({
|
|
89
|
+
command: "dep",
|
|
90
|
+
human: "Usage: trekoon dep <add|remove|list>",
|
|
91
|
+
data: {
|
|
92
|
+
args: context.args,
|
|
93
|
+
},
|
|
94
|
+
error: {
|
|
95
|
+
code: "invalid_subcommand",
|
|
96
|
+
message: "Invalid dep subcommand",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} catch (error: unknown) {
|
|
101
|
+
return failFromError(error);
|
|
102
|
+
} finally {
|
|
103
|
+
database.close();
|
|
104
|
+
}
|
|
105
|
+
}
|