@danypops/papyrus 0.41.0 → 0.42.1

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.
Files changed (78) hide show
  1. package/README.md +8 -11
  2. package/package.json +2 -2
  3. package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
  4. package/src/adapters/sqlite-artifact-store.ts +7 -5
  5. package/src/adapters/sqlite-discussion-round-store.ts +26 -17
  6. package/src/adapters/sqlite-gate-runner.ts +1 -1
  7. package/src/adapters/sqlite-graph-projection-store.ts +14 -10
  8. package/src/adapters/sqlite-log-store.ts +36 -17
  9. package/src/adapters/sqlite-note-event-store.ts +20 -16
  10. package/src/adapters/sqlite-session-identity-store.ts +13 -7
  11. package/src/adapters/sqlite-task-event-store.ts +29 -21
  12. package/src/adapters/sqlite-task-focus-store.ts +36 -10
  13. package/src/adapters/sqlite-task-lease-store.ts +12 -6
  14. package/src/adapters/sqlite-task-scope-store.ts +25 -14
  15. package/src/artifact-relationship-view.ts +4 -4
  16. package/src/artifact-subtree.ts +4 -2
  17. package/src/authority-registry.ts +2 -1
  18. package/src/cli.ts +794 -354
  19. package/src/client.ts +6 -3
  20. package/src/constants.ts +34 -56
  21. package/src/daemon-state.ts +4 -12
  22. package/src/daemon.ts +31 -9
  23. package/src/db.ts +153 -105
  24. package/src/discussion-service.ts +109 -44
  25. package/src/domain/artifact-event.ts +18 -5
  26. package/src/domain/artifact.ts +3 -1
  27. package/src/domain/blueprint-definition.ts +268 -0
  28. package/src/domain/checklist.ts +20 -17
  29. package/src/domain/discussion.ts +37 -18
  30. package/src/domain/gate.ts +7 -7
  31. package/src/domain/log-entry.ts +1 -1
  32. package/src/domain/note-event.ts +20 -7
  33. package/src/domain/task-event.ts +17 -7
  34. package/src/domain-services.ts +362 -288
  35. package/src/graph-projection-service.ts +34 -8
  36. package/src/id-migration.ts +17 -4
  37. package/src/index.ts +16 -11
  38. package/src/log-service.ts +6 -5
  39. package/src/log.ts +19 -0
  40. package/src/modules/discuss.ts +63 -28
  41. package/src/modules/docs.ts +74 -17
  42. package/src/modules/graph-projection.ts +20 -9
  43. package/src/modules/logs.ts +34 -22
  44. package/src/modules/notes.ts +66 -28
  45. package/src/modules/playbooks.ts +93 -32
  46. package/src/modules/rules.ts +57 -15
  47. package/src/modules/session-identity.ts +6 -2
  48. package/src/modules/tasks.ts +142 -67
  49. package/src/note-service.ts +11 -7
  50. package/src/ops.ts +134 -69
  51. package/src/playbook-definition.ts +124 -39
  52. package/src/playbook-execution.ts +18 -25
  53. package/src/ports/artifact-scope-store.ts +1 -1
  54. package/src/ports/note-event-store.ts +6 -4
  55. package/src/ports/task-event-store.ts +9 -6
  56. package/src/ports/task-focus-store.ts +17 -4
  57. package/src/ports/task-lease-store.ts +9 -4
  58. package/src/ports/task-scope-store.ts +3 -1
  59. package/src/service.ts +148 -110
  60. package/src/session-identity-service.ts +10 -2
  61. package/src/task-context.ts +28 -16
  62. package/src/task-execution.ts +4 -12
  63. package/src/task-graph-view.ts +12 -12
  64. package/src/task-relationship-view.ts +1 -3
  65. package/src/task-service.ts +168 -73
  66. package/src/vehicle/artifact-trash-vehicle.ts +26 -14
  67. package/src/vehicle/artifact-vehicle-shared.ts +32 -13
  68. package/src/vehicle/docs-vehicle.ts +50 -18
  69. package/src/vehicle/notes-vehicle.ts +26 -8
  70. package/src/vehicle/papyrus-vehicle.ts +16 -8
  71. package/src/vehicle/playbooks-vehicle.ts +88 -19
  72. package/src/vehicle/rules-vehicle.ts +58 -21
  73. package/src/vehicle/tasks-vehicle.ts +366 -54
  74. package/src/version.ts +1 -1
  75. package/src/workflow-execution.ts +198 -109
  76. package/src/domain/skill-definition.ts +0 -270
  77. package/src/modules/skills.ts +0 -158
  78. package/src/vehicle/skills-vehicle.ts +0 -194
