trekoon 0.4.4 → 0.4.6
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 +41 -35
- package/.agents/skills/trekoon/reference/execution.md +75 -60
- package/.agents/skills/trekoon/reference/harness-primitives.md +41 -1
- package/.agents/skills/trekoon/reference/planning.md +37 -67
- package/README.md +28 -0
- package/docs/ai-agents.md +8 -0
- package/docs/commands.md +19 -6
- package/docs/machine-contracts.md +1 -0
- package/docs/quickstart.md +4 -0
- package/package.json +1 -1
- package/src/board/assets/state/actions.js +32 -10
- package/src/board/assets/state/api.js +234 -35
- package/src/board/assets/state/utils.js +18 -0
- package/src/board/routes.ts +27 -14
- package/src/board/snapshot.ts +9 -19
- package/src/board/wal-watcher.ts +637 -74
- package/src/commands/epic.ts +4 -4
- package/src/commands/help.ts +18 -6
- package/src/commands/quickstart.ts +5 -2
- package/src/commands/session.ts +161 -1
- package/src/commands/subtask.ts +2 -2
- package/src/commands/suggest.ts +1 -1
- package/src/commands/task.ts +2 -2
- package/src/domain/mutation-service.ts +83 -9
- package/src/domain/tracker-domain.ts +109 -6
- package/src/io/output.ts +1 -1
- package/src/storage/database.ts +67 -2
- package/src/storage/migrations.ts +149 -2
- package/src/storage/schema.ts +6 -1
- package/src/sync/event-writes.ts +24 -2
- package/.agents/skills/trekoon/reference/execution-with-team.md +0 -161
package/src/storage/database.ts
CHANGED
|
@@ -398,6 +398,57 @@ export function openTrekoonDatabase(
|
|
|
398
398
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
399
399
|
db.exec("PRAGMA foreign_keys = ON;");
|
|
400
400
|
|
|
401
|
+
// Open-time read+write throughput tuning. WAL is required (set above);
|
|
402
|
+
// the rest are layered on top.
|
|
403
|
+
//
|
|
404
|
+
// synchronous: WAL+NORMAL is the documented standard pairing
|
|
405
|
+
// (https://www.sqlite.org/wal.html#performance_considerations) — durability
|
|
406
|
+
// semantics: on a hard kernel/OS crash the last unfsynced transactions can
|
|
407
|
+
// be lost, but the DB file itself never corrupts. Operators who want the
|
|
408
|
+
// pre-tuning behaviour (synchronous=FULL) can set
|
|
409
|
+
// TREKOON_SQLITE_DURABILITY=full at open time.
|
|
410
|
+
const durabilityMode: string = (process.env.TREKOON_SQLITE_DURABILITY ?? "").toLowerCase();
|
|
411
|
+
if (durabilityMode === "full") {
|
|
412
|
+
db.exec("PRAGMA synchronous = FULL;");
|
|
413
|
+
} else {
|
|
414
|
+
db.exec("PRAGMA synchronous = NORMAL;");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// temp_store=MEMORY keeps temp B-tree sorts and intermediate result sets
|
|
418
|
+
// out of the temp file on disk — most relevant for the ORDER BY paths
|
|
419
|
+
// that pre-0013 schemas relied on temp-b-tree sorts for.
|
|
420
|
+
db.exec("PRAGMA temp_store = MEMORY;");
|
|
421
|
+
|
|
422
|
+
// mmap_size: opportunistic memory-mapped reads for the first 256 MiB of
|
|
423
|
+
// the DB file. Reduces read syscall overhead for hot pages. bun:sqlite
|
|
424
|
+
// forwards the PRAGMA to libsqlite3; the connection will silently keep
|
|
425
|
+
// the previous value if the platform does not support mmap.
|
|
426
|
+
db.exec("PRAGMA mmap_size = 268435456;");
|
|
427
|
+
|
|
428
|
+
// cache_size negative value -> KiB, positive value -> pages.
|
|
429
|
+
// Default: 64 MiB. Override with TREKOON_SQLITE_CACHE_MIB (integer MiB).
|
|
430
|
+
// Negative values are rejected. With 16-handle daemon mode the per-process
|
|
431
|
+
// page cache approaches CACHED_DATABASES_CAPACITY × cache_size, so
|
|
432
|
+
// operators should lower TREKOON_SQLITE_CACHE_MIB (e.g. to 16) when memory
|
|
433
|
+
// is constrained.
|
|
434
|
+
const cacheMibRaw: string = (process.env.TREKOON_SQLITE_CACHE_MIB ?? "").trim();
|
|
435
|
+
const cacheMib: number = cacheMibRaw.length > 0 ? Number(cacheMibRaw) : 64;
|
|
436
|
+
if (!Number.isInteger(cacheMib) || cacheMib < 0) {
|
|
437
|
+
throw new DomainError({
|
|
438
|
+
code: "invalid_input",
|
|
439
|
+
message:
|
|
440
|
+
`TREKOON_SQLITE_CACHE_MIB must be a non-negative integer (got ${JSON.stringify(cacheMibRaw)}).`,
|
|
441
|
+
details: { envVar: "TREKOON_SQLITE_CACHE_MIB", provided: cacheMibRaw },
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
db.exec(`PRAGMA cache_size = ${-(cacheMib * 1024)};`);
|
|
445
|
+
|
|
446
|
+
// Trigger a checkpoint roughly every 1000 frames so the WAL file does
|
|
447
|
+
// not grow unbounded under sustained writes. Default is 1000 already,
|
|
448
|
+
// but we pin it explicitly so the value cannot drift if libsqlite3
|
|
449
|
+
// changes its default in a future bump.
|
|
450
|
+
db.exec("PRAGMA wal_autocheckpoint = 1000;");
|
|
451
|
+
|
|
401
452
|
if (options.autoMigrate ?? true) {
|
|
402
453
|
migrateDatabase(db);
|
|
403
454
|
}
|
|
@@ -429,8 +480,22 @@ export function openTrekoonDatabase(
|
|
|
429
480
|
paths,
|
|
430
481
|
diagnostics,
|
|
431
482
|
close(): void {
|
|
432
|
-
|
|
433
|
-
|
|
483
|
+
// Best-effort checkpoint: matches closeCachedHandle's posture. WAL
|
|
484
|
+
// checkpointing is maintenance, not durability — skipping it cannot
|
|
485
|
+
// corrupt the DB. Suppressing errors here lets read-only contexts
|
|
486
|
+
// (read-only filesystem, immutable DB file, sandboxed agents) close
|
|
487
|
+
// cleanly instead of throwing SQLITE_READONLY on the very last
|
|
488
|
+
// syscall before db.close().
|
|
489
|
+
try {
|
|
490
|
+
db.exec("PRAGMA wal_checkpoint(PASSIVE);");
|
|
491
|
+
} catch {
|
|
492
|
+
/* best effort — checkpoint is maintenance, not durability */
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
db.close(false);
|
|
496
|
+
} catch {
|
|
497
|
+
/* best effort — handle may already be closing */
|
|
498
|
+
}
|
|
434
499
|
},
|
|
435
500
|
};
|
|
436
501
|
}
|
|
@@ -4,7 +4,7 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { Database } from "bun:sqlite";
|
|
5
5
|
|
|
6
6
|
import { DomainError } from "../domain/types";
|
|
7
|
-
import {
|
|
7
|
+
import { BASE_SCHEMA_REVISION, BASE_SCHEMA_STATEMENTS } from "./schema";
|
|
8
8
|
|
|
9
9
|
const BACKUP_HINT = "Run 'trekoon migrate backup' to snapshot .trekoon/trekoon.db before any manual recovery.";
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ function migrationDownUnsupported(migrationName: string, version: number): Domai
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const BASE_MIGRATION_VERSION = 1;
|
|
26
|
-
const BASE_MIGRATION_NAME = `0001_base_schema_v${
|
|
26
|
+
const BASE_MIGRATION_NAME = `0001_base_schema_v${BASE_SCHEMA_REVISION}`;
|
|
27
27
|
const LEGACY_BASE_MIGRATION_NAME_PATTERNS: readonly string[] = [
|
|
28
28
|
"0001_base_schema_v*",
|
|
29
29
|
];
|
|
@@ -138,6 +138,116 @@ const SYNC_CONFLICTS_SCOPE_DOWN_STATEMENTS: readonly string[] = [
|
|
|
138
138
|
"DROP INDEX IF EXISTS idx_sync_conflicts_scope_resolution;",
|
|
139
139
|
];
|
|
140
140
|
|
|
141
|
+
const TASK_ORDERED_SCAN_INDEX_UP_STATEMENTS: readonly string[] = [
|
|
142
|
+
"CREATE INDEX IF NOT EXISTS idx_tasks_epic_created ON tasks(epic_id, created_at, id);",
|
|
143
|
+
"CREATE INDEX IF NOT EXISTS idx_subtasks_task_created ON subtasks(task_id, created_at, id);",
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const TASK_ORDERED_SCAN_INDEX_DOWN_STATEMENTS: readonly string[] = [
|
|
147
|
+
"DROP INDEX IF EXISTS idx_subtasks_task_created;",
|
|
148
|
+
"DROP INDEX IF EXISTS idx_tasks_epic_created;",
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const DEPENDENCY_KIND_INDEX_DOWN_STATEMENTS: readonly string[] = [
|
|
152
|
+
"DROP INDEX IF EXISTS uniq_dependencies_edge;",
|
|
153
|
+
"DROP INDEX IF EXISTS idx_dependencies_target;",
|
|
154
|
+
"DROP INDEX IF EXISTS idx_dependencies_source;",
|
|
155
|
+
// v12 dropped the v2 single-column source/target indexes to make room for
|
|
156
|
+
// the composite versions of the same names. Restore the v2 indexes on
|
|
157
|
+
// rollback so v11 lookups continue to benefit from them.
|
|
158
|
+
"CREATE INDEX IF NOT EXISTS idx_dependencies_source ON dependencies(source_id);",
|
|
159
|
+
"CREATE INDEX IF NOT EXISTS idx_dependencies_depends_on ON dependencies(depends_on_id);",
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Migration 0012: dedupe dependency rows that share the full polymorphic
|
|
164
|
+
* edge (source_id, source_kind, depends_on_id, depends_on_kind), then add
|
|
165
|
+
* a source-side composite index, a target-side composite index, and a
|
|
166
|
+
* UNIQUE index on the full 4-column edge. This closes the polymorphic-FK
|
|
167
|
+
* gap left by 0005 (which made (source_id, depends_on_id) unique but did
|
|
168
|
+
* not include kind columns in the UNIQUE constraint).
|
|
169
|
+
*
|
|
170
|
+
* Step 1 is idempotent: the DELETE is a no-op when no duplicates exist.
|
|
171
|
+
* The dedupe keeps the row with the lowest created_at per logical edge.
|
|
172
|
+
* The dropped duplicates are not recoverable — rollback only drops the
|
|
173
|
+
* indexes; the migration is irreversibly destructive of duplicate rows,
|
|
174
|
+
* which is why down() throws migration_down_unsupported.
|
|
175
|
+
*/
|
|
176
|
+
function migrateDependencyKindIndexes(db: Database): void {
|
|
177
|
+
// Defensive: skip the migration entirely if the dependencies table is
|
|
178
|
+
// missing. This guards partial-schema test fixtures (and any future
|
|
179
|
+
// legacy DBs that arrive without the v1 base schema) the same way
|
|
180
|
+
// migrateSyncConflictsScope and migrateBoardIdempotencyState do.
|
|
181
|
+
if (!tableExists(db, "dependencies")) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Step 1: dedupe rows that share the full edge, keeping the lowest
|
|
186
|
+
// created_at survivor (tiebreak on id for determinism). Performed under
|
|
187
|
+
// the same exclusive transaction the migration runner holds.
|
|
188
|
+
//
|
|
189
|
+
// EXISTS guard: skip the expensive window-function DELETE entirely when
|
|
190
|
+
// the table has no duplicate edges. This avoids a full-table scan on
|
|
191
|
+
// clean databases and prevents spurious console.warn output.
|
|
192
|
+
const hasDuplicates = db
|
|
193
|
+
.query(
|
|
194
|
+
`
|
|
195
|
+
SELECT 1 FROM dependencies
|
|
196
|
+
GROUP BY source_id, source_kind, depends_on_id, depends_on_kind
|
|
197
|
+
HAVING count(*) > 1
|
|
198
|
+
LIMIT 1;
|
|
199
|
+
`,
|
|
200
|
+
)
|
|
201
|
+
.get() as Record<string, unknown> | null;
|
|
202
|
+
|
|
203
|
+
if (hasDuplicates !== null) {
|
|
204
|
+
const dedupeResult = db
|
|
205
|
+
.query(
|
|
206
|
+
`
|
|
207
|
+
DELETE FROM dependencies
|
|
208
|
+
WHERE id NOT IN (
|
|
209
|
+
SELECT id FROM (
|
|
210
|
+
SELECT id,
|
|
211
|
+
ROW_NUMBER() OVER (
|
|
212
|
+
PARTITION BY source_id, source_kind, depends_on_id, depends_on_kind
|
|
213
|
+
ORDER BY created_at ASC, id ASC
|
|
214
|
+
) AS rn
|
|
215
|
+
FROM dependencies
|
|
216
|
+
)
|
|
217
|
+
WHERE rn = 1
|
|
218
|
+
);
|
|
219
|
+
`,
|
|
220
|
+
)
|
|
221
|
+
.run();
|
|
222
|
+
|
|
223
|
+
const dedupedCount: number = Number(dedupeResult.changes ?? 0);
|
|
224
|
+
if (dedupedCount > 0) {
|
|
225
|
+
console.warn(
|
|
226
|
+
`[trekoon] migration 0012_dependency_kind_indexes: removed ${dedupedCount} duplicate dependency edge(s) ` +
|
|
227
|
+
"before adding uniq_dependencies_edge UNIQUE index (irreversible).",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Step 2: indexes that accelerate the polymorphic listDependencies /
|
|
233
|
+
// listReverseDependencies / addDependency lookup paths.
|
|
234
|
+
//
|
|
235
|
+
// v2 already created single-column idx_dependencies_source(source_id) and
|
|
236
|
+
// idx_dependencies_depends_on(depends_on_id). Replace both with composite
|
|
237
|
+
// (id, kind) indexes — the source-side keeps the v2 name so the schema
|
|
238
|
+
// surface stays minimal, the target-side gets a new idx_dependencies_target
|
|
239
|
+
// name. We drop the v2 indexes first because CREATE INDEX IF NOT EXISTS
|
|
240
|
+
// would otherwise be a no-op against the existing single-column index of
|
|
241
|
+
// the same name.
|
|
242
|
+
db.exec("DROP INDEX IF EXISTS idx_dependencies_source;");
|
|
243
|
+
db.exec("DROP INDEX IF EXISTS idx_dependencies_depends_on;");
|
|
244
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_dependencies_source ON dependencies(source_id, source_kind);");
|
|
245
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_dependencies_target ON dependencies(depends_on_id, depends_on_kind);");
|
|
246
|
+
db.exec(
|
|
247
|
+
"CREATE UNIQUE INDEX IF NOT EXISTS uniq_dependencies_edge ON dependencies(source_id, source_kind, depends_on_id, depends_on_kind);",
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
141
251
|
function migrateSyncConflictsScope(db: Database): void {
|
|
142
252
|
if (!tableExists(db, "sync_conflicts")) {
|
|
143
253
|
return;
|
|
@@ -454,6 +564,43 @@ const MIGRATIONS: readonly Migration[] = [
|
|
|
454
564
|
}
|
|
455
565
|
},
|
|
456
566
|
},
|
|
567
|
+
{
|
|
568
|
+
version: 12,
|
|
569
|
+
name: "0012_dependency_kind_indexes",
|
|
570
|
+
up(db: Database): void {
|
|
571
|
+
migrateDependencyKindIndexes(db);
|
|
572
|
+
},
|
|
573
|
+
down(db: Database): void {
|
|
574
|
+
// up() deduplicates rows sharing the full polymorphic edge
|
|
575
|
+
// (source_id, source_kind, depends_on_id, depends_on_kind); those
|
|
576
|
+
// row deletions are unrecoverable. down() therefore drops only the
|
|
577
|
+
// new indexes — schema-level recovery — and leaves data restoration
|
|
578
|
+
// to operators (see the migrate help notes for the backup workflow).
|
|
579
|
+
for (const statement of DEPENDENCY_KIND_INDEX_DOWN_STATEMENTS) {
|
|
580
|
+
db.exec(statement);
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
version: 13,
|
|
586
|
+
name: "0013_task_ordered_scan_indexes",
|
|
587
|
+
up(db: Database): void {
|
|
588
|
+
// Guard against partial-schema fixtures the same way 0011/0012 do.
|
|
589
|
+
// If tasks or subtasks are missing the base v1 schema never ran;
|
|
590
|
+
// there is nothing to index against.
|
|
591
|
+
if (!tableExists(db, "tasks") || !tableExists(db, "subtasks")) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
for (const statement of TASK_ORDERED_SCAN_INDEX_UP_STATEMENTS) {
|
|
595
|
+
db.exec(statement);
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
down(db: Database): void {
|
|
599
|
+
for (const statement of TASK_ORDERED_SCAN_INDEX_DOWN_STATEMENTS) {
|
|
600
|
+
db.exec(statement);
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
},
|
|
457
604
|
];
|
|
458
605
|
|
|
459
606
|
function migrationTableExists(db: Database): boolean {
|
package/src/storage/schema.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const BASE_SCHEMA_REVISION = 5;
|
|
2
2
|
|
|
3
3
|
export const BASE_SCHEMA_STATEMENTS: readonly string[] = [
|
|
4
4
|
`PRAGMA foreign_keys = ON;`,
|
|
@@ -142,4 +142,9 @@ export const BASE_SCHEMA_STATEMENTS: readonly string[] = [
|
|
|
142
142
|
`CREATE INDEX IF NOT EXISTS idx_sync_conflicts_scope_resolution ON sync_conflicts(worktree_path, current_branch, resolution);`,
|
|
143
143
|
`CREATE INDEX IF NOT EXISTS idx_board_idempotency_created_at ON board_idempotency_keys(created_at);`,
|
|
144
144
|
`CREATE INDEX IF NOT EXISTS idx_board_idempotency_state_created_at ON board_idempotency_keys(state, created_at);`,
|
|
145
|
+
`CREATE INDEX IF NOT EXISTS idx_dependencies_source ON dependencies(source_id, source_kind);`,
|
|
146
|
+
`CREATE INDEX IF NOT EXISTS idx_dependencies_target ON dependencies(depends_on_id, depends_on_kind);`,
|
|
147
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS uniq_dependencies_edge ON dependencies(source_id, source_kind, depends_on_id, depends_on_kind);`,
|
|
148
|
+
`CREATE INDEX IF NOT EXISTS idx_tasks_epic_created ON tasks(epic_id, created_at, id);`,
|
|
149
|
+
`CREATE INDEX IF NOT EXISTS idx_subtasks_task_created ON subtasks(task_id, created_at, id);`,
|
|
145
150
|
];
|
package/src/sync/event-writes.ts
CHANGED
|
@@ -14,6 +14,16 @@ interface EventRecordInput {
|
|
|
14
14
|
interface EventWriteContext {
|
|
15
15
|
readonly git: ResolvedGitContext;
|
|
16
16
|
nextTimestamp: number;
|
|
17
|
+
/**
|
|
18
|
+
* Transaction-scoped guard: `persistGitContext` upserts the same
|
|
19
|
+
* (worktree_path, branch_name, head_sha) row on every call, so doing it
|
|
20
|
+
* once per appended event row is wasted IO under bulk creates. The first
|
|
21
|
+
* `appendEventWithGitContext` inside the transaction flips this to `true`;
|
|
22
|
+
* subsequent appends within the same write lock skip the upsert. Per-event
|
|
23
|
+
* rows still carry `git_branch` / `git_head` from `context.git`, so the
|
|
24
|
+
* events-table contract is unchanged.
|
|
25
|
+
*/
|
|
26
|
+
gitPersisted: boolean;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
const transactionEventContexts: WeakMap<Database, EventWriteContext> = new WeakMap();
|
|
@@ -79,7 +89,7 @@ export function withTransactionEventContext<T>(db: Database, git: ResolvedGitCon
|
|
|
79
89
|
// subprocess invocations happen here.
|
|
80
90
|
const nextTimestamp: number = nextEventTimestamp(db);
|
|
81
91
|
const resolvedGit: ResolvedGitContext = { ...git, persistedAt: nextTimestamp };
|
|
82
|
-
const context: EventWriteContext = { git: resolvedGit, nextTimestamp };
|
|
92
|
+
const context: EventWriteContext = { git: resolvedGit, nextTimestamp, gitPersisted: false };
|
|
83
93
|
|
|
84
94
|
transactionEventContexts.set(db, context);
|
|
85
95
|
|
|
@@ -101,7 +111,19 @@ export function appendEventWithGitContext(
|
|
|
101
111
|
const git: ResolvedGitContext = context?.git ?? resolveGitContext(cwd, now);
|
|
102
112
|
const eventId: string = randomUUID();
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
// Bulk mutations append many events under one BEGIN IMMEDIATE write lock.
|
|
115
|
+
// persistGitContext upserts the same (worktree_path, branch_name, head_sha)
|
|
116
|
+
// row every call, so we only need it once per transaction. Inside a
|
|
117
|
+
// transaction context the first call flips `gitPersisted` to `true` and
|
|
118
|
+
// subsequent appends skip the redundant upsert. Single-event paths (no
|
|
119
|
+
// active transaction context) still upsert on every call — preserving
|
|
120
|
+
// existing behaviour for direct callers like sync-helpers.
|
|
121
|
+
if (context === undefined) {
|
|
122
|
+
persistGitContext(db, git, now);
|
|
123
|
+
} else if (!context.gitPersisted) {
|
|
124
|
+
persistGitContext(db, git, now);
|
|
125
|
+
context.gitPersisted = true;
|
|
126
|
+
}
|
|
105
127
|
|
|
106
128
|
if (context) {
|
|
107
129
|
context.nextTimestamp += 1;
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# Execution With Agent Teams Reference
|
|
2
|
-
|
|
3
|
-
You are a team lead orchestrator. Use this file only for Claude Code Agent
|
|
4
|
-
Teams when the user explicitly asks for team execution and
|
|
5
|
-
`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=true`. This is a runtime-specific path,
|
|
6
|
-
not the default subagent path for Codex, OpenCode, Pi, or other harnesses.
|
|
7
|
-
|
|
8
|
-
Team execution is complete only when the epic is marked `done`, all remaining
|
|
9
|
-
work is blocked with recorded reasons, or real user input is required.
|
|
10
|
-
|
|
11
|
-
Clarify meaningful ambiguity before starting.
|
|
12
|
-
|
|
13
|
-
## Start
|
|
14
|
-
|
|
15
|
-
Build the graph with the standard execution reference: `task ready`,
|
|
16
|
-
`dep reverse`, lane grouping, and first-wave validation. Then mark the epic in
|
|
17
|
-
progress:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
trekoon --toon epic update <epic-id> --status in_progress
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Create Team And Tasks
|
|
24
|
-
|
|
25
|
-
1. Create the team:
|
|
26
|
-
|
|
27
|
-
```text
|
|
28
|
-
TeamCreate:
|
|
29
|
-
team_name: "<epic-slug>"
|
|
30
|
-
description: "Executing epic <epic-id>: <title>"
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
2. Create one shared team task per lane:
|
|
34
|
-
|
|
35
|
-
```text
|
|
36
|
-
TaskCreate:
|
|
37
|
-
subject: "<lane>: <task-ids/titles>"
|
|
38
|
-
description: |
|
|
39
|
-
Execute these Trekoon tasks IN ORDER unless task descriptions allow
|
|
40
|
-
parallel subtasks:
|
|
41
|
-
- Task <id>: <title>
|
|
42
|
-
|
|
43
|
-
Before each task:
|
|
44
|
-
- trekoon --toon task claim <id> --owner <lane-name>
|
|
45
|
-
- trekoon --toon task update <id> --append "Starting implementation"
|
|
46
|
-
|
|
47
|
-
While working:
|
|
48
|
-
- Complete required subtasks.
|
|
49
|
-
- Append progress notes; do not rewrite task descriptions.
|
|
50
|
-
- Use task done for completion.
|
|
51
|
-
- Use --compact for noisy Trekoon reads.
|
|
52
|
-
|
|
53
|
-
On completion:
|
|
54
|
-
- Append verification evidence.
|
|
55
|
-
- trekoon --toon task done <id>
|
|
56
|
-
- Report unblocked tasks, open subtask warnings, and next candidate via
|
|
57
|
-
SendMessage.
|
|
58
|
-
- Report review result or review gap for non-trivial code changes.
|
|
59
|
-
|
|
60
|
-
If blocked:
|
|
61
|
-
- Append blocker reason, dependency id, and exact failing command/output.
|
|
62
|
-
- trekoon --toon task update <id> --append "Blocked by <reason>" --status blocked
|
|
63
|
-
- Notify team lead via SendMessage.
|
|
64
|
-
|
|
65
|
-
Do not create branches, commits, pushes, or PRs unless the user explicitly
|
|
66
|
-
asked and harness policy allows it.
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Use `blockedBy` via TaskUpdate for team tasks that must run sequentially.
|
|
70
|
-
|
|
71
|
-
3. Spawn one teammate per parallel lane:
|
|
72
|
-
|
|
73
|
-
```text
|
|
74
|
-
Agent:
|
|
75
|
-
name: "developer-1"
|
|
76
|
-
team_name: "<epic-slug>"
|
|
77
|
-
subagent_type: "general-purpose"
|
|
78
|
-
description: "<lane>: <task titles>"
|
|
79
|
-
prompt: |
|
|
80
|
-
You are a developer on team "<epic-slug>".
|
|
81
|
-
Work through your TaskList assignment.
|
|
82
|
-
Claim each Trekoon task before editing:
|
|
83
|
-
trekoon --toon task claim <trekoon-task-id> --owner <your-name>
|
|
84
|
-
|
|
85
|
-
Use task done for completion. Read and report unblocked tasks, warnings,
|
|
86
|
-
and next candidate via SendMessage.
|
|
87
|
-
|
|
88
|
-
Communicate blockers and coordination needs via SendMessage.
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Use 3-5 teammates for most epics. Do not over-parallelize. Use
|
|
92
|
-
`general-purpose` for implementation and `Explore`/`Plan` only for read-only
|
|
93
|
-
research or planning.
|
|
94
|
-
|
|
95
|
-
## Coordinate
|
|
96
|
-
|
|
97
|
-
Your job as team lead:
|
|
98
|
-
|
|
99
|
-
1. Monitor SendMessage updates.
|
|
100
|
-
2. When a teammate reports `unblocked` tasks from `task done`, create new team
|
|
101
|
-
tasks and assign idle teammates.
|
|
102
|
-
3. Help resolve or reassign blockers.
|
|
103
|
-
4. Keep Trekoon owners current:
|
|
104
|
-
```bash
|
|
105
|
-
trekoon --toon task update <task-id> --owner <teammate-name>
|
|
106
|
-
```
|
|
107
|
-
5. Use SendMessage to direct teammates.
|
|
108
|
-
6. Check progress:
|
|
109
|
-
```bash
|
|
110
|
-
trekoon --toon epic progress <epic-id>
|
|
111
|
-
```
|
|
112
|
-
7. When all teammates are blocked, run:
|
|
113
|
-
```bash
|
|
114
|
-
trekoon --toon suggest --epic <epic-id>
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Recovery
|
|
118
|
-
|
|
119
|
-
Use the standard execution recovery rules. Teammates should try to fix failures
|
|
120
|
-
with their local context. If they cannot, they must report exact command/output
|
|
121
|
-
via SendMessage so you can give fix instructions or reassign.
|
|
122
|
-
|
|
123
|
-
For `status_transition_invalid`, inspect current status with:
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
trekoon --toon --compact task show <id>
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
For `dependency_blocked`, inspect the dependency, append a blocker note, then
|
|
130
|
-
continue with a ready candidate from:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
trekoon --toon task ready --epic <epic-id>
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Verify And Close
|
|
137
|
-
|
|
138
|
-
Use the standard execution verification rules: review, automated tests, manual
|
|
139
|
-
checks, DX quality, and Trekoon evidence notes.
|
|
140
|
-
|
|
141
|
-
After all work is verified:
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
trekoon --toon epic progress <epic-id>
|
|
145
|
-
trekoon --toon suggest --epic <epic-id>
|
|
146
|
-
trekoon --toon epic update <epic-id> --status done
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Then send `shutdown_request` to each teammate, delete the team with TeamDelete,
|
|
150
|
-
and return completed tasks, files changed, verification, review, remaining
|
|
151
|
-
blockers, and dependency state.
|
|
152
|
-
|
|
153
|
-
## Team Tools
|
|
154
|
-
|
|
155
|
-
| Purpose | Tool |
|
|
156
|
-
|---|---|
|
|
157
|
-
| Create team | `TeamCreate` |
|
|
158
|
-
| Manage shared tasks | `TaskCreate` / `TaskList` / `TaskUpdate` / `TaskGet` |
|
|
159
|
-
| Spawn teammates | `Agent` with `team_name` |
|
|
160
|
-
| Communicate | `SendMessage` |
|
|
161
|
-
| Clean up | `TeamDelete` |
|