trekoon 0.4.1 → 0.4.2
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 +20 -577
- package/.agents/skills/trekoon/reference/execution-with-team.md +21 -9
- package/.agents/skills/trekoon/reference/execution.md +246 -7
- package/.agents/skills/trekoon/reference/planning.md +138 -1
- package/.agents/skills/trekoon/reference/status-machine.md +21 -0
- package/.agents/skills/trekoon/reference/sync.md +129 -0
- package/README.md +8 -1
- package/docs/ai-agents.md +17 -2
- package/docs/commands.md +147 -3
- package/docs/machine-contracts.md +123 -0
- package/docs/quickstart.md +52 -0
- package/package.json +1 -1
- package/src/board/assets/app.js +45 -13
- package/src/board/assets/components/Component.js +22 -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 +42 -4
- package/src/board/assets/state/api.js +284 -11
- package/src/board/assets/state/store.js +79 -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 +72 -0
- package/src/board/routes.ts +412 -33
- package/src/board/server.ts +77 -8
- package/src/board/wal-watcher.ts +302 -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 +69 -4
- package/src/commands/migrate.ts +153 -24
- package/src/commands/quickstart.ts +7 -0
- 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 +828 -192
- package/src/domain/search.ts +113 -0
- package/src/domain/tracker-domain.ts +150 -680
- package/src/domain/types.ts +53 -2
- package/src/index.ts +37 -0
- package/src/runtime/cli-shell.ts +44 -0
- package/src/runtime/daemon.ts +639 -0
- package/src/storage/backup.ts +166 -0
- package/src/storage/database.ts +261 -4
- package/src/storage/migrations.ts +422 -20
- 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 +650 -147
package/src/domain/types.ts
CHANGED
|
@@ -255,14 +255,65 @@ export interface StatusCascadePlan {
|
|
|
255
255
|
readonly counts: StatusCascadeCounts;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
export const ERROR_CODES = {
|
|
259
|
+
ALREADY_DONE: "already_done",
|
|
260
|
+
ALREADY_RESOLVED: "already_resolved",
|
|
261
|
+
AMBIGUOUS_LEGACY_STATE: "ambiguous_legacy_state",
|
|
262
|
+
BACKPRESSURE: "backpressure",
|
|
263
|
+
BACKUP_ALREADY_EXISTS: "backup_already_exists",
|
|
264
|
+
BACKUP_DATABASE_MISSING: "backup_database_missing",
|
|
265
|
+
BACKUP_FAILED: "backup_failed",
|
|
266
|
+
CANCELLED: "cancelled",
|
|
267
|
+
CONFIRMATION_REQUIRED: "confirmation_required",
|
|
268
|
+
CONFLICT_SET_CHANGED: "conflict_set_changed",
|
|
269
|
+
DAEMON_START_FAILED: "daemon_start_failed",
|
|
270
|
+
DATABASE_BUSY: "database_busy",
|
|
271
|
+
DEPENDENCY_BLOCKED: "dependency_blocked",
|
|
272
|
+
DISALLOWED_FIELD: "disallowed_field",
|
|
273
|
+
EVENTS_FAILED: "events_failed",
|
|
274
|
+
INSTALL_FAILED: "install_failed",
|
|
275
|
+
INTERNAL_ERROR: "internal_error",
|
|
276
|
+
INVALID_ARGS: "invalid_args",
|
|
277
|
+
INVALID_DEPENDENCY: "invalid_dependency",
|
|
278
|
+
INVALID_INPUT: "invalid_input",
|
|
279
|
+
INVALID_PATH: "invalid_path",
|
|
280
|
+
INVALID_SOURCE: "invalid_source",
|
|
281
|
+
INVALID_STATE: "invalid_state",
|
|
282
|
+
INVALID_SUBCOMMAND: "invalid_subcommand",
|
|
283
|
+
LEGACY_IMPORT_FAILED: "legacy_import_failed",
|
|
284
|
+
MIGRATE_FAILED: "migrate_failed",
|
|
285
|
+
MIGRATION_DOWN_UNSUPPORTED: "migration_down_unsupported",
|
|
286
|
+
MISSING_ASSET: "missing_asset",
|
|
287
|
+
NO_MATCHING_CONFLICTS: "no_matching_conflicts",
|
|
288
|
+
NOT_FOUND: "not_found",
|
|
289
|
+
ORPHANED_EXTERNAL_NODE: "orphaned_external_node",
|
|
290
|
+
OUTSIDE_REPO_TARGET: "outside_repo_target",
|
|
291
|
+
PERMISSION_DENIED: "permission_denied",
|
|
292
|
+
PRECONDITION_FAILED: "precondition_failed",
|
|
293
|
+
ROW_NOT_FOUND: "row_not_found",
|
|
294
|
+
STATUS_TRANSITION_INVALID: "status_transition_invalid",
|
|
295
|
+
STREAM_UNAVAILABLE: "stream_unavailable",
|
|
296
|
+
SYNC_FAILED: "sync_failed",
|
|
297
|
+
TRACKED_IGNORED_MISMATCH: "tracked_ignored_mismatch",
|
|
298
|
+
UNAUTHORIZED: "unauthorized",
|
|
299
|
+
UNHANDLED_COMMAND: "unhandled_command",
|
|
300
|
+
UNKNOWN_COMMAND: "unknown_command",
|
|
301
|
+
UNKNOWN_OPTION: "unknown_option",
|
|
302
|
+
UNSUPPORTED_ENTITY_KIND: "unsupported_entity_kind",
|
|
303
|
+
UPDATE_FAILED: "update_failed",
|
|
304
|
+
WRONG_ENTITY_TYPE: "wrong_entity_type",
|
|
305
|
+
} as const;
|
|
306
|
+
|
|
307
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
308
|
+
|
|
258
309
|
export interface DomainErrorShape {
|
|
259
|
-
readonly code:
|
|
310
|
+
readonly code: ErrorCode;
|
|
260
311
|
readonly message: string;
|
|
261
312
|
readonly details?: Record<string, unknown>;
|
|
262
313
|
}
|
|
263
314
|
|
|
264
315
|
export class DomainError extends Error {
|
|
265
|
-
readonly code:
|
|
316
|
+
readonly code: ErrorCode;
|
|
266
317
|
readonly details?: Record<string, unknown>;
|
|
267
318
|
|
|
268
319
|
constructor(input: DomainErrorShape) {
|
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
|
+
}
|