@@ -7,15 +7,33 @@ export class SQLiteTaskFocusStore implements TaskFocusStore {
7
7
  constructor(private readonly db: Db) {}
8
8
 
9
9
  get(scope?: string): TaskFocusState | undefined {
10
- const row = this.db.prepare("SELECT task_id, status, pause_reason, updated_at FROM task_focus WHERE scope = ?").get(normalizeFocusScope(scope)) as
11
- | { task_id: string; status: TaskFocusStatus; pause_reason: string | null; updated_at: string }
12
- | null;
13
- return row ? { taskId: row.task_id, status: row.status, updatedAt: row.updated_at, ...(row.pause_reason ? { pauseReason: row.pause_reason } : {}) } : undefined;
10
+ const row = this.db
11
+ .prepare("SELECT task_id, status, pause_reason, updated_at FROM task_focus WHERE scope = ?")
12
+ .get(normalizeFocusScope(scope)) as {
13
+ task_id: string;
14
+ status: TaskFocusStatus;
15
+ pause_reason: string | null;
16
+ updated_at: string;
17
+ } | null;
18
+ return row
19
+ ? {
20
+ taskId: row.task_id,
21
+ status: row.status,
22
+ updatedAt: row.updated_at,
23
+ ...(row.pause_reason ? { pauseReason: row.pause_reason } : {}),
24
+ }
25
+ : undefined;
14
26
  }
15
27
 
16
- set(taskId: string, scope?: string): TaskFocusState { return this.write(taskId, "active", scope); }
17
- pause(taskId: string, reason?: string, scope?: string): TaskFocusState { return this.transition(taskId, "active", "paused", reason, scope); }
18
- unpause(taskId: string, scope?: string): TaskFocusState { return this.transition(taskId, "paused", "active", undefined, scope); }
28
+ set(taskId: string, scope?: string): TaskFocusState {
29
+ return this.write(taskId, "active", scope);
30
+ }
31
+ pause(taskId: string, reason?: string, scope?: string): TaskFocusState {
32
+ return this.transition(taskId, "active", "paused", reason, scope);
33
+ }
34
+ unpause(taskId: string, scope?: string): TaskFocusState {
35
+ return this.transition(taskId, "paused", "active", undefined, scope);
36
+ }
19
37
 
20
38
  clear(taskId?: string, scope?: string): void {
21
39
  const key = normalizeFocusScope(scope);
@@ -35,7 +53,13 @@ export class SQLiteTaskFocusStore implements TaskFocusStore {
35
53
  return this.db.prepare("DELETE FROM task_focus WHERE updated_at < ?").run(olderThanIso).changes;
36
54
  }
37
55
 
38
- private transition(taskId: string, expected: TaskFocusStatus, status: TaskFocusStatus, reason: string | undefined, scope: string | undefined): TaskFocusState {
56
+ private transition(
57
+ taskId: string,
58
+ expected: TaskFocusStatus,
59
+ status: TaskFocusStatus,
60
+ reason: string | undefined,
61
+ scope: string | undefined,
62
+ ): TaskFocusState {
39
63
  const current = this.get(scope);
40
64
  if (current?.taskId !== taskId) throw new Error(`task "${taskId}" is not focused`);
41
65
  if (current.status !== expected) throw new Error(`focus is ${current.status}, expected ${expected}`);
@@ -56,11 +80,13 @@ export class SQLiteTaskFocusStore implements TaskFocusStore {
56
80
  const updatedAt = new Date().toISOString();
57
81
  inTransaction(this.db, () => {
58
82
  this.evictOldestBeyondCap(key);
59
- this.db.prepare(`
83
+ this.db
84
+ .prepare(`
60
85
  INSERT INTO task_focus (scope, task_id, status, pause_reason, updated_at)
61
86
  VALUES (?, ?, ?, ?, ?)
62
87
  ON CONFLICT(scope) DO UPDATE SET task_id = excluded.task_id, status = excluded.status, pause_reason = excluded.pause_reason, updated_at = excluded.updated_at
63
- `).run(key, taskId, status, pauseReason ?? null, updatedAt);
88
+ `)
89
+ .run(key, taskId, status, pauseReason ?? null, updatedAt);
64
90
  });
65
91
  return { taskId, status, updatedAt, ...(pauseReason ? { pauseReason } : {}) };
66
92
  }
@@ -1,7 +1,7 @@
1
1
  import { TASK_LEASE_DEFAULT_TTL_MS } from "../constants.ts";
2
2
  import type { Db } from "../db.ts";
3
3
  import { inTransaction } from "../db.ts";
4
- import { isLeaseExpired, validateLeaseNote, validateLeaseOwner, validateLeaseTtlMs, type TaskLease } from "../domain/task-lease.ts";
4
+ import { isLeaseExpired, type TaskLease, validateLeaseNote, validateLeaseOwner, validateLeaseTtlMs } from "../domain/task-lease.ts";
5
5
  import type { TaskLeaseStore } from "../ports/task-lease-store.ts";
6
6
 
7
7
  interface TaskLeaseRow {
@@ -49,12 +49,14 @@ export class SQLiteTaskLeaseStore implements TaskLeaseStore {
49
49
  const token = renewingSameOwner ? current!.token : crypto.randomUUID();
50
50
  const claimedAt = renewingSameOwner ? current!.claimedAt : nowIso;
51
51
  const leaseExpiresAt = new Date(now.getTime() + ttlMs).toISOString();
52
- this.db.prepare(`
52
+ this.db
53
+ .prepare(`
53
54
  INSERT INTO task_leases (task_id, owner, token, claimed_at, lease_expires_at, heartbeat_at, note)
54
55
  VALUES (?, ?, ?, ?, ?, NULL, ?)
55
56
  ON CONFLICT(task_id) DO UPDATE SET owner = excluded.owner, token = excluded.token, claimed_at = excluded.claimed_at,
56
57
  lease_expires_at = excluded.lease_expires_at, heartbeat_at = NULL, note = excluded.note
57
- `).run(taskId, owner, token, claimedAt, leaseExpiresAt, note ?? null);
58
+ `)
59
+ .run(taskId, owner, token, claimedAt, leaseExpiresAt, note ?? null);
58
60
  return fromRow(this.row(taskId)!);
59
61
  });
60
62
  }
@@ -66,9 +68,12 @@ export class SQLiteTaskLeaseStore implements TaskLeaseStore {
66
68
  const existing = this.row(taskId);
67
69
  const current = existing ? fromRow(existing) : undefined;
68
70
  if (!current || isLeaseExpired(current, nowIso)) throw new Error(`task "${taskId}" has no live lease to renew`);
69
- if (current.owner !== owner || current.token !== token) throw new Error(`lease for task "${taskId}" is held by a different owner/token`);
71
+ if (current.owner !== owner || current.token !== token)
72
+ throw new Error(`lease for task "${taskId}" is held by a different owner/token`);
70
73
  const leaseExpiresAt = new Date(Date.now() + ttlMs).toISOString();
71
- this.db.prepare("UPDATE task_leases SET lease_expires_at = ?, heartbeat_at = ? WHERE task_id = ?").run(leaseExpiresAt, nowIso, taskId);
74
+ this.db
75
+ .prepare("UPDATE task_leases SET lease_expires_at = ?, heartbeat_at = ? WHERE task_id = ?")
76
+ .run(leaseExpiresAt, nowIso, taskId);
72
77
  return fromRow(this.row(taskId)!);
73
78
  });
74
79
  }
@@ -79,7 +84,8 @@ export class SQLiteTaskLeaseStore implements TaskLeaseStore {
79
84
  const existing = this.row(taskId);
80
85
  const current = existing ? fromRow(existing) : undefined;
81
86
  if (!current || isLeaseExpired(current, nowIso)) return { released: false };
82
- if (current.owner !== owner || current.token !== token) throw new Error(`lease for task "${taskId}" is held by a different owner/token`);
87
+ if (current.owner !== owner || current.token !== token)
88
+ throw new Error(`lease for task "${taskId}" is held by a different owner/token`);
83
89
  this.db.prepare("DELETE FROM task_leases WHERE task_id = ?").run(taskId);
84
90
  return { released: true };
85
91
  });
