@danypops/papyrus 0.57.1 → 0.58.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 (33) hide show
  1. package/package.json +1 -1
  2. package/src/artifact/artifact-scope-store.ts +13 -17
  3. package/src/artifact/in-memory-artifact-scope-store.ts +8 -10
  4. package/src/artifact/sqlite-artifact-scope-store.ts +8 -8
  5. package/src/docs/docs-service.ts +1 -1
  6. package/src/domain-service-shared.ts +1 -1
  7. package/src/gate/gate-execution.ts +4 -17
  8. package/src/handlers/playbooks.ts +4 -4
  9. package/src/handlers/registry.ts +2 -2
  10. package/src/handlers/task-classifiers.ts +1 -9
  11. package/src/handlers/tasks.ts +1 -1
  12. package/src/index.ts +2 -2
  13. package/src/modules/playbooks.ts +4 -4
  14. package/src/modules/projects.ts +1 -1
  15. package/src/modules/tasks.ts +2 -2
  16. package/src/playbook/playbook-service.ts +1 -1
  17. package/src/playbook/workflow-execution.ts +6 -6
  18. package/src/project-registry/scope-source.ts +14 -0
  19. package/src/rules/rules-service.ts +1 -1
  20. package/src/scope-group/scope-group.ts +4 -11
  21. package/src/service.ts +6 -6
  22. package/src/{task-event → task/event}/sqlite-task-event-store.ts +4 -4
  23. package/src/{task-event → task/event}/task-event-store.ts +6 -2
  24. package/src/{task-event → task/event}/task-event.ts +1 -1
  25. package/src/{task-scope → task/scope}/sqlite-task-scope-store.ts +5 -5
  26. package/src/{task-scope → task/scope}/task-scope-store.ts +9 -11
  27. package/src/{task-scope → task/scope}/task-scope.ts +8 -13
  28. package/src/task/task-edges.ts +6 -13
  29. package/src/task/task-focus-coordinator.ts +4 -10
  30. package/src/task/task-lease-coordinator.ts +3 -6
  31. package/src/task/task-mutation-coordinator.ts +9 -19
  32. package/src/task/task-project-scope.ts +6 -11
  33. package/src/task/task-service.ts +19 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.57.1",
3
+ "version": "0.58.1",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -1,16 +1,12 @@
1
+ import type { ScopeAssignmentSource } from "../project-registry/scope-source.ts";
1
2
  import type { ScopeMemberRef } from "../scope-group/scope-group.ts";
2
- import type { TaskScopeSource } from "../task-scope/task-scope.ts";
3
3
 
4
4
  /**
5
- * Project scoping for Docs/Rules/Playbooks: an artifact is explicitly "none" (hidden -- never
6
- * applicable, never context-injected, regardless of project), explicitly "all" (applies
7
- * everywhere), or bound to a bounded, non-empty set of explicit members -- never inferred from
8
- * an accidentally empty join table, which is why `mode` is its own explicit field rather than
9
- * "members.length === 0 means none/all". A member is either a registered project (from the
10
- * shared ProjectRegistryStore) or a scope group (from ScopeGroupStore, itself a possibly-nested
11
- * collection of projects/groups) -- "explicit scope can include nested scopes". Membership is by
12
- * id, never by root/name, so a registered project's root (or a group's name) can move without a
13
- * best-effort string rewrite across every artifact that references it.
5
+ * Project scoping for Docs/Rules/Playbooks: "none" hides an artifact from context injection
6
+ * entirely, "all" applies it everywhere, "explicit" binds it to a non-empty member set (a
7
+ * project or a possibly-nested scope group). `mode` is its own field rather than inferred from
8
+ * an empty member list, and membership is by id rather than root/name, so a project or group can
9
+ * be renamed/moved without rewriting every artifact that references it.
14
10
  */
15
11
  export type ArtifactScopeMode = "none" | "all" | "explicit";
16
12
 
@@ -19,13 +15,13 @@ export interface ArtifactScope {
19
15
  mode: ArtifactScopeMode;
20
16
  /** Direct membership only (not expanded through nested groups) -- always empty when mode is "none"/"all", always non-empty when mode is "explicit". */
21
17
  members: ScopeMemberRef[];
22
- source: TaskScopeSource;
18
+ source: ScopeAssignmentSource;
23
19
  }
24
20
 
25
21
  export interface LegacyArtifactScope {
26
22
  artifactId: string;
27
23
  projectRoot?: string;
28
- source: TaskScopeSource;
24
+ source: ScopeAssignmentSource;
29
25
  }
30
26
 
31
27
  export interface ArtifactScopeStore {
@@ -34,15 +30,15 @@ export interface ArtifactScopeStore {
34
30
  /** Single-root compatibility view over scope(): an "all"/"none" or unscoped artifact omits projectRoot; an "explicit" mode artifact with exactly one project-type membership (and no group members) resolves it back to that project's current root; anything else (zero, more than one, or any group membership) omits projectRoot, since this shape cannot represent it. */
35
31
  get(artifactId: string): LegacyArtifactScope | undefined;
36
32
  /** Single-root compatibility shim: registers/resolves projectRoot and replaces the artifact's scope with exactly that one project membership, or setAll() when projectRoot is undefined. Every existing caller (rules/docs/playbooks assign_project) keeps working unchanged. */
37
- assign(artifactId: string, projectRoot: string | undefined, source: TaskScopeSource): LegacyArtifactScope;
33
+ assign(artifactId: string, projectRoot: string | undefined, source: ScopeAssignmentSource): LegacyArtifactScope;
38
34
  /** Sets an artifact to explicitly "all" (applies everywhere), clearing any membership. */
39
- setAll(artifactId: string, source: TaskScopeSource): ArtifactScope;
35
+ setAll(artifactId: string, source: ScopeAssignmentSource): ArtifactScope;
40
36
  /** Sets an artifact to explicitly "none" (fully hidden -- never applicable, never context-injected, regardless of project), clearing any membership. */
41
- setNone(artifactId: string, source: TaskScopeSource): ArtifactScope;
37
+ setNone(artifactId: string, source: ScopeAssignmentSource): ArtifactScope;
42
38
  /** Replaces an artifact's entire explicit membership set with exactly these (project and/or group) members -- must be non-empty; use setAll/setNone to clear scoping entirely. */
43
- replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: TaskScopeSource): ArtifactScope;
39
+ replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: ScopeAssignmentSource): ArtifactScope;
44
40
  /** Adds one member to an artifact's explicit membership (idempotent), switching mode to "explicit" if it was "all"/"none". Enforces the bounded maximum membership count. */
45
- addMember(artifactId: string, member: ScopeMemberRef, source: TaskScopeSource): ArtifactScope;
41
+ addMember(artifactId: string, member: ScopeMemberRef, source: ScopeAssignmentSource): ArtifactScope;
46
42
  /** Removes one member from an artifact's explicit membership (idempotent). Rejects removing the last membership while mode is "explicit": a caller must explicitly call setAll/setNone instead of accidentally broadening/narrowing scope by emptying the set. */
