@danypops/papyrus 0.29.8 → 0.30.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/package.json +1 -1
- package/src/adapters/sqlite-artifact-store.ts +3 -1
- package/src/cli.ts +2 -2
- package/src/modules/tasks.ts +1 -0
- package/src/ports/artifact-event-reader.ts +8 -0
- package/src/ports/artifact-store.ts +4 -12
- package/src/ports/artifact-trash-store.ts +15 -0
- package/src/service.ts +6 -1
- package/src/task-service.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Db } from "../db.ts";
|
|
2
2
|
import { inTransaction } from "../db.ts";
|
|
3
3
|
import type { AtomicArtifactStore } from "../ports/atomic-artifact-store.ts";
|
|
4
|
+
import type { ArtifactEventReader } from "../ports/artifact-event-reader.ts";
|
|
5
|
+
import type { ArtifactTrashStore } from "../ports/artifact-trash-store.ts";
|
|
4
6
|
import type {
|
|
5
7
|
Artifact,
|
|
6
8
|
ArtifactEdge,
|
|
@@ -30,7 +32,7 @@ import {
|
|
|
30
32
|
updateStatus,
|
|
31
33
|
} from "../ops.ts";
|
|
32
34
|
|
|
33
|
-
export class SQLiteArtifactStore implements AtomicArtifactStore {
|
|
35
|
+
export class SQLiteArtifactStore implements AtomicArtifactStore, ArtifactTrashStore, ArtifactEventReader {
|
|
34
36
|
constructor(private readonly db: Db) {}
|
|
35
37
|
|
|
36
38
|
atomic<T>(operation: () => T): T {
|
package/src/cli.ts
CHANGED
|
@@ -1441,7 +1441,7 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1441
1441
|
case "list": {
|
|
1442
1442
|
if (id) throw new Error("tasks list accepts no positional arguments");
|
|
1443
1443
|
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("tasks.list", {
|
|
1444
|
-
status, text, limit, project_root: projectRoot, scope: listScope, root_task_id: rootTaskId, ...sessionScope,
|
|
1444
|
+
status, text, limit, labels, project_root: projectRoot, scope: listScope, root_task_id: rootTaskId, ...sessionScope,
|
|
1445
1445
|
});
|
|
1446
1446
|
result = rows;
|
|
1447
1447
|
human = rows.length === 0 ? "No tasks found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
@@ -1571,7 +1571,7 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1571
1571
|
const graph = await client.call<Record<string, unknown>, {
|
|
1572
1572
|
nodes: Array<{ dependencyIds: string[]; childIds: string[] }>;
|
|
1573
1573
|
rootIds: string[];
|
|
1574
|
-
}>("tasks.graph", { limit: TASK_EXECUTION_MAX_NODES + 1, project_root: projectRoot, ...sessionScope });
|
|
1574
|
+
}>("tasks.graph", { limit: TASK_EXECUTION_MAX_NODES + 1, labels, project_root: projectRoot, scope: listScope, root_task_id: rootTaskId, ...sessionScope });
|
|
1575
1575
|
result = graph;
|
|
1576
1576
|
const dependencies = graph.nodes.reduce((count, node) => count + node.dependencyIds.length, 0);
|
|
1577
1577
|
const children = graph.nodes.reduce((count, node) => count + node.childIds.length, 0);
|
package/src/modules/tasks.ts
CHANGED
|
@@ -74,6 +74,7 @@ const taskFilter = (input: OperationInput) => ({
|
|
|
74
74
|
scope: optionalString(input, "scope") as TaskViewMode | undefined,
|
|
75
75
|
rootTaskId: optionalString(input, "root_task_id"),
|
|
76
76
|
sessionId: optionalString(input, "session_id") ?? optionalString(input, "sessionId"),
|
|
77
|
+
labels: optionalStringArray(input, "labels"),
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
/**
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArtifactEventPage, ArtifactEventQuery } from "../domain/artifact-event.ts";
|
|
2
|
+
|
|
3
|
+
/** Read access to the generic mutation event log shared by every kind -- split out of
|
|
4
|
+
* ArtifactStore since only service.ts's graph.history operation reads it. */
|
|
5
|
+
export interface ArtifactEventReader {
|
|
6
|
+
/** Bounded query over the generic mutation event log shared by every kind. */
|
|
7
|
+
events(query: ArtifactEventQuery): ArtifactEventPage;
|
|
8
|
+
}
|
|
@@ -8,9 +8,11 @@ import type {
|
|
|
8
8
|
RelationshipQuery,
|
|
9
9
|
UpdateArtifactInput,
|
|
10
10
|
} from "../domain/artifact.ts";
|
|
11
|
-
import type { ArtifactEventContext
|
|
12
|
-
import type { ArtifactTrashRecord } from "../domain/artifact-trash.ts";
|
|
11
|
+
import type { ArtifactEventContext } from "../domain/artifact-event.ts";
|
|
13
12
|
|
|
13
|
+
/** Core CRUD/graph operations every domain-service module needs. Trash lifecycle and event-log
|
|
14
|
+
* reading are separate ports (ArtifactTrashStore, ArtifactEventReader) -- only the composition
|
|
15
|
+
* root (daemon.ts, service.ts) needs those, not every consumer of this store. */
|
|
14
16
|
export interface ArtifactStore {
|
|
15
17
|
create(input: CreateArtifactInput, context?: ArtifactEventContext): Artifact;
|
|
16
18
|
get(id: string, options?: ArtifactGraphOptions): Artifact | null;
|
|
@@ -22,14 +24,4 @@ export interface ArtifactStore {
|
|
|
22
24
|
setExtra(id: string, extra: Record<string, unknown>, context?: ArtifactEventContext): Artifact | null;
|
|
23
25
|
updateContent(id: string, input: UpdateArtifactInput, context?: ArtifactEventContext): Artifact | null;
|
|
24
26
|
relationships(filter?: RelationshipQuery): ArtifactEdge[];
|
|
25
|
-
/** Bounded query over the generic mutation event log shared by every kind. */
|
|
26
|
-
events(query: ArtifactEventQuery): ArtifactEventPage;
|
|
27
|
-
/** See domain/artifact-trash.ts. Moves an artifact to the trash; throws if it does not exist or is the live Task Focus in any scope. */
|
|
28
|
-
trash(id: string, options?: { reason?: string; context?: ArtifactEventContext }): ArtifactTrashRecord;
|
|
29
|
-
/** Idempotent: restoring an artifact that is not currently trashed is a real no-op. */
|
|
30
|
-
restore(id: string, context?: ArtifactEventContext): { restored: boolean };
|
|
31
|
-
trashStatus(id: string): ArtifactTrashRecord | null;
|
|
32
|
-
listTrash(): ArtifactTrashRecord[];
|
|
33
|
-
/** Real, cascading, irreversible deletion of every artifact past its purge deadline; returns how many were purged. */
|
|
34
|
-
purgeDueTrash(): number;
|
|
35
27
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ArtifactEventContext } from "../domain/artifact-event.ts";
|
|
2
|
+
import type { ArtifactTrashRecord } from "../domain/artifact-trash.ts";
|
|
3
|
+
|
|
4
|
+
/** Trash lifecycle for artifacts -- split out of ArtifactStore since only the composition root
|
|
5
|
+
* (daemon.ts, service.ts) needs it; every domain-service module depends on core CRUD/graph only. */
|
|
6
|
+
export interface ArtifactTrashStore {
|
|
7
|
+
/** See domain/artifact-trash.ts. Moves an artifact to the trash; throws if it does not exist or is the live Task Focus in any scope. */
|
|
8
|
+
trash(id: string, options?: { reason?: string; context?: ArtifactEventContext }): ArtifactTrashRecord;
|
|
9
|
+
/** Idempotent: restoring an artifact that is not currently trashed is a real no-op. */
|
|
10
|
+
restore(id: string, context?: ArtifactEventContext): { restored: boolean };
|
|
11
|
+
trashStatus(id: string): ArtifactTrashRecord | null;
|
|
12
|
+
listTrash(): ArtifactTrashRecord[];
|
|
13
|
+
/** Real, cascading, irreversible deletion of every artifact past its purge deadline; returns how many were purged. */
|
|
14
|
+
purgeDueTrash(): number;
|
|
15
|
+
}
|
package/src/service.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { AuthorityRegistry, AuthorizedArtifactWriter, type AuthorityClaim } from
|
|
|
14
14
|
import type { TaskEventContext } from "./domain/task-event.ts";
|
|
15
15
|
import type { TaskViewMode } from "./domain/task-scope.ts";
|
|
16
16
|
import type { ArtifactStore } from "./ports/artifact-store.ts";
|
|
17
|
+
import type { ArtifactEventReader } from "./ports/artifact-event-reader.ts";
|
|
18
|
+
import type { ArtifactTrashStore } from "./ports/artifact-trash-store.ts";
|
|
17
19
|
import type { GateRunner } from "./ports/gate-runner.ts";
|
|
18
20
|
import type { TaskEventStore } from "./ports/task-event-store.ts";
|
|
19
21
|
import type { TaskScopeStore } from "./ports/task-scope-store.ts";
|
|
@@ -187,7 +189,10 @@ export interface PapyrusService {
|
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
function handlers(
|
|
190
|
-
|
|
192
|
+
// The composition root's own handler table -- the one place that genuinely needs trash
|
|
193
|
+
// lifecycle and event-log reading alongside core CRUD/graph, unlike every domain-service
|
|
194
|
+
// module (which only ever depends on the narrower ArtifactStore).
|
|
195
|
+
artifacts: ArtifactStore & ArtifactTrashStore & ArtifactEventReader,
|
|
191
196
|
gates: GateRunner,
|
|
192
197
|
tasks: Tasks,
|
|
193
198
|
notes: Notes,
|
package/src/task-service.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface TaskFilter {
|
|
|
38
38
|
rootTaskId?: string;
|
|
39
39
|
/** Requesting agent session id — scopes Task Focus reads so concurrent agents see only their own Focus. Defaults to a shared "global" scope when omitted. */
|
|
40
40
|
sessionId?: string;
|
|
41
|
+
/** AND semantics: a task must carry every requested label, matching ArtifactStore.query's own labels filter. */
|
|
42
|
+
labels?: string[];
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export type TaskStatus = TaskLifecycleStatus;
|
|
@@ -225,17 +227,19 @@ export class Tasks {
|
|
|
225
227
|
throw new Error(`task list limit must be between 1 and ${TASK_SCOPE_MAX_TASKS + 1}`);
|
|
226
228
|
}
|
|
227
229
|
if (selection.mode === "all") {
|
|
228
|
-
return this.artifacts.query({ kind: "task", excludeSubtype: DISCUSSION_SUBTYPE, status: filter.status, text: filter.text, limit });
|
|
230
|
+
return this.artifacts.query({ kind: "task", excludeSubtype: DISCUSSION_SUBTYPE, status: filter.status, text: filter.text, labels: filter.labels, limit });
|
|
229
231
|
}
|
|
230
232
|
const ids = this.scopes.taskIds(selection.projectRoot, TASK_SCOPE_MAX_TASKS + 1);
|
|
231
233
|
if (ids.length > TASK_SCOPE_MAX_TASKS) throw new Error(`task project scope exceeds ${TASK_SCOPE_MAX_TASKS} tasks`);
|
|
232
234
|
const selectedIds = selection.mode === "graph" ? this.descendantIds(selection.rootTaskId!, ids) : new Set(ids);
|
|
233
235
|
const text = filter.text?.toLowerCase();
|
|
236
|
+
const labels = filter.labels ?? [];
|
|
234
237
|
return [...selectedIds]
|
|
235
238
|
.map((id) => this.artifacts.get(id))
|
|
236
239
|
.filter((task): task is Artifact => task?.kind === "task" && !isDiscussionArtifact(task))
|
|
237
240
|
.filter((task) => filter.status === undefined || task.status === filter.status)
|
|
238
241
|
.filter((task) => text === undefined || task.title.toLowerCase().includes(text) || task.body.toLowerCase().includes(text))
|
|
242
|
+
.filter((task) => labels.every((label) => task.labels.includes(label)))
|
|
239
243
|
.sort((left, right) => right.updated_at.localeCompare(left.updated_at) || left.id.localeCompare(right.id))
|
|
240
244
|
.slice(0, limit);
|
|
241
245
|
}
|