@danypops/papyrus 0.42.0 → 0.42.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/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 +3 -3
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +785 -179
- package/src/client.ts +46 -20
- package/src/constants.ts +15 -4
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +119 -98
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +17 -4
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +26 -31
- 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 +241 -110
- 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 +33 -21
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +88 -29
- 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 +131 -68
- package/src/playbook-definition.ts +56 -17
- package/src/playbook-execution.ts +13 -3
- 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 +143 -89
- 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 +167 -72
- package/src/vehicle/artifact-trash-vehicle.ts +25 -13
- package/src/vehicle/artifact-vehicle-shared.ts +28 -7
- package/src/vehicle/docs-vehicle.ts +48 -16
- package/src/vehicle/notes-vehicle.ts +24 -6
- package/src/vehicle/papyrus-vehicle.ts +14 -3
- package/src/vehicle/playbooks-vehicle.ts +87 -18
- 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 +71 -52
package/src/modules/tasks.ts
CHANGED
|
@@ -26,7 +26,7 @@ import type { ArtifactStore } from "../ports/artifact-store.ts";
|
|
|
26
26
|
import type { SessionIdentity } from "../session-identity-service.ts";
|
|
27
27
|
import { taskContext } from "../task-context.ts";
|
|
28
28
|
import { projectTaskExecution } from "../task-execution.ts";
|
|
29
|
-
import {
|
|
29
|
+
import type { TaskStatus, Tasks } from "../task-service.ts";
|
|
30
30
|
|
|
31
31
|
const MODULE_ID = "tasks";
|
|
32
32
|
|
|
@@ -87,17 +87,51 @@ const taskFilter = (input: OperationInput) => ({
|
|
|
87
87
|
*/
|
|
88
88
|
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
|
|
89
89
|
export const TASKS_OPERATION_NAMES = [
|
|
90
|
-
"tasks.create",
|
|
91
|
-
"tasks.
|
|
92
|
-
"tasks.
|
|
93
|
-
"tasks.
|
|
94
|
-
"tasks.
|
|
95
|
-
"tasks.
|
|
90
|
+
"tasks.create",
|
|
91
|
+
"tasks.update",
|
|
92
|
+
"tasks.list",
|
|
93
|
+
"tasks.graph",
|
|
94
|
+
"tasks.plan",
|
|
95
|
+
"tasks.show",
|
|
96
|
+
"tasks.history",
|
|
97
|
+
"tasks.scope",
|
|
98
|
+
"tasks.set_scope",
|
|
99
|
+
"tasks.assign_project",
|
|
100
|
+
"tasks.active",
|
|
101
|
+
"tasks.focused",
|
|
102
|
+
"tasks.focus",
|
|
103
|
+
"tasks.pause",
|
|
104
|
+
"tasks.unpause",
|
|
105
|
+
"tasks.clear_focus",
|
|
106
|
+
"tasks.start",
|
|
107
|
+
"tasks.submit",
|
|
108
|
+
"tasks.complete",
|
|
109
|
+
"tasks.run_gates",
|
|
110
|
+
"tasks.set_checklist",
|
|
111
|
+
"tasks.set_gates",
|
|
112
|
+
"tasks.context",
|
|
113
|
+
"tasks.reject",
|
|
114
|
+
"tasks.retry",
|
|
115
|
+
"tasks.cancel",
|
|
116
|
+
"tasks.cancel_subtree",
|
|
117
|
+
"tasks.depend",
|
|
118
|
+
"tasks.undepend",
|
|
119
|
+
"tasks.contain",
|
|
120
|
+
"tasks.uncontain",
|
|
121
|
+
"tasks.reap_stale_focus",
|
|
122
|
+
"tasks.claim",
|
|
123
|
+
"tasks.heartbeat_lease",
|
|
124
|
+
"tasks.release_lease",
|
|
125
|
+
"tasks.lease",
|
|
126
|
+
"tasks.reap_stale_leases",
|
|
127
|
+
"tasks.event_feed",
|
|
96
128
|
] as const;
|
|
97
129
|
|
|
98
130
|
export function tasksOperations(tasks: Tasks, artifacts: ArtifactStore, sessionIdentity: SessionIdentity): OperationDefinition[] {
|
|
99
131
|
const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
|
|
100
|
-
name,
|
|
132
|
+
name,
|
|
133
|
+
moduleId: MODULE_ID,
|
|
134
|
+
execute,
|
|
101
135
|
});
|
|
102
136
|
// Enforced only for the operations where session_id is BEHAVIOR-affecting (it selects
|
|
103
137
|
// which Task Focus row is mutated), not for every session_id-carrying operation -- see
|
|
@@ -107,82 +141,123 @@ export function tasksOperations(tasks: Tasks, artifacts: ArtifactStore, sessionI
|
|
|
107
141
|
sessionIdentity.assertAuthorized(eventContext(input).sessionId, optionalString(input, "session_secret"));
|
|
108
142
|
};
|
|
109
143
|
return [
|
|
110
|
-
define("tasks.create", (input: OperationInput) =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
144
|
+
define("tasks.create", (input: OperationInput) =>
|
|
145
|
+
tasks.create(
|
|
146
|
+
{
|
|
147
|
+
title: string(input, "title"),
|
|
148
|
+
body: optionalString(input, "body"),
|
|
149
|
+
status: optionalString(input, "status") as TaskStatus | undefined,
|
|
150
|
+
labels: input.labels as string[] | undefined,
|
|
151
|
+
extra: input.extra as Record<string, unknown> | undefined,
|
|
152
|
+
gates: input.gates as Parameters<Tasks["create"]>[0]["gates"],
|
|
153
|
+
checklist: input.checklist as Checklist | undefined,
|
|
154
|
+
templateId: optionalString(input, "template_id") ?? optionalString(input, "templateId"),
|
|
155
|
+
parentId: optionalString(input, "parent_id") ?? optionalString(input, "parentId"),
|
|
156
|
+
dependsOn: (input.depends_on ?? input.dependsOn) as string[] | undefined,
|
|
157
|
+
projectRoot: string(input, "project_root"),
|
|
158
|
+
projectSource: "cwd",
|
|
159
|
+
},
|
|
160
|
+
eventContext(input),
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
define("tasks.update", (input: OperationInput) =>
|
|
164
|
+
tasks.update(
|
|
165
|
+
string(input, "id"),
|
|
166
|
+
{
|
|
167
|
+
...(input.title !== undefined ? { title: optionalString(input, "title")! } : {}),
|
|
168
|
+
...(input.body !== undefined ? { body: optionalString(input, "body")! } : {}),
|
|
169
|
+
...(input.labels !== undefined ? { labels: optionalStringArray(input, "labels")! } : {}),
|
|
170
|
+
...(input.status !== undefined ? { status: string(input, "status") as "todo" } : {}),
|
|
171
|
+
},
|
|
172
|
+
eventContext(input),
|
|
173
|
+
),
|
|
174
|
+
),
|
|
130
175
|
define("tasks.list", (input: OperationInput) => tasks.list(taskFilter(input))),
|
|
131
176
|
define("tasks.graph", (input: OperationInput) => tasks.graph(taskFilter(input))),
|
|
132
177
|
define("tasks.plan", (input: OperationInput) => projectTaskExecution(tasks.graph(taskFilter(input)))),
|
|
133
178
|
define("tasks.show", (input: OperationInput) => tasks.show(string(input, "id"))),
|
|
134
|
-
define("tasks.history", (input: OperationInput) =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
179
|
+
define("tasks.history", (input: OperationInput) =>
|
|
180
|
+
tasks.history(string(input, "id"), {
|
|
181
|
+
limit: optionalNumber(input, "limit"),
|
|
182
|
+
cursor: optionalNumber(input, "cursor"),
|
|
183
|
+
direction: optionalString(input, "direction") as TaskEventDirection | undefined,
|
|
184
|
+
}),
|
|
185
|
+
),
|
|
139
186
|
define("tasks.scope", (input: OperationInput) => tasks.scopeSelection(string(input, "project_root"))),
|
|
140
|
-
define("tasks.set_scope", (input: OperationInput) =>
|
|
141
|
-
string(input, "project_root"),
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
string(input, "id"),
|
|
147
|
-
string(input, "project_root"),
|
|
148
|
-
eventContext(input),
|
|
149
|
-
)),
|
|
187
|
+
define("tasks.set_scope", (input: OperationInput) =>
|
|
188
|
+
tasks.setView(string(input, "project_root"), string(input, "scope") as TaskViewMode, optionalString(input, "root_task_id")),
|
|
189
|
+
),
|
|
190
|
+
define("tasks.assign_project", (input: OperationInput) =>
|
|
191
|
+
tasks.assignProject(string(input, "id"), string(input, "project_root"), eventContext(input)),
|
|
192
|
+
),
|
|
150
193
|
define("tasks.active", (input: OperationInput) => tasks.active(taskFilter(input))),
|
|
151
194
|
define("tasks.focused", (input: OperationInput) => tasks.focused(taskFilter(input))),
|
|
152
|
-
define("tasks.focus", (input: OperationInput) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
195
|
+
define("tasks.focus", (input: OperationInput) => {
|
|
196
|
+
guardFocusMutation(input);
|
|
197
|
+
return tasks.focus(string(input, "id"), eventContext(input));
|
|
198
|
+
}),
|
|
199
|
+
define("tasks.pause", (input: OperationInput) => {
|
|
200
|
+
guardFocusMutation(input);
|
|
201
|
+
return tasks.pauseFocus(eventContext(input));
|
|
202
|
+
}),
|
|
203
|
+
define("tasks.unpause", (input: OperationInput) => {
|
|
204
|
+
guardFocusMutation(input);
|
|
205
|
+
return tasks.unpauseFocus(eventContext(input));
|
|
206
|
+
}),
|
|
207
|
+
define("tasks.clear_focus", (input: OperationInput) => {
|
|
208
|
+
guardFocusMutation(input);
|
|
209
|
+
return tasks.clearFocus(eventContext(input));
|
|
210
|
+
}),
|
|
156
211
|
define("tasks.reap_stale_focus", () => ({ removed: tasks.reapStaleFocus() })),
|
|
157
212
|
define("tasks.start", (input: OperationInput) => tasks.transition(string(input, "id"), "start", eventContext(input))),
|
|
158
213
|
define("tasks.submit", (input: OperationInput) => tasks.transition(string(input, "id"), "submit", eventContext(input))),
|
|
159
214
|
define("tasks.complete", (input: OperationInput) => tasks.completeAsync(string(input, "id"), eventContext(input))),
|
|
160
215
|
define("tasks.run_gates", (input: OperationInput) => tasks.runGates(string(input, "id"), eventContext(input))),
|
|
161
|
-
define("tasks.set_checklist", (input: OperationInput) => tasks.setChecklist(string(input, "id"), input
|
|
162
|
-
define("tasks.set_gates", (input: OperationInput) =>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
define("tasks.set_checklist", (input: OperationInput) => tasks.setChecklist(string(input, "id"), input.checklist as Checklist)),
|
|
217
|
+
define("tasks.set_gates", (input: OperationInput) =>
|
|
218
|
+
tasks.setGates(string(input, "id"), input.gates as Parameters<Tasks["setGates"]>[1]),
|
|
219
|
+
),
|
|
220
|
+
define("tasks.context", (input: OperationInput) =>
|
|
221
|
+
taskContext(
|
|
222
|
+
artifacts,
|
|
223
|
+
tasks.active(taskFilter(input))?.id,
|
|
224
|
+
new Set(tasks.list(taskFilter(input)).map((task) => task.id)),
|
|
225
|
+
optionalString(input, "verbosity") === "summary" ? "summary" : "full",
|
|
226
|
+
),
|
|
227
|
+
),
|
|
169
228
|
define("tasks.reject", (input: OperationInput) => tasks.transition(string(input, "id"), "reject", eventContext(input))),
|
|
170
229
|
define("tasks.retry", (input: OperationInput) => tasks.transition(string(input, "id"), "retry", eventContext(input))),
|
|
171
230
|
define("tasks.cancel", (input: OperationInput) => tasks.transition(string(input, "id"), "cancel", eventContext(input))),
|
|
172
231
|
define("tasks.cancel_subtree", (input: OperationInput) => tasks.cancelSubtree(string(input, "id"), eventContext(input))),
|
|
173
|
-
define("tasks.depend", (input: OperationInput) =>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
define("tasks.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
define("tasks.
|
|
232
|
+
define("tasks.depend", (input: OperationInput) =>
|
|
233
|
+
tasks.depend(string(input, "id"), string(input, "dependency_id"), eventContext(input)),
|
|
234
|
+
),
|
|
235
|
+
define("tasks.undepend", (input: OperationInput) =>
|
|
236
|
+
tasks.undepend(string(input, "id"), string(input, "dependency_id"), eventContext(input)),
|
|
237
|
+
),
|
|
238
|
+
define("tasks.contain", (input: OperationInput) =>
|
|
239
|
+
tasks.contain(string(input, "parent_id"), string(input, "child_id"), eventContext(input)),
|
|
240
|
+
),
|
|
241
|
+
define("tasks.uncontain", (input: OperationInput) =>
|
|
242
|
+
tasks.uncontain(string(input, "parent_id"), string(input, "child_id"), eventContext(input)),
|
|
243
|
+
),
|
|
244
|
+
define("tasks.claim", (input: OperationInput) =>
|
|
245
|
+
tasks.claimLease(string(input, "id"), string(input, "owner"), optionalNumber(input, "ttl_ms"), optionalString(input, "note")),
|
|
246
|
+
),
|
|
247
|
+
define("tasks.heartbeat_lease", (input: OperationInput) =>
|
|
248
|
+
tasks.heartbeatLease(string(input, "id"), string(input, "owner"), string(input, "token"), optionalNumber(input, "ttl_ms")),
|
|
249
|
+
),
|
|
250
|
+
define("tasks.release_lease", (input: OperationInput) =>
|
|
251
|
+
tasks.releaseLease(string(input, "id"), string(input, "owner"), string(input, "token")),
|
|
252
|
+
),
|
|
180
253
|
define("tasks.lease", (input: OperationInput) => tasks.getLease(string(input, "id")) ?? null),
|
|
181
254
|
define("tasks.reap_stale_leases", () => ({ removed: tasks.reapStaleLeases() })),
|
|
182
|
-
define("tasks.event_feed", (input: OperationInput) =>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
255
|
+
define("tasks.event_feed", (input: OperationInput) =>
|
|
256
|
+
tasks.eventFeed({
|
|
257
|
+
cursor: optionalNumber(input, "cursor"),
|
|
258
|
+
limit: optionalNumber(input, "limit"),
|
|
259
|
+
eventTypes: optionalStringArray(input, "event_types") as TaskEventFeedQuery["eventTypes"],
|
|
260
|
+
}),
|
|
261
|
+
),
|
|
187
262
|
];
|
|
188
263
|
}
|
package/src/note-service.ts
CHANGED
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
} from "./constants.ts";
|
|
10
10
|
import type { Artifact } from "./domain/artifact.ts";
|
|
11
11
|
import type { AppendNoteEvent, NoteEventType, NoteHistoryPage, NoteHistoryQuery } from "./domain/note-event.ts";
|
|
12
|
-
import { InMemoryNoteEventStore, type NoteEventStore } from "./ports/note-event-store.ts";
|
|
13
|
-
import { requireAtomicArtifactStore } from "./ports/atomic-artifact-store.ts";
|
|
14
12
|
import type { ArtifactStore } from "./ports/artifact-store.ts";
|
|
13
|
+
import { requireAtomicArtifactStore } from "./ports/atomic-artifact-store.ts";
|
|
14
|
+
import { InMemoryNoteEventStore, type NoteEventStore } from "./ports/note-event-store.ts";
|
|
15
15
|
|
|
16
16
|
export const NOTE_SUBTYPE = "note";
|
|
17
17
|
export const NOTE_DISPOSITIONS = ["completed", "duplicate", "declined", "superseded"] as const;
|
|
18
|
-
export type NoteDisposition = typeof NOTE_DISPOSITIONS[number];
|
|
18
|
+
export type NoteDisposition = (typeof NOTE_DISPOSITIONS)[number];
|
|
19
19
|
|
|
20
20
|
export interface NoteProvenance {
|
|
21
21
|
actor?: string;
|
|
@@ -60,7 +60,10 @@ function noteTitle(body: string, requested?: string): string {
|
|
|
60
60
|
return firstLine.slice(0, NOTE_TITLE_MAX_CHARACTERS) || "Deferred note";
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function provenance(
|
|
63
|
+
function provenance(
|
|
64
|
+
input: NoteProvenance,
|
|
65
|
+
defaults: { actor: string; source: string },
|
|
66
|
+
): Omit<AppendNoteEvent, "noteId" | "type" | "relatedId" | "disposition"> {
|
|
64
67
|
return {
|
|
65
68
|
actor: optionalBounded(input.actor, "note actor", NOTE_PROVENANCE_MAX_LENGTH) ?? defaults.actor,
|
|
66
69
|
source: optionalBounded(input.source, "note source", NOTE_PROVENANCE_MAX_LENGTH) ?? defaults.source,
|
|
@@ -154,7 +157,8 @@ export class Notes {
|
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
archive(id: string, input: ArchiveNoteInput): Artifact {
|
|
157
|
-
if (!NOTE_DISPOSITIONS.includes(input.disposition))
|
|
160
|
+
if (!NOTE_DISPOSITIONS.includes(input.disposition))
|
|
161
|
+
throw new Error("note disposition must be completed, duplicate, declined, or superseded");
|
|
158
162
|
const atomic = requireAtomicArtifactStore(this.artifacts);
|
|
159
163
|
return atomic.atomic(() => {
|
|
160
164
|
const note = this.requireNote(id);
|
|
@@ -183,12 +187,12 @@ export class Notes {
|
|
|
183
187
|
|
|
184
188
|
private requireNote(id: string): Artifact {
|
|
185
189
|
const artifact = this.artifacts.get(id);
|
|
186
|
-
if (
|
|
190
|
+
if (artifact?.kind !== "doc" || artifact.subtype !== NOTE_SUBTYPE) throw new Error(`note "${id}" not found`);
|
|
187
191
|
return artifact;
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
private requireProject(note: Artifact, projectRoot: string): void {
|
|
191
195
|
const requested = requiredBounded(projectRoot, "project_root", TASK_PROJECT_ROOT_MAX_LENGTH);
|
|
192
|
-
if (note.extra
|
|
196
|
+
if (note.extra.projectRoot !== requested) throw new Error(`note "${note.id}" is outside project scope`);
|
|
193
197
|
}
|
|
194
198
|
}
|