47
43
  removeMember(artifactId: string, member: ScopeMemberRef): ArtifactScope;
48
44
  /** Bounded id listing for one project root's EXACT direct membership (or the "all" bucket when projectRoot is undefined) -- an unregistered root always yields an empty list. Deliberately does not expand nested groups (audit semantics, matching this method's own pre-existing "exact membership" contract) -- use appliesToProjectRoot for the applicable/injection query instead. */
@@ -1,16 +1,16 @@
1
1
  import { ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT } from "../constants.ts";
2
2
  import { InMemoryProjectRegistryStore } from "../project-registry/in-memory-project-registry-store.ts";
3
3
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
4
+ import type { ScopeAssignmentSource } from "../project-registry/scope-source.ts";
4
5
  import { InMemoryScopeGroupStore } from "../scope-group/in-memory-scope-group-store.ts";
5
6
  import { type ScopeMemberRef, sameScopeMember } from "../scope-group/scope-group.ts";
6
7
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
7
- import type { TaskScopeSource } from "../task-scope/task-scope.ts";
8
8
  import type { ArtifactScope, ArtifactScopeMode, ArtifactScopeStore, LegacyArtifactScope } from "./artifact-scope-store.ts";
9
9
 
10
10
  interface Row {
11
11
  mode: ArtifactScopeMode;
12
12
  members: ScopeMemberRef[];
13
- source: TaskScopeSource;
13
+ source: ScopeAssignmentSource;
14
14
  }
15
15
 
16
16
  export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
@@ -18,9 +18,7 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
18
18
  private readonly registry: InMemoryProjectRegistryStore;
19
19
  private readonly scopeGroups: ScopeGroupStore;
20
20
 
21
- // Membership is stored by project/group id, never by root/name, so a registry root move (see
22
- // ProjectRegistryStore.registerProject) or a scope group rename needs no rewrite here at all --
23
- // unlike InMemoryTaskScopeStore, this store never subscribes to root-move notifications.
21
+ // Membership is by project/group id, so a registry root move or group rename needs no rewrite here.
24
22
  constructor(registry?: ProjectRegistryStore, scopeGroups?: ScopeGroupStore) {
25
23
  this.registry = registry instanceof InMemoryProjectRegistryStore ? registry : new InMemoryProjectRegistryStore();
26
24
  this.scopeGroups = scopeGroups ?? new InMemoryScopeGroupStore();
@@ -45,7 +43,7 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
45
43
  return { artifactId, ...(projectRoot === undefined ? {} : { projectRoot }), source: row.source };
46
44
  }
47
45
 