@@ -8,36 +8,45 @@ export class SQLiteTaskScopeStore implements TaskScopeStore {
8
8
 
9
9
  assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope {
10
10
  inTransaction(this.db, () => {
11
- this.db.prepare(`
11
+ this.db
12
+ .prepare(`
12
13
  INSERT INTO task_scopes (task_id, project_root, source, assigned_at)
13
14
  VALUES (?, ?, ?, ?)
14
15
  ON CONFLICT(task_id) DO UPDATE SET
15
16
  project_root = excluded.project_root,
16
17
  source = excluded.source,
17
18
  assigned_at = excluded.assigned_at
18
- `).run(taskId, projectRoot ?? null, source, new Date().toISOString());
19
+ `)
20
+ .run(taskId, projectRoot ?? null, source, new Date().toISOString());
19
21
  });
20
22
  return { taskId, ...(projectRoot === undefined ? {} : { projectRoot }), source };
21
23
  }
22
24
 
23
25
  get(taskId: string): TaskProjectScope | undefined {
24
- const row = this.db.prepare("SELECT task_id, project_root, source FROM task_scopes WHERE task_id = ?").get(taskId) as
25
- | { task_id: string; project_root: string | null; source: TaskScopeSource }
26
- | null;
27
- return row ? { taskId: row.task_id, ...(row.project_root === null ? {} : { projectRoot: row.project_root }), source: row.source } : undefined;
26
+ const row = this.db.prepare("SELECT task_id, project_root, source FROM task_scopes WHERE task_id = ?").get(taskId) as {
27
+ task_id: string;
28
+ project_root: string | null;
29
+ source: TaskScopeSource;
30
+ } | null;
31
+ return row
32
+ ? { taskId: row.task_id, ...(row.project_root === null ? {} : { projectRoot: row.project_root }), source: row.source }
33
+ : undefined;
28
34
  }
29
35
 
30
36
  taskIds(projectRoot: string | undefined, limit: number): string[] {
31
- const rows = projectRoot === undefined
32
- ? this.db.prepare("SELECT task_id FROM task_scopes WHERE project_root IS NULL ORDER BY task_id LIMIT ?").all(limit)
33
- : this.db.prepare("SELECT task_id FROM task_scopes WHERE project_root = ? ORDER BY task_id LIMIT ?").all(projectRoot, limit);
37
+ const rows =
38
+ projectRoot === undefined
39
+ ? this.db.prepare("SELECT task_id FROM task_scopes WHERE project_root IS NULL ORDER BY task_id LIMIT ?").all(limit)
40
+ : this.db.prepare("SELECT task_id FROM task_scopes WHERE project_root = ? ORDER BY task_id LIMIT ?").all(projectRoot, limit);
34
41
  return (rows as Array<{ task_id: string }>).map((row) => row.task_id);
35
42
  }
36
43
 
37
44
  view(projectRoot: string): TaskViewPreference {
38
- const row = this.db.prepare("SELECT project_root, mode, root_task_id FROM task_views WHERE project_root = ?").get(projectRoot) as
39
- | { project_root: string; mode: TaskViewMode; root_task_id: string | null }
40
- | null;
45
+ const row = this.db.prepare("SELECT project_root, mode, root_task_id FROM task_views WHERE project_root = ?").get(projectRoot) as {
46
+ project_root: string;
47
+ mode: TaskViewMode;
48
+ root_task_id: string | null;
49
+ } | null;
41
50
  return row
42
51
  ? { projectRoot: row.project_root, mode: row.mode, ...(row.root_task_id === null ? {} : { rootTaskId: row.root_task_id }) }
43
52
  : { projectRoot, mode: "project" };
@@ -45,14 +54,16 @@ export class SQLiteTaskScopeStore implements TaskScopeStore {
45
54
 
46
55
  setView(projectRoot: string, mode: TaskViewMode, rootTaskId?: string): TaskViewPreference {
47
56
  inTransaction(this.db, () => {
48
- this.db.prepare(`
57
+ this.db
58
+ .prepare(`
49
59
  INSERT INTO task_views (project_root, mode, root_task_id, updated_at)
50
60
  VALUES (?, ?, ?, ?)
51
61
  ON CONFLICT(project_root) DO UPDATE SET
52
62
  mode = excluded.mode,
53
63
  root_task_id = excluded.root_task_id,
54
64
  updated_at = excluded.updated_at
55
- `).run(projectRoot, mode, rootTaskId ?? null, new Date().toISOString());
65
+ `)
66
+ .run(projectRoot, mode, rootTaskId ?? null, new Date().toISOString());
56
67
  });
57
68
  return { projectRoot, mode, ...(rootTaskId === undefined ? {} : { rootTaskId }) };
58
69
  }
@@ -4,7 +4,7 @@ import { fallbackLabel } from "./task-relationship-view.ts";
4
4
 
5
5
  /**
6
6
  * The generic-artifact counterpart to projectTaskRelationships: unlike a Task, which already
7
- * has its containing TaskGraph's real titles in memory, a generic Doc/Rule/Skill/Playbook only
7
+ * has its containing TaskGraph's real titles in memory, a generic Doc/Rule/Playbook only
8
8
  * has raw edge id pairs (artifact.show's tree fetch never resolves neighbor titles -- see
9
9
  * ops.ts getArtifact). Reuses the same fallbackLabel heuristic rather than adding a network
10
10
  * round-trip per neighbor, which would turn a rendering concern into a new daemon-adjacent one.
@@ -16,8 +16,8 @@ export function projectArtifactRelationships(artifact: Artifact): DisplayGraph {
16
16
  nodeIds.add(edge.from);
17
17
  nodeIds.add(edge.to);
18
18
  }
19
- const nodes: DisplayGraphNode[] = [...nodeIds].map((id) => id === artifact.id
20
- ? { id, label: artifact.title, status: artifact.status }
21
- : { id, label: fallbackLabel(id) });
19
+ const nodes: DisplayGraphNode[] = [...nodeIds].map((id) =>
20
+ id === artifact.id ? { id, label: artifact.title, status: artifact.status } : { id, label: fallbackLabel(id) },
21
+ );
22
22
  return { direction: "LR", nodes, edges };
23
23
  }
@@ -38,8 +38,10 @@ export function removeArtifactSubtree(
38
38
  const current = queue.shift()!;
39
39
  if (visited.has(current)) continue;
40
40
  visited.add(current);
41
- if (visited.size > ARTIFACT_REMOVE_SUBTREE_MAX_NODES) throw new Error(`remove_subtree exceeds ${ARTIFACT_REMOVE_SUBTREE_MAX_NODES} artifacts`);
42
- const childIds = store.relationships({ artifactIds: [current] })
41
+ if (visited.size > ARTIFACT_REMOVE_SUBTREE_MAX_NODES)
42
+ throw new Error(`remove_subtree exceeds ${ARTIFACT_REMOVE_SUBTREE_MAX_NODES} artifacts`);
43
+ const childIds = store
44
+ .relationships({ artifactIds: [current] })
43
45
  .filter((edge) => edge.from === current && edge.relation === "contains")
44
46
  .map((edge) => edge.to);
45
47
  queue.push(...childIds);
@@ -18,8 +18,9 @@
18
18
  * claims are constructed at the composition root (src/service.ts) where that knowledge
19
19
  * already lives, matching "the core has generic registries" from the decision doc.
20
20
  */
21
- import type { ArtifactEventContext } from "./domain/artifact-event.ts";
21
+
22
22
  import type { Artifact, ArtifactLink } from "./domain/artifact.ts";
23
+ import type { ArtifactEventContext } from "./domain/artifact-event.ts";
23
24
  import type { ArtifactStore } from "./ports/artifact-store.ts";
24
25
 
25
26
  export type ArtifactAction = "create" | "link" | "status" | "update";