@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.
- package/README.md +8 -11
- package/package.json +2 -2
- package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
- package/src/adapters/sqlite-artifact-store.ts +7 -5
- package/src/adapters/sqlite-discussion-round-store.ts +26 -17
- package/src/adapters/sqlite-gate-runner.ts +1 -1
- package/src/adapters/sqlite-graph-projection-store.ts +14 -10
- package/src/adapters/sqlite-log-store.ts +36 -17
- package/src/adapters/sqlite-note-event-store.ts +20 -16
- package/src/adapters/sqlite-session-identity-store.ts +13 -7
- package/src/adapters/sqlite-task-event-store.ts +29 -21
- package/src/adapters/sqlite-task-focus-store.ts +36 -10
- package/src/adapters/sqlite-task-lease-store.ts +12 -6
- package/src/adapters/sqlite-task-scope-store.ts +25 -14
- package/src/artifact-relationship-view.ts +4 -4
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +794 -354
- package/src/client.ts +6 -3
- package/src/constants.ts +34 -56
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +153 -105
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +18 -5
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +268 -0
- package/src/domain/checklist.ts +20 -17
- package/src/domain/discussion.ts +37 -18
- package/src/domain/gate.ts +7 -7
- package/src/domain/log-entry.ts +1 -1
- package/src/domain/note-event.ts +20 -7
- package/src/domain/task-event.ts +17 -7
- package/src/domain-services.ts +362 -288
- package/src/graph-projection-service.ts +34 -8
- package/src/id-migration.ts +17 -4
- package/src/index.ts +16 -11
- package/src/log-service.ts +6 -5
- package/src/log.ts +19 -0
- package/src/modules/discuss.ts +63 -28
- package/src/modules/docs.ts +74 -17
- package/src/modules/graph-projection.ts +20 -9
- package/src/modules/logs.ts +34 -22
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +93 -32
- package/src/modules/rules.ts +57 -15
- package/src/modules/session-identity.ts +6 -2
- package/src/modules/tasks.ts +142 -67
- package/src/note-service.ts +11 -7
- package/src/ops.ts +134 -69
- package/src/playbook-definition.ts +124 -39
- package/src/playbook-execution.ts +18 -25
- package/src/ports/artifact-scope-store.ts +1 -1
- package/src/ports/note-event-store.ts +6 -4
- package/src/ports/task-event-store.ts +9 -6
- package/src/ports/task-focus-store.ts +17 -4
- package/src/ports/task-lease-store.ts +9 -4
- package/src/ports/task-scope-store.ts +3 -1
- package/src/service.ts +148 -110
- package/src/session-identity-service.ts +10 -2
- package/src/task-context.ts +28 -16
- package/src/task-execution.ts +4 -12
- package/src/task-graph-view.ts +12 -12
- package/src/task-relationship-view.ts +1 -3
- package/src/task-service.ts +168 -73
- package/src/vehicle/artifact-trash-vehicle.ts +26 -14
- package/src/vehicle/artifact-vehicle-shared.ts +32 -13
- package/src/vehicle/docs-vehicle.ts +50 -18
- package/src/vehicle/notes-vehicle.ts +26 -8
- package/src/vehicle/papyrus-vehicle.ts +16 -8
- package/src/vehicle/playbooks-vehicle.ts +88 -19
- package/src/vehicle/rules-vehicle.ts +58 -21
- package/src/vehicle/tasks-vehicle.ts +366 -54
- package/src/version.ts +1 -1
- package/src/workflow-execution.ts +198 -109
- package/src/domain/skill-definition.ts +0 -270
- package/src/modules/skills.ts +0 -158
- 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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 {
|
|
17
|
-
|
|
18
|
-
|
|
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(
|
|
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
|
|
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
|
-
`)
|
|
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
|
|
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
|
|
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
|
-
`)
|
|
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)
|
|
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
|
|
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)
|
|
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
|
|
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
|
-
`)
|
|
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
|
-
|
|
26
|
-
| null;
|
|
27
|
-
|
|
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 =
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
|
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
|
-
`)
|
|
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/
|
|
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) =>
|
|
20
|
-
? { id, label: artifact.title, status: artifact.status }
|
|
21
|
-
|
|
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
|
}
|
package/src/artifact-subtree.ts
CHANGED
|
@@ -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)
|
|
42
|
-
|
|
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
|
-
|
|
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";
|