48
- assign(artifactId: string, projectRoot: string | undefined, source: TaskScopeSource): LegacyArtifactScope {
46
+ assign(artifactId: string, projectRoot: string | undefined, source: ScopeAssignmentSource): LegacyArtifactScope {
49
47
  if (projectRoot === undefined) {
50
48
  this.setAll(artifactId, source);
51
49
  return { artifactId, source };
@@ -55,19 +53,19 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
55
53
  return { artifactId, projectRoot: project.projectRoot, source };
56
54
  }
57
55
 
58
- setAll(artifactId: string, source: TaskScopeSource): ArtifactScope {
56
+ setAll(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
59
57
  const row: Row = { mode: "all", members: [], source };
60
58
  this.rows.set(artifactId, row);
61
59
  return this.toScope(artifactId, row);
62
60
  }
63
61
 
64
- setNone(artifactId: string, source: TaskScopeSource): ArtifactScope {
62
+ setNone(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
65
63
  const row: Row = { mode: "none", members: [], source };
66
64
  this.rows.set(artifactId, row);
67
65
  return this.toScope(artifactId, row);
68
66
  }
69
67
 
70
- replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: TaskScopeSource): ArtifactScope {
68
+ replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: ScopeAssignmentSource): ArtifactScope {
71
69
  if (members.length === 0) throw new Error("replaceMembers requires at least one member; use setAll/setNone to clear scoping");
72
70
  if (members.length > ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
73
71
  throw new Error(`an artifact cannot have more than ${ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT} scope members`);
@@ -78,7 +76,7 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
78
76
  return this.toScope(artifactId, row);
79
77
  }
80
78
 
81
- addMember(artifactId: string, member: ScopeMemberRef, source: TaskScopeSource): ArtifactScope {
79
+ addMember(artifactId: string, member: ScopeMemberRef, source: ScopeAssignmentSource): ArtifactScope {
82
80
  const existing = this.rows.get(artifactId);
83
81
  const members = dedupeMembers(existing?.mode === "explicit" ? existing.members : []);
84
82
  if (!members.some((candidate) => sameScopeMember(candidate, member)) && members.length >= ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
@@ -1,16 +1,16 @@
1
1
  import { ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT } from "../constants.ts";
2
2
  import type { Db } from "../db.ts";
3
3
  import { inTransaction } from "../db.ts";
4
+ import type { ScopeAssignmentSource } from "../project-registry/scope-source.ts";
4
5
  import { SQLiteProjectRegistryStore } from "../project-registry/sqlite-project-registry-store.ts";
5
6
  import type { ScopeMemberRef } from "../scope-group/scope-group.ts";
6
7
  import { SQLiteScopeGroupStore } from "../scope-group/sqlite-scope-group-store.ts";
7
- import type { TaskScopeSource } from "../task-scope/task-scope.ts";
8
8
  import type { ArtifactScope, ArtifactScopeMode, ArtifactScopeStore, LegacyArtifactScope } from "./artifact-scope-store.ts";
9
9
 
10
10
  interface ScopeRow {
11
11
  artifact_id: string;
12
12
  mode: ArtifactScopeMode;
13
- source: TaskScopeSource;
13
+ source: ScopeAssignmentSource;
14
14
  }
15
15
 
16
16
  interface MemberRow {
@@ -65,7 +65,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
65
65
  return { artifactId, ...(project ? { projectRoot: project.projectRoot } : {}), source: row.source };
66
66
  }
67
67
 
68
- assign(artifactId: string, projectRoot: string | undefined, source: TaskScopeSource): LegacyArtifactScope {
68
+ assign(artifactId: string, projectRoot: string | undefined, source: ScopeAssignmentSource): LegacyArtifactScope {
69
69
  if (projectRoot === undefined) {
70
70
  this.setAll(artifactId, source);
71
71
  return { artifactId, source };
@@ -77,7 +77,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
77
77
  });
78
78
  }
79
79
 
80
- private upsertScopeRow(artifactId: string, mode: ArtifactScopeMode, source: TaskScopeSource): void {
80
+ private upsertScopeRow(artifactId: string, mode: ArtifactScopeMode, source: ScopeAssignmentSource): void {
81
81
  this.db
82
82
  .prepare(`
83
83
  INSERT INTO artifact_scopes (artifact_id, project_root, mode, source, assigned_at)
@@ -91,7 +91,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
91
91
  .run(artifactId, mode, source, new Date().toISOString());
92
92
  }
93
93
 
94
- setAll(artifactId: string, source: TaskScopeSource): ArtifactScope {
94
+ setAll(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
95
95
  return inTransaction(this.db, () => {
96
96
  this.upsertScopeRow(artifactId, "all", source);
97
97
  this.db.prepare("DELETE FROM artifact_scope_members WHERE artifact_id = ?").run(artifactId);
@@ -99,7 +99,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
99
99
  });
100
100
  }
101
101
 
102
- setNone(artifactId: string, source: TaskScopeSource): ArtifactScope {
102
+ setNone(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
103
103
  return inTransaction(this.db, () => {
104
104
  this.upsertScopeRow(artifactId, "none", source);
105
105
  this.db.prepare("DELETE FROM artifact_scope_members WHERE artifact_id = ?").run(artifactId);
@@ -107,7 +107,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
107
107
  });
108
108
  }
109
109
 
110
- replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: TaskScopeSource): ArtifactScope {
110
+ replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: ScopeAssignmentSource): ArtifactScope {
111
111
  if (members.length === 0) throw new Error("replaceMembers requires at least one member; use setAll/setNone to clear scoping");
112
112
  if (members.length > ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
113
113
  throw new Error(`an artifact cannot have more than ${ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT} scope members`);
@@ -127,7 +127,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
127
127
  });
128
128
  }
129
129
 
130
- addMember(artifactId: string, member: ScopeMemberRef, source: TaskScopeSource): ArtifactScope {
130
+ addMember(artifactId: string, member: ScopeMemberRef, source: ScopeAssignmentSource): ArtifactScope {
131
131
  return inTransaction(this.db, () => {
132
132
  const current = this.members(artifactId);
133
133
  const already = current.some((candidate) => candidate.type === member.type && candidate.id === member.id);
@@ -30,8 +30,8 @@ import {
30
30
  } from "../domain-service-shared.ts";
31
31
  import { NOTE_SUBTYPE } from "../note/note-service.ts";
32
32
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
33
+ import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
33
34
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
34
- import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
35
35
 
36
36
  function rejectsNoteTemplate(artifacts: ArtifactStore, templateId: string | undefined, subtype: string | undefined): boolean {
37
37
  if (subtype === NOTE_SUBTYPE) return true;
@@ -19,9 +19,9 @@ import {
19
19
  } from "./constants.ts";
20
20
  import { resolveProjectReference } from "./project-registry/project-registry.ts";
21
21
  import type { ProjectRegistryStore } from "./project-registry/project-registry-store.ts";
22
+ import { normalizeProjectRoot } from "./project-registry/scope-source.ts";
22
23
  import { resolveScopeGroupReference } from "./scope-group/scope-group.ts";
23
24
  import type { ScopeGroupStore } from "./scope-group/scope-group-store.ts";
24
- import { normalizeProjectRoot } from "./task-scope/task-scope.ts";
25
25
 
26
26
  export interface UpdateContentInput {
27
27
  title?: string;
@@ -1,21 +1,8 @@
1
1
  /**
2
- * Gate-execution engine, split out of ops.ts (the artifact-CRUD file) as part of a SOLID-audit-
3
- * driven decomposition (see Doc "Modularity playbook: building-block-shaped TypeScript modules
4
- * for papyrus/pi-papyrus" and the "gate-execution engine" child of "Epic: Modularize papyrus/
5
- * pi-papyrus god-files into building-block modules"). This logic was already unified in a prior
6
- * refactor (sync/async outcome evaluation shared via evaluateProcessGateResult/spawnErrorGateResult)
7
- * but still lived inside the artifact-CRUD file until now.
8
- *
9
- * Only `runGates`/`runGatesAsync` are real public API -- verified via find_references before this
10
- * move, not assumed from a grep hit count: the only two real importers were
11
- * stores/sqlite-gate-runner.ts and test/ops.test.ts (every other `runGates`-named hit in the
12
- * codebase is Tasks.runGates, a same-named but distinct method that calls into this module only
13
- * indirectly, through the GateRunner port). Both were updated to import from this file directly;
14
- * no barrel re-export needed.
15
- *
16
- * Depends on ops.ts's own `getArtifact` (still the right owner of that read -- it's real
17
- * artifact-CRUD, not gate-execution's own concern) -- a one-directional dependency, since ops.ts
18
- * no longer needs to import anything back from here.
2
+ * Gate-execution engine. `Tasks.runGates` is a separate, same-named method that calls into this
3
+ * module only indirectly, through the GateRunner port -- not the same thing as this file's
4
+ * `runGates`/`runGatesAsync`. Depends on ops.ts's `getArtifact` (real artifact-CRUD, not this
5
+ * module's own concern); ops.ts does not depend back on this file.
19
6
  */
20
7
  import { createRequire } from "node:module";
21
8
  import {
@@ -27,9 +27,9 @@ import { listPlaybooks } from "../playbook/playbook-service.ts";
27
27
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
28
28
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
29
29
  import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
30
+ import type { TaskEventSink } from "../task/event/task-event-store.ts";
31
+ import type { TaskScopeAssigner } from "../task/scope/task-scope-store.ts";
30
32
  import type { Tasks } from "../task/task-service.ts";
31
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
32
- import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
33
33
  import {
34
34
  booleanProp,
35
35
  buildWorkflowRunContent,
@@ -53,8 +53,8 @@ const jsonObjectProp = {
53
53
 
54
54
  export interface PlaybooksVehicleDeps {
55
55
  artifacts: ArtifactStore;
56
- events: TaskEventStore;
57
- scopes: TaskScopeStore;
56
+ events: TaskEventSink;
57
+ scopes: TaskScopeAssigner;
58
58
  artifactScopes: ArtifactScopeStore;
59
59
  tasks: Tasks;
60
60
  sessionIdentity: SessionIdentity;
@@ -16,9 +16,9 @@ import type { Notes } from "../note/note-service.ts";
16
16
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
17
17
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
18
18
  import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
19
+ import type { TaskEventStore } from "../task/event/task-event-store.ts";
20
+ import type { TaskScopeStore } from "../task/scope/task-scope-store.ts";
19
21
  import type { Tasks } from "../task/task-service.ts";
20
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
21
- import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
22
22
  import { registerArtifactTrashOperations } from "./artifact-trash.ts";
23
23
  import { registerDiscussVehicleOperations } from "./discuss.ts";
24
24
  import { registerDocsVehicleOperations } from "./docs.ts";
@@ -1,12 +1,4 @@
1
- /**
2
- * Business-rule error classifiers -- turn an ordinary, expected domain rejection into its own
3
- * classified VehicleError instead of vehicle-registry's generic opaque "handler-failed", split out
4
- * of handlers/shared.ts as part of a SOLID-audit-driven decomposition (see Doc "Modularity
5
- * playbook: building-block-shaped TypeScript modules for papyrus/pi-papyrus" and the
6
- * "handlers/shared.ts split" child of "Epic: Modularize papyrus/pi-papyrus god-files into
7
- * building-block modules"). Unlike operation-schema.ts, every function here is specific to this
8
- * package's own domain error types.
9
- */
1
+ /** Business-rule error classifiers -- turn an ordinary, expected domain rejection into its own classified VehicleError instead of vehicle-registry's generic opaque "handler-failed". */
10
2
  import { VehicleError } from "@danypops/vehicle-core";
11
3
  import { PlaybookCompositionError } from "../playbook/playbook-definition.ts";
12
4
  import { InvalidSessionSecretError } from "../session-identity/session-identity-service.ts";
@@ -28,6 +28,7 @@ import { tasksOperations } from "../modules/tasks.ts";
28
28
  import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
29
29
  import { PROOF_TYPES } from "../task/checklist.ts";
30
30
  import { TaskMutationIdempotencyConflictError, TaskMutationPendingError } from "../task/mutation-request/task-mutation-request-store.ts";
31
+ import type { TaskViewMode } from "../task/scope/task-scope.ts";
31
32
  import type { TaskExecutionPlan } from "../task/task-execution.ts";
32
33
  import {
33
34
  type TaskCompletion,
@@ -37,7 +38,6 @@ import {
37
38
  TaskProjectNotFoundError,
38
39
  type Tasks,
39
40
  } from "../task/task-service.ts";
40
- import type { TaskViewMode } from "../task-scope/task-scope.ts";
41
41
  import {
42
42
  booleanProp,
43
43
  classifySessionAuthorization,
package/src/index.ts CHANGED
@@ -28,7 +28,9 @@ export type { PlaybookInvocationResult, PlaybookMissingArguments } from "./playb
28
28
  export type { WorkflowRunResult } from "./playbook/workflow-execution.ts";
29
29
  export type { OperationName, SchemaState } from "./service.ts";
30
30
  export { checklistEntries, PROOF_TYPES, type ProofReference } from "./task/checklist.ts";
31
+ export type { TaskEvent, TaskHistoryPage } from "./task/event/task-event.ts";
31
32
  export type { TaskLease, TaskLeaseView } from "./task/lease/task-lease.ts";
33
+ export type { TaskViewSelection } from "./task/scope/task-scope.ts";
32
34
  export { taskContext } from "./task/task-context.ts";
33
35
  export { projectTaskExecution, type TaskExecutionPlan, type TaskExecutionState } from "./task/task-execution.ts";
34
36
  export { projectTaskGraph, type TaskGraphView } from "./task/task-graph-view.ts";
@@ -42,5 +44,3 @@ export type {
42
44
  TaskStatus,
43
45
  } from "./task/task-service.ts";
44
46
  export { TaskInvalidTransitionError, TaskMutationReceiptNotFoundError } from "./task/task-service.ts";
45
- export type { TaskEvent, TaskHistoryPage } from "./task-event/task-event.ts";
46
- export type { TaskViewSelection } from "./task-scope/task-scope.ts";
@@ -40,9 +40,9 @@ import {
40
40
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
41
41
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
42
42
  import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
43
+ import type { TaskEventSink } from "../task/event/task-event-store.ts";
44
+ import type { TaskScopeAssigner } from "../task/scope/task-scope-store.ts";
43
45
  import type { Tasks } from "../task/task-service.ts";
44
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
45
- import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
46
46
  import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
47
47
 
48
48
  const MODULE_ID = "playbooks";
@@ -105,8 +105,8 @@ export const PLAYBOOKS_OPERATION_NAMES = [
105
105
 
106
106
  export interface PlaybooksModuleDeps {
107
107
  artifacts: ArtifactStore;
108
- events: TaskEventStore;
109
- scopes: TaskScopeStore;
108
+ events: TaskEventSink;
109
+ scopes: TaskScopeAssigner;
110
110
  /** Docs/Rules/Skills/Playbooks project scoping (distinct from `scopes`, which is Task-run project scoping for playbooks.invoke's materialized tasks). */
111
111
  artifactScopes: ArtifactScopeStore;
112
112
  /** Used for exactly one thing: focusing the entry task after a successful invoke -- the one safety-checked Tasks operation this module needs, not bulk graph construction (that goes straight through artifacts/events/scopes in playbook/playbook-execution.ts, mirroring playbook/workflow-execution.ts). */
@@ -11,7 +11,7 @@ import { TASK_PROJECT_LIST_MAX_RESULTS } from "../constants.ts";
11
11
  import type { OperationDefinition } from "../module-registry.ts";
12
12
  import { assertRegisterProjectInputBounds, resolveProjectReference } from "../project-registry/project-registry.ts";
13
13
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
14
- import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
14
+ import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
15
15
  import { type OperationInput, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
16
16
 
17
17
  const MODULE_ID = "projects";
@@ -24,11 +24,11 @@ import type { ArtifactStore } from "../artifact/artifact-store.ts";
24
24
  import type { OperationDefinition } from "../module-registry.ts";
25
25
  import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
26
26
  import type { Checklist } from "../task/checklist.ts";
27
+ import type { TaskEventContext, TaskEventDirection, TaskEventFeedQuery } from "../task/event/task-event.ts";
28
+ import type { TaskViewMode } from "../task/scope/task-scope.ts";
27
29
  import { taskContext } from "../task/task-context.ts";
28
30
  import { projectTaskExecution } from "../task/task-execution.ts";
29
31
  import type { TaskMutationRequestContext, TaskStatus, Tasks } from "../task/task-service.ts";
30
- import type { TaskEventContext, TaskEventDirection, TaskEventFeedQuery } from "../task-event/task-event.ts";
31
- import type { TaskViewMode } from "../task-scope/task-scope.ts";
32
32
  import { type OperationInput, optionalBoolean, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
33
33
 
34
34
  const MODULE_ID = "tasks";
@@ -57,8 +57,8 @@ import {
57
57
  type UpdateContentInput,
58
58
  } from "../domain-service-shared.ts";
59
59
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
60
+ import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
60
61
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
61
- import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
62
62
  import {
63
63
  BLUEPRINT_INPUT_TYPES,
64
64
  type BlueprintArgumentValue,
@@ -9,13 +9,13 @@ import {
9
9
  SKILL_WORKFLOW_MAX_NESTING_DEPTH,
10
10
  TASK_EXECUTION_MAX_EDGES,
11
11
  } from "../constants.ts";
12
+ import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
12
13
  import { validateChecklist } from "../task/checklist.ts";
14
+ import type { TaskEventContext } from "../task/event/task-event.ts";
15
+ import type { TaskEventSink } from "../task/event/task-event-store.ts";
16
+ import type { TaskScopeAssigner } from "../task/scope/task-scope-store.ts";
13
17
  import { projectTaskExecution, TaskExecutionBoundExceededError, type TaskExecutionPlan } from "../task/task-execution.ts";
14
18
  import type { TaskGraph, TaskNode, TaskStatus } from "../task/task-service.ts";
15
- import type { TaskEventContext } from "../task-event/task-event.ts";
16
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
17
- import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
18
- import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
19
19
  import {
20
20
  type BlueprintArgumentValue,
21
21
  type BlueprintDefinition,
@@ -167,8 +167,8 @@ function executionGraph(tasks: Artifact[], definition: BlueprintDefinition, ids:
167
167
  * other project's context.
168
168
  */
169
169
  export type WorkflowRunHistory = {
170
- events: TaskEventStore;
171
- scopes: TaskScopeStore;
170
+ events: TaskEventSink;
171
+ scopes: TaskScopeAssigner;
172
172
  artifactScopes?: ArtifactScopeStore;
173
173
  projectRoot?: string;
174
174
  context?: TaskEventContext;
@@ -0,0 +1,14 @@
1
+ import { basename, isAbsolute, normalize } from "node:path";
2
+ import { TASK_PROJECT_ROOT_MAX_LENGTH } from "../constants.ts";
3
+
4
+ /** How a project root got attached to a scoped artifact -- shared across Tasks/Docs/Rules/Playbooks, same category as Project in project-registry.ts. */
5
+ export type ScopeAssignmentSource = "cwd" | "explicit" | "unscoped";
6
+
7
+ export function normalizeProjectRoot(value: string): string {
8
+ if (!isAbsolute(value)) throw new Error("project_root must be an absolute path");
9
+ const normalized = normalize(value);
10
+ if (normalized.length > TASK_PROJECT_ROOT_MAX_LENGTH) {
11
+ throw new Error(`project_root cannot exceed ${TASK_PROJECT_ROOT_MAX_LENGTH} characters`);
12
+ }
13
+ return normalized;
14
+ }
@@ -31,8 +31,8 @@ import {
31
31
  type UpdateContentInput,
32
32
  } from "../domain-service-shared.ts";
33
33
  import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
34
+ import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
34
35
  import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
35
- import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
36
36
 
37
37
  export interface CreateRuleInput {
38
38
  title: string;
@@ -1,17 +1,10 @@
1
1
  import { SCOPE_GROUP_ALIAS_MAX_COUNT, SCOPE_GROUP_NAME_MAX_LENGTH } from "../constants.ts";
2
2
 
3
3
  /**
4
- * A named, reusable collection of scope members (registered projects and/or other scope groups)
5
- * -- the "explicit scope can include nested scopes" primitive. Lives alongside the project
6
- * registry (same shape: id/name/aliases/createdAt/updatedAt) rather than as an Artifact, since
7
- * it is bookkeeping infrastructure for scoping, not itself a piece of knowledge or work.
8
- *
9
- * Deliberately explicit/opt-in membership, not filesystem-path-derived nesting: a registered
10
- * project whose root happens to be a subdirectory of another registered project's root is NOT
11
- * automatically its child (a real false-positive confirmed live: alignment-lector is registered
12
- * under lector/packages/alignment-lector, a subdirectory of lector's own root, but is Lector's
13
- * adapter for a *different* project, not part of Lector's own scope). A group's membership is
14
- * only ever what was deliberately added to it.
4
+ * A named, reusable collection of scope members (registered projects and/or other scope groups).
5
+ * Membership is explicit/opt-in, never path-derived: a project registered under another
6
+ * project's root directory (e.g. a vendored adapter package) is not automatically that parent's
7
+ * child -- only what's deliberately added counts.
15
8
  */
16
9
  export interface ScopeGroup {
17
10
  id: string;
package/src/service.ts CHANGED
@@ -44,16 +44,16 @@ import { SQLiteScopeGroupStore } from "./scope-group/sqlite-scope-group-store.ts
44
44
  import { InvalidSessionSecretError, SessionIdentity } from "./session-identity/session-identity-service.ts";
45
45
  import { SQLiteSessionIdentityStore } from "./session-identity/sqlite-session-identity-store.ts";
46
46
  import { SQLiteTaskCreateRequestStore } from "./task/create-request/sqlite-task-create-request-store.ts";
47
+ import { SQLiteTaskEventStore } from "./task/event/sqlite-task-event-store.ts";
48
+ import type { TaskEventContext } from "./task/event/task-event.ts";
49
+ import type { TaskEventStore } from "./task/event/task-event-store.ts";
47
50
  import { SQLiteTaskFocusStore } from "./task/focus/sqlite-task-focus-store.ts";
48
51
  import { SQLiteTaskLeaseStore } from "./task/lease/sqlite-task-lease-store.ts";
49
52
  import { SQLiteTaskMutationRequestStore } from "./task/mutation-request/sqlite-task-mutation-request-store.ts";
53
+ import { SQLiteTaskScopeStore } from "./task/scope/sqlite-task-scope-store.ts";
54
+ import type { TaskViewMode } from "./task/scope/task-scope.ts";
55
+ import type { TaskScopeStore } from "./task/scope/task-scope-store.ts";
50
56
  import { type TaskStatus, Tasks } from "./task/task-service.ts";
51
- import { SQLiteTaskEventStore } from "./task-event/sqlite-task-event-store.ts";
52
- import type { TaskEventContext } from "./task-event/task-event.ts";
53
- import type { TaskEventStore } from "./task-event/task-event-store.ts";
54
- import { SQLiteTaskScopeStore } from "./task-scope/sqlite-task-scope-store.ts";
55
- import type { TaskViewMode } from "./task-scope/task-scope.ts";
56
- import type { TaskScopeStore } from "./task-scope/task-scope-store.ts";
57
57
  import { VERSION } from "./version.ts";
58
58
 
59
59
  /**
@@ -1,5 +1,5 @@
1
- import type { Db } from "../db.ts";
2
- import { inTransaction } from "../db.ts";
1
+ import type { Db } from "../../db.ts";
2
+ import { inTransaction } from "../../db.ts";
3
3
  import {
4
4
  type AppendTaskEvent,
5
5
  normalizeTaskEventFeedQuery,
@@ -13,8 +13,8 @@ import {
13
13
  type TaskHistoryQuery,
14
14
  type TaskLifecycleStatus,
15
15
  validateTaskEvent,
16
- } from "../task-event/task-event.ts";
17
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
16
+ } from "./task-event.ts";
17
+ import type { TaskEventStore } from "./task-event-store.ts";
18
18
 
19
19
  interface TaskEventRow {
20
20
  id: number;
@@ -8,11 +8,15 @@ import {
8
8
  type TaskHistoryPage,
9
9
  type TaskHistoryQuery,
10
10
  validateTaskEvent,
11
- } from "../task-event/task-event.ts";
11
+ } from "./task-event.ts";
12
12
 
13
- export interface TaskEventStore {
13
+ /** What a caller outside Task needs to append events -- history/feed stay on TaskEventStore since only Task's own readers use them. */
14
+ export interface TaskEventSink {
14
15
  atomic<T>(operation: () => T): T;
15
16
  append(event: AppendTaskEvent): TaskEvent;
17
+ }
18
+
19
+ export interface TaskEventStore extends TaskEventSink {
16
20
  history(taskId: string, query?: TaskHistoryQuery): TaskHistoryPage;
17
21
  /** Bounded, sequenced, cross-task replay feed -- see TaskEventFeedQuery. */
18
22
  feed(query?: TaskEventFeedQuery): TaskEventFeedPage;
@@ -6,7 +6,7 @@ import {
6
6
  TASK_EVENT_REASON_MAX_LENGTH,
7
7
  TASK_HISTORY_DEFAULT_LIMIT,
8
8
  TASK_HISTORY_MAX_LIMIT,
9
- } from "../constants.ts";
9
+ } from "../../constants.ts";
10
10
  export type TaskLifecycleStatus = "todo" | "in-progress" | "review" | "rejected" | "done" | "canceled";
11
11
 
12
12
  export const TASK_EVENT_TYPES = [
@@ -1,6 +1,6 @@
1
- import type { Db } from "../db.ts";
2
- import { inTransaction } from "../db.ts";
3
- import { SQLiteProjectRegistryStore } from "../project-registry/sqlite-project-registry-store.ts";
1
+ import type { Db } from "../../db.ts";
2
+ import { inTransaction } from "../../db.ts";
3
+ import { SQLiteProjectRegistryStore } from "../../project-registry/sqlite-project-registry-store.ts";
4
4
  import type {
5
5
  RegisterTaskProjectInput,
6
6
  TaskProject,
@@ -8,8 +8,8 @@ import type {
8
8
  TaskScopeSource,
9
9
  TaskViewMode,
10
10
  TaskViewPreference,
11
- } from "../task-scope/task-scope.ts";
12
- import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
11
+ } from "./task-scope.ts";
12
+ import type { TaskScopeStore } from "./task-scope-store.ts";
13
13
 
14
14
  /** Rewrites every task_scopes/task_views row pinned to a project root that just moved -- called inside the same registerProject() transaction, so a rename/move is atomic with the rewrite. */
15
15
  function rewriteTaskRowsForMovedRoot(db: Db, previousRoot: string, nextRoot: string): void {
@@ -1,5 +1,5 @@
1
- import { InMemoryProjectRegistryStore } from "../project-registry/in-memory-project-registry-store.ts";
2
- import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
1
+ import { InMemoryProjectRegistryStore } from "../../project-registry/in-memory-project-registry-store.ts";
2
+ import type { ProjectRegistryStore } from "../../project-registry/project-registry-store.ts";
3
3
  import type {
4
4
  RegisterTaskProjectInput,
5
5
  TaskProject,
@@ -7,10 +7,14 @@ import type {
7
7
  TaskScopeSource,
8
8
  TaskViewMode,
9
9
  TaskViewPreference,
10
- } from "../task-scope/task-scope.ts";
10
+ } from "./task-scope.ts";
11
11
 
12
- export interface TaskScopeStore {
12
+ /** What a caller outside Task needs to assign a task's project scope -- the rest of TaskScopeStore (catalog, views, listing) stays Task-only. */
13
+ export interface TaskScopeAssigner {
13
14
  assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope;
15
+ }
16
+
17
+ export interface TaskScopeStore extends TaskScopeAssigner {
14
18
  get(taskId: string): TaskProjectScope | undefined;
15
19
  taskIds(projectRoot: string | undefined, limit: number): string[];
16
20
  view(projectRoot: string): TaskViewPreference;
@@ -20,13 +24,7 @@ export interface TaskScopeStore {
20
24
  registerProject(input: RegisterTaskProjectInput): TaskProject;
21
25
  }
22
26
 
23
- /**
24
- * Task's own scope/view bookkeeping, composing a ProjectRegistryStore for the project-catalog
25
- * methods (projects/matchingProjects/registerProject) rather than implementing that bookkeeping
26
- * itself -- see project-registry-store.ts. Pass a shared registry instance to keep Task and a
27
- * non-Task ArtifactScopeStore resolving against the exact same project identities; omitted, this
28
- * store gets its own private one (matching this class's own behavior before the extraction).
29
- */
27
+ /** Pass a shared registry to keep Task and ArtifactScopeStore resolving the same project identities; omitted, this gets its own private one. */
30
28
  export class InMemoryTaskScopeStore implements TaskScopeStore {
31
29
  private readonly scopes = new Map<string, TaskProjectScope>();
32
30
  private readonly views = new Map<string, TaskViewPreference>();
@@ -1,9 +1,13 @@
1
- import { basename, isAbsolute, normalize } from "node:path";
2
- import { TASK_PROJECT_ROOT_MAX_LENGTH } from "../constants.ts";
3
- import type { Project, RegisterProjectInput } from "../project-registry/project-registry.ts";
1
+ import { basename } from "node:path";
2
+ import type { Project, RegisterProjectInput } from "../../project-registry/project-registry.ts";
3
+ import type { ScopeAssignmentSource } from "../../project-registry/scope-source.ts";
4
+
5
+ export { normalizeProjectRoot } from "../../project-registry/scope-source.ts";
4
6
 
5
7
  export type TaskViewMode = "project" | "graph" | "all";
6
- export type TaskScopeSource = "cwd" | "explicit" | "unscoped";
8
+
9
+ /** Alias of ScopeAssignmentSource for Task's own call sites -- see project-registry/scope-source.ts. */
10
+ export type TaskScopeSource = ScopeAssignmentSource;
7
11
 
8
12
  export interface TaskProjectScope {
9
13
  taskId: string;
@@ -29,15 +33,6 @@ export interface TaskViewSelection {
29
33
  rootTaskId?: string;
30
34
  }
31
35
 
32
- export function normalizeProjectRoot(value: string): string {
33
- if (!isAbsolute(value)) throw new Error("project_root must be an absolute path");
34
- const normalized = normalize(value);
35
- if (normalized.length > TASK_PROJECT_ROOT_MAX_LENGTH) {
36
- throw new Error(`project_root cannot exceed ${TASK_PROJECT_ROOT_MAX_LENGTH} characters`);
37
- }
38
- return normalized;
39
- }
40
-
41
36
  export function taskScopeLabel(mode: TaskViewMode, projectRoot?: string, rootTitle?: string): string {
42
37
  if (mode === "all") return "All projects";
43
38
  const project = projectRoot ? basename(projectRoot) || projectRoot : "Unscoped";
@@ -1,8 +1,8 @@
1
1
  import type { Artifact, ArtifactEdge } from "../artifact/artifact.ts";
2
2
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
3
3
  import { TASK_EXECUTION_MAX_DEGREE } from "../constants.ts";
4
- import type { AppendTaskEvent, TaskEventContext } from "../task-event/task-event.ts";
5
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
4
+ import type { AppendTaskEvent, TaskEventContext } from "./event/task-event.ts";
5
+ import type { TaskEventStore } from "./event/task-event-store.ts";
6
6
  import { assertDependencyEdgeAllowed, TaskExecutionBoundExceededError } from "./task-execution.ts";
7
7
  // Type-only import: erased entirely at compile time, so this does not create a real runtime
8
8
  // circular dependency even though task-service.ts also imports TaskEdges (a real value) from
@@ -10,17 +10,10 @@ import { assertDependencyEdgeAllowed, TaskExecutionBoundExceededError } from "./
10
10
  import type { TaskGraph } from "./task-service.ts";
11
11
 
12
12
  /**
13
- * Task dependency/containment edge mutations (depend/undepend/contain/uncontain), split out of
14
- * the Tasks god class as part of a SOLID-audit-driven decomposition (see task b51419a0 and the
15
- * "TaskEdges" child of "Epic: Modularize papyrus/pi-papyrus god-files into building-block
16
- * modules"), mirroring the TaskLeaseCoordinator/TaskMutationCoordinator/TaskFocusCoordinator/
17
- * TaskProjectScope precedent in this same directory.
18
- *
19
- * Unlike those simpler extractions, this one only owns the mutation side of edges -- it reads the
20
- * graph/relationships it needs through injected callbacks (dependencyCheckGraph/dependencyIds/
21
- * relationships) rather than duplicating that graph-construction machinery, since those reads are
22
- * shared with concerns that stay on Tasks (list/graph/buildGraph, progress propagation, blockage
23
- * checks in transition/complete).
13
+ * Task dependency/containment edge mutations (depend/undepend/contain/uncontain). Reads the
14
+ * graph/relationships it needs through injected callbacks rather than owning graph construction,
15
+ * since those reads are shared with concerns that stay on Tasks (list/graph, progress propagation,
16
+ * blockage checks).
24
17
  */
25
18
  export class TaskEdges {
26
19
  constructor(
@@ -1,8 +1,8 @@
1
1
  import type { Artifact } from "../artifact/artifact.ts";
2
2
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
3
3
  import { TASK_FOCUS_STALE_AFTER_MS } from "../constants.ts";
4
- import { type AppendTaskEvent, type TaskEventContext, validateEventContext } from "../task-event/task-event.ts";
5
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
4
+ import { type AppendTaskEvent, type TaskEventContext, validateEventContext } from "./event/task-event.ts";
5
+ import type { TaskEventStore } from "./event/task-event-store.ts";
6
6
  import type { TaskFocusStatus, TaskFocusStore } from "./focus/task-focus-store.ts";
7
7
  import { TaskInvalidTransitionError } from "./task-lifecycle-errors.ts";
8
8
  import type { TaskMutationCoordinator, TaskMutationRequestContext } from "./task-mutation-coordinator.ts";
@@ -21,14 +21,8 @@ export interface TaskFocus {
21
21
  export type TaskFocusMutationResult = TaskFocus & TaskMutationMetadata;
22
22
 
23
23
  /**
24
- * Task Focus (the single active/paused task per session scope), split out of the Tasks god class
25
- * as part of a SOLID-audit-driven decomposition (see task b51419a0 and the "TaskFocusCoordinator"
26
- * child of "Epic: Modularize papyrus/pi-papyrus god-files into building-block modules"), mirroring
27
- * the existing TaskLeaseCoordinator/TaskMutationCoordinator precedent in this same directory.
28
- *
29
- * Focus is orthogonal to lifecycle and lease: focusing a task does not start it, and does not
30
- * claim its lease -- so this concern has nothing to do with status transitions or worker
31
- * exclusivity, the other concerns that were previously interleaved with it in one class.
24
+ * Task Focus (the single active/paused task per session scope). Orthogonal to lifecycle and
25
+ * lease: focusing a task does not start it and does not claim its lease.
32
26
  */
33
27
  export class TaskFocusCoordinator {
34
28
  constructor(
@@ -3,12 +3,9 @@ import type { TaskLease, TaskLeaseView } from "./lease/task-lease.ts";
3
3
  import type { TaskLeaseStore } from "./lease/task-lease-store.ts";
4
4
 
5
5
  /**
6
- * Task lease management (claim/heartbeat/release/get/reap), split out of the Tasks god class as
7
- * part of a SOLID-audit-driven decomposition (see task b51419a0). A lease is orthogonal to
8
- * lifecycle and Focus -- claiming a task does not start it, and does not require it to be
9
- * Focused -- so its own concern (a single active worker per task, TTL-based) has nothing to do
10
- * with status transitions, idempotency receipts, or checklist review, the other concerns that
11
- * were previously interleaved with it in one class.
6
+ * Task lease management (claim/heartbeat/release/get/reap) -- a single active worker per task,
7
+ * TTL-based. Orthogonal to lifecycle and Focus: claiming a task does not start it or require it
8
+ * to be Focused.
12
9
  */
13
10
  export class TaskLeaseCoordinator {
14
11
  constructor(
@@ -41,26 +41,16 @@ function canonicalJson(value: unknown): string {
41
41
  }
42
42
 
43
43
  /**
44
- * Idempotency-key-backed mutation receipt plumbing, split out of the Tasks god class as part of
45
- * a SOLID-audit-driven decomposition (see task b51419a0). Owns reserving a "pending" receipt
46
- * before a real mutation runs, replaying an already-completed one, rejecting a genuinely
47
- * different payload reused under the same key, and rejecting a NEW attempt against a
48
- * task+operation that already has one in flight.
44
+ * Idempotency-key-backed mutation receipt plumbing: reserves a "pending" receipt before a real
45
+ * mutation runs, replays an already-completed one, rejects a different payload reused under the
46
+ * same key, and rejects a new attempt against a task+operation that already has one in flight.
49
47
  *
50
- * `validate`, when supplied to prepare(), runs before anything else -- including before the
51
- * existing/replay lookup -- specifically so a caller-supplied validation failure (e.g. an
52
- * over-length `reason`) can never leave a receipt reserved with no way to ever mark it complete.
53
- * This is the direct fix for a real incident (task a54f0649, discovered live completing task
54
- * d0eb81b7): validation previously ran deep inside the CALLER's own atomic block (appendEvent's
55
- * own validateTaskEvent), strictly AFTER prepare()'s reserving call had already durably written
56
- * the receipt as pending. Once that validation threw, nothing downstream ever reached the code
57
- * path that marks a receipt complete, permanently stranding it -- and since the pending-mutation
58
- * lock is keyed on (taskId, operation) rather than the idempotency key, that stuck receipt then
59
- * blocked every subsequent attempt on the same task+operation, under ANY key, until the record's
60
- * 7-day retention window expired. No self-service recovery existed; the live incident required a
61
- * direct database row deletion. Every caller that can determine its own event-context validity up
62
- * front (reason/sessionId length, at minimum) should now pass a `validate` callback here instead
63
- * of only validating once real mutation work is already underway.
48
+ * `validate`, when passed to prepare(), runs before the existing/replay lookup, so a
49
+ * caller-supplied validation failure can never leave a receipt reserved with no way to complete
50
+ * it -- a stuck pending receipt blocks every later attempt on that task+operation, under any key,
51
+ * until its retention window expires, with no self-service recovery. Any caller that can
52
+ * determine its own event-context validity up front should pass `validate` here rather than
53
+ * validating only once real mutation work is underway.
64
54
  */
65
55
  export class TaskMutationCoordinator {
66
56
  constructor(
@@ -1,8 +1,8 @@
1
1
  import type { Artifact } from "../artifact/artifact.ts";
2
2
  import { TASK_PROJECT_LIST_MAX_RESULTS } from "../constants.ts";
3
3
  import { assertRegisterProjectInputBounds } from "../project-registry/project-registry.ts";
4
- import type { AppendTaskEvent, TaskEventContext } from "../task-event/task-event.ts";
5
- import type { TaskEventStore } from "../task-event/task-event-store.ts";
4
+ import type { AppendTaskEvent, TaskEventContext } from "./event/task-event.ts";
5
+ import type { TaskEventStore } from "./event/task-event-store.ts";
6
6
  import {
7
7
  normalizeProjectRoot,
8
8
  type RegisterTaskProjectInput,
@@ -10,21 +10,16 @@ import {
10
10
  type TaskViewMode,
11
11
  type TaskViewSelection,
12
12
  taskScopeLabel,
13
- } from "../task-scope/task-scope.ts";
14
- import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
13
+ } from "./scope/task-scope.ts";
14
+ import type { TaskScopeStore } from "./scope/task-scope-store.ts";
15
15
 
16
16
  export class TaskProjectNotFoundError extends Error {}
17
17
  export class TaskProjectAmbiguousError extends Error {}
18
18
 
19
19
  /**
20
20
  * Task project-scope management (scopeSelection/setView/assignProject/projects/resolveProject/
21
- * registerProject), split out of the Tasks god class as part of a SOLID-audit-driven
22
- * decomposition (see task b51419a0 and the "TaskProjectScope" child of "Epic: Modularize
23
- * papyrus/pi-papyrus god-files into building-block modules"), mirroring the existing
24
- * TaskLeaseCoordinator/TaskMutationCoordinator/TaskFocusCoordinator precedent in this directory.
25
- *
26
- * list()/graph() (which stay on Tasks -- they're core query/graph-construction, not project-scope
27
- * itself) still call scopeSelection() on this collaborator via Tasks' own thin delegation.
21
+ * registerProject). list()/graph() stay on Tasks but still call scopeSelection() here via a thin
22
+ * delegation, since those are core query/graph-construction, not project-scope itself.
28
23
  */
29
24
  export class TaskProjectScope {
30
25
  constructor(
@@ -18,6 +18,12 @@ import { DISCUSSION_SUBTYPE, isDiscussionArtifact, readDiscussionExtra } from ".
18
18
  import type { TransitionTable } from "../domain-service-shared.ts";
19
19
  import { type Gate, type GateResult, validateGates } from "../gate/gate.ts";
20
20
  import type { GateRunner } from "../gate/gate-runner.ts";
21
+ import { type Checklist, checklistEntries, type ProofReference, validateChecklist } from "./checklist.ts";
22
+ import {
23
+ InMemoryTaskCreateRequestStore,
24
+ TaskCreateIdempotencyConflictError,
25
+ type TaskCreateRequestStore,
26
+ } from "./create-request/task-create-request-store.ts";
21
27
  import type {
22
28
  AppendTaskEvent,
23
29
  TaskEventContext,
@@ -26,25 +32,9 @@ import type {
26
32
  TaskHistoryPage,
27
33
  TaskHistoryQuery,
28
34
  TaskLifecycleStatus,
29
- } from "../task-event/task-event.ts";
30
- import { validateEventContext } from "../task-event/task-event.ts";
31
- import { InMemoryTaskEventStore, type TaskEventStore } from "../task-event/task-event-store.ts";
32
- import {
33
- normalizeProjectRoot,
34
- type RegisterTaskProjectInput,
35
- type TaskProject,
36
- type TaskScopeSource,
37
- type TaskViewMode,
38
- type TaskViewSelection,
39
- taskScopeLabel,
40
- } from "../task-scope/task-scope.ts";
41
- import { InMemoryTaskScopeStore, type TaskScopeStore } from "../task-scope/task-scope-store.ts";
42
- import { type Checklist, checklistEntries, type ProofReference, validateChecklist } from "./checklist.ts";
43
- import {
44
- InMemoryTaskCreateRequestStore,
45
- TaskCreateIdempotencyConflictError,
46
- type TaskCreateRequestStore,
47
- } from "./create-request/task-create-request-store.ts";
35
+ } from "./event/task-event.ts";
36
+ import { validateEventContext } from "./event/task-event.ts";
37
+ import { InMemoryTaskEventStore, type TaskEventStore } from "./event/task-event-store.ts";
48
38
  import { InMemoryTaskFocusStore, type TaskFocusStatus, type TaskFocusStore } from "./focus/task-focus-store.ts";
49
39
  import type { TaskLeaseView } from "./lease/task-lease.ts";
50
40
  import { InMemoryTaskLeaseStore, type TaskLeaseStore } from "./lease/task-lease-store.ts";
@@ -54,6 +44,16 @@ import {
54
44
  type TaskMutationRequestRecord,
55
45
  type TaskMutationRequestStore,
56
46
  } from "./mutation-request/task-mutation-request-store.ts";
47
+ import {
48
+ normalizeProjectRoot,
49
+ type RegisterTaskProjectInput,
50
+ type TaskProject,
51
+ type TaskScopeSource,
52
+ type TaskViewMode,
53
+ type TaskViewSelection,
54
+ taskScopeLabel,
55
+ } from "./scope/task-scope.ts";
56
+ import { InMemoryTaskScopeStore, type TaskScopeStore } from "./scope/task-scope-store.ts";
57
57
  import { TaskEdges } from "./task-edges.ts";
58
58
  import { TaskExecutionBoundExceededError } from "./task-execution.ts";
59
59
  import { type TaskFocus, TaskFocusCoordinator, type TaskFocusMutationResult } from "./task-focus-coordinator.ts";