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,49 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
|
|
7
|
+
export interface BranchDatabaseSnapshot {
|
|
8
|
+
readonly branch: string;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly db: Database;
|
|
11
|
+
close(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class MissingBranchDatabaseError extends Error {
|
|
15
|
+
constructor(branch: string) {
|
|
16
|
+
super(`Unable to read .trekoon/trekoon.db from branch '${branch}'.`);
|
|
17
|
+
this.name = "MissingBranchDatabaseError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function openBranchDatabaseSnapshot(branch: string, cwd: string): BranchDatabaseSnapshot {
|
|
22
|
+
const show = Bun.spawnSync({
|
|
23
|
+
cmd: ["git", "show", `${branch}:.trekoon/trekoon.db`],
|
|
24
|
+
cwd,
|
|
25
|
+
stdout: "pipe",
|
|
26
|
+
stderr: "pipe",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (show.exitCode !== 0 || show.stdout.byteLength === 0) {
|
|
30
|
+
throw new MissingBranchDatabaseError(branch);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tempDir: string = mkdtempSync(join(tmpdir(), "trekoon-sync-branch-"));
|
|
34
|
+
const tempDbPath: string = join(tempDir, "remote.db");
|
|
35
|
+
|
|
36
|
+
writeFileSync(tempDbPath, show.stdout);
|
|
37
|
+
|
|
38
|
+
const db: Database = new Database(tempDbPath);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
branch,
|
|
42
|
+
path: tempDbPath,
|
|
43
|
+
db,
|
|
44
|
+
close(): void {
|
|
45
|
+
db.close(false);
|
|
46
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { type Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import { persistGitContext, resolveGitContext } from "./git-context";
|
|
6
|
+
|
|
7
|
+
interface EventRecordInput {
|
|
8
|
+
readonly entityKind: string;
|
|
9
|
+
readonly entityId: string;
|
|
10
|
+
readonly operation: string;
|
|
11
|
+
readonly fields: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function appendEventWithGitContext(db: Database, cwd: string, input: EventRecordInput): string {
|
|
15
|
+
const git = resolveGitContext(cwd);
|
|
16
|
+
persistGitContext(db, git);
|
|
17
|
+
|
|
18
|
+
const now: number = Date.now();
|
|
19
|
+
const eventId: string = randomUUID();
|
|
20
|
+
|
|
21
|
+
db.query(
|
|
22
|
+
`
|
|
23
|
+
INSERT INTO events (
|
|
24
|
+
id,
|
|
25
|
+
entity_kind,
|
|
26
|
+
entity_id,
|
|
27
|
+
operation,
|
|
28
|
+
payload,
|
|
29
|
+
git_branch,
|
|
30
|
+
git_head,
|
|
31
|
+
created_at,
|
|
32
|
+
updated_at,
|
|
33
|
+
version
|
|
34
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1);
|
|
35
|
+
`,
|
|
36
|
+
).run(
|
|
37
|
+
eventId,
|
|
38
|
+
input.entityKind,
|
|
39
|
+
input.entityId,
|
|
40
|
+
input.operation,
|
|
41
|
+
JSON.stringify({ fields: input.fields }),
|
|
42
|
+
git.branchName,
|
|
43
|
+
git.headSha,
|
|
44
|
+
now,
|
|
45
|
+
now,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return eventId;
|
|
49
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import { type GitContextSnapshot } from "./types";
|
|
4
|
+
|
|
5
|
+
function runGit(args: readonly string[], cwd: string): string | null {
|
|
6
|
+
const command = Bun.spawnSync({
|
|
7
|
+
cmd: ["git", ...args],
|
|
8
|
+
cwd,
|
|
9
|
+
stdout: "pipe",
|
|
10
|
+
stderr: "pipe",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (command.exitCode !== 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const output: string = new TextDecoder().decode(command.stdout).trim();
|
|
18
|
+
return output.length > 0 ? output : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveGitContext(cwd: string): GitContextSnapshot {
|
|
22
|
+
const branchName: string | null = runGit(["branch", "--show-current"], cwd);
|
|
23
|
+
const headSha: string | null = runGit(["rev-parse", "HEAD"], cwd);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
worktreePath: cwd,
|
|
27
|
+
branchName,
|
|
28
|
+
headSha,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function persistGitContext(db: Database, git: GitContextSnapshot): void {
|
|
33
|
+
const now: number = Date.now();
|
|
34
|
+
|
|
35
|
+
db.query(
|
|
36
|
+
`
|
|
37
|
+
INSERT INTO git_context (
|
|
38
|
+
id,
|
|
39
|
+
worktree_path,
|
|
40
|
+
branch_name,
|
|
41
|
+
head_sha,
|
|
42
|
+
created_at,
|
|
43
|
+
updated_at,
|
|
44
|
+
version
|
|
45
|
+
) VALUES (
|
|
46
|
+
'current',
|
|
47
|
+
@worktreePath,
|
|
48
|
+
@branchName,
|
|
49
|
+
@headSha,
|
|
50
|
+
@now,
|
|
51
|
+
@now,
|
|
52
|
+
1
|
|
53
|
+
)
|
|
54
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
55
|
+
worktree_path = excluded.worktree_path,
|
|
56
|
+
branch_name = excluded.branch_name,
|
|
57
|
+
head_sha = excluded.head_sha,
|
|
58
|
+
updated_at = excluded.updated_at,
|
|
59
|
+
version = git_context.version + 1;
|
|
60
|
+
`,
|
|
61
|
+
).run({
|
|
62
|
+
"@worktreePath": git.worktreePath,
|
|
63
|
+
"@branchName": git.branchName,
|
|
64
|
+
"@headSha": git.headSha,
|
|
65
|
+
"@now": now,
|
|
66
|
+
});
|
|
67
|
+
}
|