trekoon 0.4.1 → 0.4.3
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 +97 -765
- package/.agents/skills/trekoon/reference/execution-with-team.md +91 -141
- package/.agents/skills/trekoon/reference/execution.md +188 -159
- package/.agents/skills/trekoon/reference/harness-primitives.md +77 -0
- package/.agents/skills/trekoon/reference/planning.md +213 -213
- package/.agents/skills/trekoon/reference/status-machine.md +21 -0
- package/.agents/skills/trekoon/reference/sync.md +82 -0
- package/README.md +29 -8
- package/docs/ai-agents.md +65 -6
- package/docs/commands.md +149 -5
- package/docs/machine-contracts.md +123 -0
- package/docs/quickstart.md +55 -3
- package/package.json +1 -1
- package/src/board/assets/app.js +47 -13
- package/src/board/assets/components/Component.js +20 -8
- package/src/board/assets/components/Workspace.js +9 -3
- package/src/board/assets/components/helpers.js +4 -0
- package/src/board/assets/runtime/delegation.js +8 -0
- package/src/board/assets/runtime/focus-trap.js +48 -0
- package/src/board/assets/state/actions.js +45 -4
- package/src/board/assets/state/api.js +304 -17
- package/src/board/assets/state/store.js +82 -11
- package/src/board/assets/state/url.js +10 -0
- package/src/board/assets/state/utils.js +2 -1
- package/src/board/event-bus.ts +81 -0
- package/src/board/routes.ts +430 -40
- package/src/board/server.ts +86 -10
- package/src/board/snapshot.ts +6 -0
- package/src/board/wal-watcher.ts +313 -0
- package/src/commands/board.ts +52 -17
- package/src/commands/epic.ts +7 -9
- package/src/commands/error-utils.ts +54 -1
- package/src/commands/help.ts +75 -10
- package/src/commands/migrate.ts +153 -24
- package/src/commands/quickstart.ts +7 -0
- package/src/commands/skills.ts +17 -5
- package/src/commands/subtask.ts +71 -10
- package/src/commands/suggest.ts +6 -13
- package/src/commands/task.ts +137 -88
- package/src/domain/batch-validation.ts +329 -0
- package/src/domain/cascade-planner.ts +412 -0
- package/src/domain/dependency-rules.ts +15 -0
- package/src/domain/mutation-service.ts +842 -187
- package/src/domain/search.ts +113 -0
- package/src/domain/tracker-domain.ts +167 -693
- package/src/domain/types.ts +56 -2
- package/src/export/render-markdown.ts +1 -2
- package/src/index.ts +37 -0
- package/src/runtime/cli-shell.ts +44 -0
- package/src/runtime/daemon.ts +700 -0
- package/src/storage/backup.ts +166 -0
- package/src/storage/database.ts +268 -4
- package/src/storage/migrations.ts +441 -22
- package/src/storage/path.ts +8 -0
- package/src/storage/schema.ts +5 -1
- package/src/sync/event-writes.ts +38 -11
- package/src/sync/git-context.ts +226 -8
- package/src/sync/service.ts +679 -156
package/src/domain/types.ts
CHANGED
|
@@ -97,6 +97,7 @@ export interface EpicRecord {
|
|
|
97
97
|
readonly status: string;
|
|
98
98
|
readonly createdAt: number;
|
|
99
99
|
readonly updatedAt: number;
|
|
100
|
+
readonly version: number;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export interface TaskRecord {
|
|
@@ -108,6 +109,7 @@ export interface TaskRecord {
|
|
|
108
109
|
readonly owner: string | null;
|
|
109
110
|
readonly createdAt: number;
|
|
110
111
|
readonly updatedAt: number;
|
|
112
|
+
readonly version: number;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
export interface SubtaskRecord {
|
|
@@ -119,6 +121,7 @@ export interface SubtaskRecord {
|
|
|
119
121
|
readonly owner: string | null;
|
|
120
122
|
readonly createdAt: number;
|
|
121
123
|
readonly updatedAt: number;
|
|
124
|
+
readonly version: number;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
export interface DependencyRecord {
|
|
@@ -255,14 +258,65 @@ export interface StatusCascadePlan {
|
|
|
255
258
|
readonly counts: StatusCascadeCounts;
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
export const ERROR_CODES = {
|
|
262
|
+
ALREADY_DONE: "already_done",
|
|
263
|
+
ALREADY_RESOLVED: "already_resolved",
|
|
264
|
+
AMBIGUOUS_LEGACY_STATE: "ambiguous_legacy_state",
|
|
265
|
+
BACKPRESSURE: "backpressure",
|
|
266
|
+
BACKUP_ALREADY_EXISTS: "backup_already_exists",
|
|
267
|
+
BACKUP_DATABASE_MISSING: "backup_database_missing",
|
|
268
|
+
BACKUP_FAILED: "backup_failed",
|
|
269
|
+
CANCELLED: "cancelled",
|
|
270
|
+
CONFIRMATION_REQUIRED: "confirmation_required",
|
|
271
|
+
CONFLICT_SET_CHANGED: "conflict_set_changed",
|
|
272
|
+
DAEMON_START_FAILED: "daemon_start_failed",
|
|
273
|
+
DATABASE_BUSY: "database_busy",
|
|
274
|
+
DEPENDENCY_BLOCKED: "dependency_blocked",
|
|
275
|
+
DISALLOWED_FIELD: "disallowed_field",
|
|
276
|
+
EVENTS_FAILED: "events_failed",
|
|
277
|
+
INSTALL_FAILED: "install_failed",
|
|
278
|
+
INTERNAL_ERROR: "internal_error",
|
|
279
|
+
INVALID_ARGS: "invalid_args",
|
|
280
|
+
INVALID_DEPENDENCY: "invalid_dependency",
|
|
281
|
+
INVALID_INPUT: "invalid_input",
|
|
282
|
+
INVALID_PATH: "invalid_path",
|
|
283
|
+
INVALID_SOURCE: "invalid_source",
|
|
284
|
+
INVALID_STATE: "invalid_state",
|
|
285
|
+
INVALID_SUBCOMMAND: "invalid_subcommand",
|
|
286
|
+
LEGACY_IMPORT_FAILED: "legacy_import_failed",
|
|
287
|
+
MIGRATE_FAILED: "migrate_failed",
|
|
288
|
+
MIGRATION_DOWN_UNSUPPORTED: "migration_down_unsupported",
|
|
289
|
+
MISSING_ASSET: "missing_asset",
|
|
290
|
+
NO_MATCHING_CONFLICTS: "no_matching_conflicts",
|
|
291
|
+
NOT_FOUND: "not_found",
|
|
292
|
+
ORPHANED_EXTERNAL_NODE: "orphaned_external_node",
|
|
293
|
+
OUTSIDE_REPO_TARGET: "outside_repo_target",
|
|
294
|
+
PERMISSION_DENIED: "permission_denied",
|
|
295
|
+
PRECONDITION_FAILED: "precondition_failed",
|
|
296
|
+
ROW_NOT_FOUND: "row_not_found",
|
|
297
|
+
STATUS_TRANSITION_INVALID: "status_transition_invalid",
|
|
298
|
+
STREAM_UNAVAILABLE: "stream_unavailable",
|
|
299
|
+
SYNC_FAILED: "sync_failed",
|
|
300
|
+
TRACKED_IGNORED_MISMATCH: "tracked_ignored_mismatch",
|
|
301
|
+
UNAUTHORIZED: "unauthorized",
|
|
302
|
+
UNHANDLED_COMMAND: "unhandled_command",
|
|
303
|
+
UNKNOWN_COMMAND: "unknown_command",
|
|
304
|
+
UNKNOWN_OPTION: "unknown_option",
|
|
305
|
+
UNSUPPORTED_ENTITY_KIND: "unsupported_entity_kind",
|
|
306
|
+
UPDATE_FAILED: "update_failed",
|
|
307
|
+
WRONG_ENTITY_TYPE: "wrong_entity_type",
|
|
308
|
+
} as const;
|
|
309
|
+
|
|
310
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
311
|
+
|
|
258
312
|
export interface DomainErrorShape {
|
|
259
|
-
readonly code:
|
|
313
|
+
readonly code: ErrorCode;
|
|
260
314
|
readonly message: string;
|
|
261
315
|
readonly details?: Record<string, unknown>;
|
|
262
316
|
}
|
|
263
317
|
|
|
264
318
|
export class DomainError extends Error {
|
|
265
|
-
readonly code:
|
|
319
|
+
readonly code: ErrorCode;
|
|
266
320
|
readonly details?: Record<string, unknown>;
|
|
267
321
|
|
|
268
322
|
constructor(input: DomainErrorShape) {
|
|
@@ -124,8 +124,7 @@ function renderTaskIndex(lines: string[], bundle: ExportBundle): void {
|
|
|
124
124
|
lines.push("| # | Title | Status | Subtasks |");
|
|
125
125
|
lines.push("|---|-------|--------|----------|");
|
|
126
126
|
|
|
127
|
-
for (
|
|
128
|
-
const task = bundle.tasks[i];
|
|
127
|
+
for (const [i, task] of bundle.tasks.entries()) {
|
|
129
128
|
const subtaskCount = bundle.subtasks.filter((s) => s.taskId === task.id).length;
|
|
130
129
|
const anchor = taskAnchor(task);
|
|
131
130
|
lines.push(`| ${i + 1} | [${escapeTableCell(escapeInlineText(task.title))}](#${anchor}) | ${task.status} | ${subtaskCount} |`);
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,43 @@ import { executeShell, parseInvocation, renderShellResult } from "./runtime/cli-
|
|
|
4
4
|
|
|
5
5
|
export async function run(argv: readonly string[] = process.argv.slice(2)): Promise<void> {
|
|
6
6
|
const parsed = parseInvocation(argv);
|
|
7
|
+
|
|
8
|
+
// Daemon path is opt-in (TREKOON_DAEMON=1 or --daemon). The `serve`
|
|
9
|
+
// subcommand always runs in-process so it can host the daemon.
|
|
10
|
+
const daemonRequested: boolean = parsed.wantsDaemon || process.env.TREKOON_DAEMON === "1";
|
|
11
|
+
if (daemonRequested && parsed.command !== "serve") {
|
|
12
|
+
const { tryDaemonDispatch, PostWriteError } = await import("./runtime/daemon");
|
|
13
|
+
// Strip --daemon from the argv we forward (the server has its own dispatch).
|
|
14
|
+
const forwarded: readonly string[] = argv.filter((token: string): boolean => token !== "--daemon");
|
|
15
|
+
try {
|
|
16
|
+
const daemonResult = await tryDaemonDispatch(forwarded);
|
|
17
|
+
if (daemonResult !== null) {
|
|
18
|
+
if (daemonResult.stdout.length > 0) {
|
|
19
|
+
process.stdout.write(daemonResult.stdout);
|
|
20
|
+
}
|
|
21
|
+
if (daemonResult.stderr.length > 0) {
|
|
22
|
+
process.stderr.write(daemonResult.stderr);
|
|
23
|
+
}
|
|
24
|
+
process.exitCode = daemonResult.exitCode;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Fall through to one-shot CLI when no daemon is reachable (pre-write
|
|
28
|
+
// transport failure — request never made it onto the wire).
|
|
29
|
+
} catch (error: unknown) {
|
|
30
|
+
// Post-write failure: the request bytes were already flushed to the
|
|
31
|
+
// daemon. The mutation may have committed. Refuse to silently re-run
|
|
32
|
+
// the command in-process; exit non-zero so the caller can decide.
|
|
33
|
+
if (error instanceof PostWriteError) {
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
`trekoon: daemon may have committed; do not retry: ${error.message}\n`,
|
|
36
|
+
);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
7
44
|
const result = await executeShell(parsed);
|
|
8
45
|
const rendered: string = renderShellResult(result, parsed.mode, parsed.compatibilityMode, { compact: parsed.compact });
|
|
9
46
|
|
package/src/runtime/cli-shell.ts
CHANGED
|
@@ -36,6 +36,7 @@ const SUPPORTED_ROOT_COMMANDS: readonly string[] = [
|
|
|
36
36
|
"suggest",
|
|
37
37
|
"update",
|
|
38
38
|
"wipe",
|
|
39
|
+
"serve",
|
|
39
40
|
];
|
|
40
41
|
|
|
41
42
|
export interface ParsedInvocation {
|
|
@@ -48,6 +49,7 @@ export interface ParsedInvocation {
|
|
|
48
49
|
readonly args: readonly string[];
|
|
49
50
|
readonly wantsHelp: boolean;
|
|
50
51
|
readonly wantsVersion: boolean;
|
|
52
|
+
readonly wantsDaemon: boolean;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export interface ParseInvocationOptions {
|
|
@@ -62,6 +64,7 @@ export function parseInvocation(argv: readonly string[], options: ParseInvocatio
|
|
|
62
64
|
let compatibilityModeMissingValue = false;
|
|
63
65
|
let wantsHelp = false;
|
|
64
66
|
let wantsVersion = false;
|
|
67
|
+
let wantsDaemon = false;
|
|
65
68
|
const positionals: string[] = [];
|
|
66
69
|
|
|
67
70
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -95,6 +98,11 @@ export function parseInvocation(argv: readonly string[], options: ParseInvocatio
|
|
|
95
98
|
continue;
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
if (token === "--daemon") {
|
|
102
|
+
wantsDaemon = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
if (token === "--compat") {
|
|
99
107
|
const maybeValue: string | undefined = argv[index + 1];
|
|
100
108
|
if (!maybeValue || maybeValue.startsWith("--")) {
|
|
@@ -123,6 +131,7 @@ export function parseInvocation(argv: readonly string[], options: ParseInvocatio
|
|
|
123
131
|
args: positionals.slice(1),
|
|
124
132
|
wantsHelp,
|
|
125
133
|
wantsVersion,
|
|
134
|
+
wantsDaemon,
|
|
126
135
|
};
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -403,6 +412,11 @@ export async function executeShell(parsed: ParsedInvocation, cwd: string = proce
|
|
|
403
412
|
// Route `trekoon update` to `trekoon skills update` internally.
|
|
404
413
|
result = await runSkills({ ...context, args: ["update", ...context.args] });
|
|
405
414
|
break;
|
|
415
|
+
case "serve":
|
|
416
|
+
// Experimental: run the daemon in the foreground. Loaded lazily so the
|
|
417
|
+
// default cold path stays free of node:net imports.
|
|
418
|
+
result = await runServe(context);
|
|
419
|
+
break;
|
|
406
420
|
default:
|
|
407
421
|
result = failResult({
|
|
408
422
|
command: "shell",
|
|
@@ -418,3 +432,33 @@ export async function executeShell(parsed: ParsedInvocation, cwd: string = proce
|
|
|
418
432
|
|
|
419
433
|
return withStorageRootDiagnostics(result, cwd);
|
|
420
434
|
}
|
|
435
|
+
|
|
436
|
+
async function runServe(context: CliContext): Promise<CliResult> {
|
|
437
|
+
// Lazy import keeps node:net out of the normal one-shot dispatch path.
|
|
438
|
+
const { runDaemonForeground, resolveDaemonSocketPath } = await import("./daemon");
|
|
439
|
+
const socketPath: string = resolveDaemonSocketPath(context.cwd);
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
await runDaemonForeground({ cwd: context.cwd, silent: context.mode !== "human" });
|
|
443
|
+
return okResult({
|
|
444
|
+
command: "serve",
|
|
445
|
+
human: `Daemon stopped (socket: ${socketPath})`,
|
|
446
|
+
data: {
|
|
447
|
+
socketPath,
|
|
448
|
+
status: "stopped",
|
|
449
|
+
experimental: true,
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
} catch (error: unknown) {
|
|
453
|
+
const message: string = error instanceof Error ? error.message : String(error);
|
|
454
|
+
return failResult({
|
|
455
|
+
command: "serve",
|
|
456
|
+
human: `Daemon failed to start: ${message}`,
|
|
457
|
+
data: { socketPath },
|
|
458
|
+
error: {
|
|
459
|
+
code: "daemon_start_failed",
|
|
460
|
+
message,
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|