@danypops/papyrus 0.56.0 → 0.57.0

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 (107) hide show
  1. package/package.json +1 -1
  2. package/src/artifact/artifact-relationship-view.ts +1 -1
  3. package/src/artifact/artifact-scope-store.ts +32 -26
  4. package/src/artifact/in-memory-artifact-scope-store.ts +82 -43
  5. package/src/artifact/sqlite-artifact-scope-store.ts +105 -56
  6. package/src/cli/docs-command.ts +71 -0
  7. package/src/cli/gates-command.ts +1 -1
  8. package/src/cli/note-command.ts +1 -1
  9. package/src/cli/playbooks-command.ts +71 -0
  10. package/src/cli/rules-command.ts +71 -0
  11. package/src/cli/scope-groups-command.ts +197 -0
  12. package/src/cli/task-command.ts +1 -1
  13. package/src/cli.ts +7 -0
  14. package/src/constants.ts +10 -2
  15. package/src/db.ts +117 -7
  16. package/src/{stores → discussion}/discussion-round-store.ts +1 -1
  17. package/src/discussion/discussion-service.ts +2 -2
  18. package/src/{stores → discussion}/sqlite-discussion-round-store.ts +2 -2
  19. package/src/display-graph/graph-renderer.ts +5 -0
  20. package/src/docs/docs-service.ts +55 -20
  21. package/src/domain-service-shared.ts +90 -2
  22. package/src/{domain → gate}/gate-execution.ts +1 -1
  23. package/src/{stores → gate}/gate-runner.ts +1 -1
  24. package/src/{stores → gate}/sqlite-gate-runner.ts +3 -3
  25. package/src/graph-projection/graph-projection-service.ts +2 -2
  26. package/src/{stores → graph-projection}/graph-projection-store.ts +1 -1
  27. package/src/{stores → graph-projection}/sqlite-graph-projection-store.ts +2 -2
  28. package/src/handlers/discuss.ts +1 -1
  29. package/src/handlers/docs.ts +57 -2
  30. package/src/handlers/playbooks.ts +49 -8
  31. package/src/handlers/projects.ts +1 -1
  32. package/src/handlers/registry.ts +10 -5
  33. package/src/handlers/rules.ts +66 -2
  34. package/src/handlers/scope-groups.ts +100 -0
  35. package/src/handlers/task-classifiers.ts +1 -1
  36. package/src/handlers/tasks.ts +4 -4
  37. package/src/index.ts +9 -9
  38. package/src/log/log-service.ts +2 -2
  39. package/src/{stores → log}/log-store.ts +1 -1
  40. package/src/{stores → log}/sqlite-log-store.ts +2 -2
  41. package/src/modules/docs.ts +21 -1
  42. package/src/modules/graph-projection.ts +2 -2
  43. package/src/modules/logs.ts +1 -1
  44. package/src/modules/notes.ts +1 -1
  45. package/src/modules/playbooks.ts +24 -3
  46. package/src/modules/projects.ts +3 -3
  47. package/src/modules/rules.ts +21 -1
  48. package/src/modules/scope-groups.ts +92 -0
  49. package/src/modules/tasks.ts +3 -3
  50. package/src/{stores → note}/note-event-store.ts +1 -1
  51. package/src/note/note-service.ts +2 -2
  52. package/src/{stores → note}/sqlite-note-event-store.ts +2 -2
  53. package/src/ops.ts +1 -1
  54. package/src/playbook/playbook-definition.ts +2 -2
  55. package/src/playbook/playbook-execution.ts +1 -1
  56. package/src/playbook/playbook-service.ts +61 -26
  57. package/src/playbook/workflow-execution.ts +8 -8
  58. package/src/{stores → project-registry}/in-memory-project-registry-store.ts +2 -2
  59. package/src/{ports → project-registry}/project-registry-store.ts +1 -1
  60. package/src/{stores → project-registry}/sqlite-project-registry-store.ts +3 -3
  61. package/src/rules/rules-service.ts +56 -24
  62. package/src/scope-group/delete-scope-group.ts +17 -0
  63. package/src/scope-group/in-memory-scope-group-store.ts +173 -0
  64. package/src/scope-group/scope-group-store.ts +28 -0
  65. package/src/scope-group/scope-group.ts +79 -0
  66. package/src/scope-group/sqlite-scope-group-store.ts +223 -0
  67. package/src/service.ts +46 -21
  68. package/src/session-identity/session-identity-service.ts +2 -2
  69. package/src/{stores → session-identity}/sqlite-session-identity-store.ts +1 -1
  70. package/src/task/task-context.ts +1 -1
  71. package/src/task/task-edges.ts +2 -2
  72. package/src/task/task-focus-coordinator.ts +3 -3
  73. package/src/task/task-graph-view.ts +1 -1
  74. package/src/task/task-lease-coordinator.ts +2 -2
  75. package/src/task/task-mutation-coordinator.ts +1 -1
  76. package/src/task/task-project-scope.ts +5 -5
  77. package/src/task/task-relationship-view.ts +1 -1
  78. package/src/task/task-service.ts +24 -24
  79. package/src/{stores → task-create-request}/sqlite-task-create-request-store.ts +1 -1
  80. package/src/{stores → task-event}/sqlite-task-event-store.ts +2 -2
  81. package/src/{stores → task-event}/task-event-store.ts +1 -1
  82. package/src/{stores → task-focus}/sqlite-task-focus-store.ts +1 -1
  83. package/src/{stores → task-lease}/sqlite-task-lease-store.ts +2 -2
  84. package/src/{stores → task-lease}/task-lease-store.ts +1 -1
  85. package/src/{stores → task-mutation-request}/sqlite-task-mutation-request-store.ts +5 -1
  86. package/src/{stores → task-scope}/sqlite-task-scope-store.ts +3 -3
  87. package/src/{stores → task-scope}/task-scope-store.ts +3 -3
  88. package/src/{domain → task-scope}/task-scope.ts +1 -1
  89. package/src/domain/index.ts +0 -8
  90. package/src/stores/graph-renderer.ts +0 -5
  91. package/src/stores/index.ts +0 -1
  92. /package/src/{domain → discussion}/discussion.ts +0 -0
  93. /package/src/{domain → display-graph}/display-graph.ts +0 -0
  94. /package/src/{domain → gate}/gate.ts +0 -0
  95. /package/src/{domain → graph-projection}/graph-projection.ts +0 -0
  96. /package/src/{domain → log}/log-entry.ts +0 -0
  97. /package/src/{domain → note}/note-event.ts +0 -0
  98. /package/src/{domain → playbook}/blueprint-definition.ts +0 -0
  99. /package/src/{domain → project-registry}/project-registry.ts +0 -0
  100. /package/src/{stores → session-identity}/session-identity-store.ts +0 -0
  101. /package/src/{domain → session-identity}/session-identity.ts +0 -0
  102. /package/src/{domain → task}/checklist.ts +0 -0
  103. /package/src/{stores → task-create-request}/task-create-request-store.ts +0 -0
  104. /package/src/{domain → task-event}/task-event.ts +0 -0
  105. /package/src/{stores → task-focus}/task-focus-store.ts +0 -0
  106. /package/src/{domain → task-lease}/task-lease.ts +0 -0
  107. /package/src/{stores → task-mutation-request}/task-mutation-request-store.ts +0 -0
@@ -15,24 +15,29 @@ import { type Artifact, summarizeArtifact } from "../artifact/artifact.ts";
15
15
  import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
16
16
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
17
17
  import type { OperationDefinition } from "../module-registry.ts";
18
- import type { ProjectRegistryStore } from "../ports/project-registry-store.ts";
18
+ import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
19
19
  import {
20
+ addRuleGroup,
20
21
  addRuleProject,
21
22
  assignRuleProject,
22
23
  createRule,
23
24
  gateTaskWithRule,
24
25
  listRules,
25
26
  previewRule,
27
+ removeRuleGroup,
26
28
  removeRuleProject,
29
+ replaceRuleGroups,
27
30
  replaceRuleProjects,
28
31
  ruleCombinedLength,
29
32
  ruleCombinedLengthWarning,
30
33
  ruleScope,
31
34
  setRuleGlobal,
35
+ setRuleNone,
32
36
  showRule,
33
37
  transitionRule,
34
38
  updateRule,
35
39
  } from "../rules/rules-service.ts";
40
+ import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
36
41
  import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
37
42
 
38
43
  const MODULE_ID = "rules";
@@ -79,9 +84,13 @@ export const RULES_OPERATION_NAMES = [
79
84
  "rules.assign_project",
80
85
  "rules.scope",
81
86
  "rules.set_global",
87
+ "rules.set_none",
82
88
  "rules.add_project",
83
89
  "rules.remove_project",
84
90
  "rules.replace_projects",
91
+ "rules.add_group",
92
+ "rules.remove_group",
93
+ "rules.replace_groups",
85
94
  "rules.update",
86
95
  ] as const;
87
96
 
@@ -89,6 +98,7 @@ export function rulesOperations(
89
98
  artifacts: ArtifactStore,
90
99
  scopes: ArtifactScopeStore,
91
100
  registry: ProjectRegistryStore,
101
+ scopeGroups: ScopeGroupStore,
92
102
  ): OperationDefinition[] {
93
103
  const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
94
104
  name,
@@ -144,6 +154,16 @@ export function rulesOperations(
144
154
  ),
145
155
  define("rules.scope", (input: OperationInput) => ruleScope(artifacts, scopes, string(input, "id"))),
146
156
  define("rules.set_global", (input: OperationInput) => setRuleGlobal(artifacts, scopes, string(input, "id"))),
157
+ define("rules.set_none", (input: OperationInput) => setRuleNone(artifacts, scopes, string(input, "id"))),
158
+ define("rules.add_group", (input: OperationInput) =>
159
+ addRuleGroup(artifacts, scopes, scopeGroups, string(input, "id"), string(input, "group")),
160
+ ),
161
+ define("rules.remove_group", (input: OperationInput) =>
162
+ removeRuleGroup(artifacts, scopes, scopeGroups, string(input, "id"), string(input, "group")),
163
+ ),
164
+ define("rules.replace_groups", (input: OperationInput) =>
165
+ replaceRuleGroups(artifacts, scopes, scopeGroups, string(input, "id"), (input.groups as string[] | undefined) ?? []),
166
+ ),
147
167
  define("rules.add_project", (input: OperationInput) =>
148
168
  addRuleProject(artifacts, scopes, registry, string(input, "id"), string(input, "project")),
149
169
  ),
@@ -0,0 +1,92 @@
1
+ /**
2
+ * modules/scope-groups.ts — named, reusable, possibly-nested scope member collections as their
3
+ * own Papyrus-native registered module. A scope group's own members (added/removed here) are
4
+ * referenced by Docs/Rules/Playbooks' own explicit scope (docs/rules/playbooks.add_group etc.,
5
+ * see ../artifact/artifact-scope-store.ts) -- "explicit scope can include nested scopes".
6
+ * Mirrors modules/projects.ts's own shape (list/resolve/register) for the catalog half, plus
7
+ * real membership management and a real delete (deleteScopeGroup, unlike ProjectRegistryStore
8
+ * which has no delete path at all -- see papyrus task 1716b34b).
9
+ */
10
+
11
+ import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
12
+ import { SCOPE_GROUP_LIST_MAX_RESULTS } from "../constants.ts";
13
+ import type { OperationDefinition } from "../module-registry.ts";
14
+ import { resolveProjectReference } from "../project-registry/project-registry.ts";
15
+ import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
16
+ import { deleteScopeGroup } from "../scope-group/delete-scope-group.ts";
17
+ import { resolveScopeGroupReference, type ScopeMemberRef } from "../scope-group/scope-group.ts";
18
+ import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
19
+ import { type OperationInput, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
20
+
21
+ const MODULE_ID = "scope_groups";
22
+
23
+ export const SCOPE_GROUPS_OPERATION_NAMES = [
24
+ "scope_groups.list",
25
+ "scope_groups.resolve",
26
+ "scope_groups.register",
27
+ "scope_groups.show",
28
+ "scope_groups.add_member",
29
+ "scope_groups.remove_member",
30
+ "scope_groups.delete",
31
+ ] as const;
32
+
33
+ function resolveMember(
34
+ registry: ProjectRegistryStore,
35
+ scopeGroups: ScopeGroupStore,
36
+ memberType: string,
37
+ memberReference: string,
38
+ ): ScopeMemberRef {
39
+ if (memberType === "project") return { type: "project", id: resolveProjectReference(registry, memberReference).id };
40
+ if (memberType === "group") return { type: "group", id: resolveScopeGroupReference(scopeGroups, memberReference).id };
41
+ throw new Error('member_type must be "project" or "group"');
42
+ }
43
+
44
+ export function scopeGroupsOperations(
45
+ scopeGroups: ScopeGroupStore,
46
+ registry: ProjectRegistryStore,
47
+ artifactScopes: ArtifactScopeStore,
48
+ ): OperationDefinition[] {
49
+ const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
50
+ name,
51
+ moduleId: MODULE_ID,
52
+ execute,
53
+ });
54
+ return [
55
+ define("scope_groups.list", (input: OperationInput) => {
56
+ const limit = optionalNumber(input, "limit") ?? 20;
57
+ if (!Number.isInteger(limit) || limit < 1 || limit > SCOPE_GROUP_LIST_MAX_RESULTS) {
58
+ throw new Error(`scope group list limit must be between 1 and ${SCOPE_GROUP_LIST_MAX_RESULTS}`);
59
+ }
60
+ return scopeGroups.groups(optionalString(input, "query"), limit);
61
+ }),
62
+ define("scope_groups.resolve", (input: OperationInput) => resolveScopeGroupReference(scopeGroups, string(input, "reference"))),
63
+ define("scope_groups.register", (input: OperationInput) =>
64
+ scopeGroups.registerGroup({
65
+ name: optionalString(input, "name"),
66
+ aliases: optionalStringArray(input, "aliases"),
67
+ existingId: optionalString(input, "existing_id"),
68
+ }),
69
+ ),
70
+ define("scope_groups.show", (input: OperationInput) => {
71
+ const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
72
+ return { group, members: scopeGroups.members(group.id) };
73
+ }),
74
+ define("scope_groups.add_member", (input: OperationInput) => {
75
+ const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
76
+ const member = resolveMember(registry, scopeGroups, string(input, "member_type"), string(input, "member_reference"));
77
+ scopeGroups.addMember(group.id, member);
78
+ return { group, members: scopeGroups.members(group.id) };
79
+ }),
80
+ define("scope_groups.remove_member", (input: OperationInput) => {
81
+ const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
82
+ const member = resolveMember(registry, scopeGroups, string(input, "member_type"), string(input, "member_reference"));
83
+ scopeGroups.removeMember(group.id, member);
84
+ return { group, members: scopeGroups.members(group.id) };
85
+ }),
86
+ define("scope_groups.delete", (input: OperationInput) => {
87
+ const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
88
+ deleteScopeGroup(scopeGroups, artifactScopes, group.id);
89
+ return { deleted: true, id: group.id };
90
+ }),
91
+ ];
92
+ }
@@ -21,14 +21,14 @@
21
21
 
22
22
  import { summarizeArtifact } from "../artifact/artifact.ts";
23
23
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
24
- import type { Checklist } from "../domain/checklist.ts";
25
- import type { TaskEventContext, TaskEventDirection, TaskEventFeedQuery } from "../domain/task-event.ts";
26
- import type { TaskViewMode } from "../domain/task-scope.ts";
27
24
  import type { OperationDefinition } from "../module-registry.ts";
28
25
  import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
26
+ import type { Checklist } from "../task/checklist.ts";
29
27
  import { taskContext } from "../task/task-context.ts";
30
28
  import { projectTaskExecution } from "../task/task-execution.ts";
31
29
  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";
@@ -5,7 +5,7 @@ import {
5
5
  type NoteHistoryQuery,
6
6
  normalizeNoteHistoryQuery,
7
7
  validateNoteEvent,
8
- } from "../domain/note-event.ts";
8
+ } from "../note/note-event.ts";
9
9
 
10
10
  export interface NoteEventStore {
11
11
  append(event: AppendNoteEvent): NoteEvent;
@@ -10,8 +10,8 @@ import {
10
10
  NOTE_TITLE_MAX_CHARACTERS,
11
11
  TASK_PROJECT_ROOT_MAX_LENGTH,
12
12
  } from "../constants.ts";
13
- import type { AppendNoteEvent, NoteEventType, NoteHistoryPage, NoteHistoryQuery } from "../domain/note-event.ts";
14
- import { InMemoryNoteEventStore, type NoteEventStore } from "../stores/note-event-store.ts";
13
+ import type { AppendNoteEvent, NoteEventType, NoteHistoryPage, NoteHistoryQuery } from "./note-event.ts";
14
+ import { InMemoryNoteEventStore, type NoteEventStore } from "./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;
@@ -7,8 +7,8 @@ import {
7
7
  type NoteHistoryQuery,
8
8
  normalizeNoteHistoryQuery,
9
9
  validateNoteEvent,
10
- } from "../domain/note-event.ts";
11
- import type { NoteEventStore } from "./note-event-store.ts";
10
+ } from "../note/note-event.ts";
11
+ import type { NoteEventStore } from "../note/note-event-store.ts";
12
12
 
13
13
  interface NoteEventRow {
14
14
  id: number;
package/src/ops.ts CHANGED
@@ -481,7 +481,7 @@ export function purgeDueArtifacts(db: Db, now: () => string = () => new Date().t
481
481
  db.prepare("DELETE FROM task_scopes WHERE task_id = ?").run(id);
482
482
  db.prepare("DELETE FROM task_views WHERE root_task_id = ?").run(id);
483
483
  db.prepare("DELETE FROM graph_projection_identities WHERE artifact_id = ?").run(id);
484
- db.prepare("DELETE FROM artifact_scope_projects WHERE artifact_id = ?").run(id);
484
+ db.prepare("DELETE FROM artifact_scope_members WHERE artifact_id = ?").run(id);
485
485
  db.prepare("DELETE FROM artifact_scopes WHERE artifact_id = ?").run(id);
486
486
  db.prepare("DELETE FROM task_events WHERE task_id = ?").run(id);
487
487
  db.prepare("DELETE FROM note_events WHERE note_id = ?").run(id);
@@ -33,8 +33,8 @@ import type {
33
33
  DocBlueprint,
34
34
  RuleBlueprint,
35
35
  TaskBlueprint,
36
- } from "../domain/blueprint-definition.ts";
37
- import { validateBlueprintDefinition } from "../domain/blueprint-definition.ts";
36
+ } from "./blueprint-definition.ts";
37
+ import { validateBlueprintDefinition } from "./blueprint-definition.ts";
38
38
  import type { PlaybookArgument, PlaybookStep } from "./playbook-service.ts";
39
39
 
40
40
  /** A non-composing edge touching a playbook node, to be mirrored onto that node's generated root task once real task ids exist -- e.g. a Rule `gates` this playbook, or this playbook `references`/`documents` a Doc. Direction is preserved exactly: `from`/`to` name whichever side is NOT the playbook, and `ownerIsFrom` says which side the playbook (now the generated root task) occupies. */
@@ -10,8 +10,8 @@
10
10
 
11
11
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
12
12
  import { requireAtomicArtifactStore } from "../artifact/atomic-artifact-store.ts";
13
- import type { BlueprintArgumentValue } from "../domain/blueprint-definition.ts";
14
13
  import type { TaskExecutionPlan } from "../task/task-execution.ts";
14
+ import type { BlueprintArgumentValue } from "./blueprint-definition.ts";
15
15
  import { compilePlaybookDefinition } from "./playbook-definition.ts";
16
16
  import {
17
17
  applyPlaybookExternalLinks,
@@ -28,7 +28,6 @@ import type { ArtifactEventContext } from "../artifact/artifact-event.ts";
28
28
  import type { ArtifactScope, ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
29
29
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
30
30
  import {
31
- ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT,
32
31
  ARTIFACT_TITLE_MAX_LENGTH,
33
32
  PLAYBOOK_ARGUMENT_DESCRIPTION_MAX_LENGTH,
34
33
  PLAYBOOK_ARGUMENT_MAX_COUNT,
@@ -39,26 +38,33 @@ import {
39
38
  SKILL_MAX_ENUM_VALUES,
40
39
  } from "../constants.ts";
41
40
  import {
42
- BLUEPRINT_INPUT_TYPES,
43
- type BlueprintArgumentValue,
44
- type BlueprintInputType,
45
- validateArgumentValue,
46
- } from "../domain/blueprint-definition.ts";
47
- import { resolveProjectReference } from "../domain/project-registry.ts";
48
- import { normalizeProjectRoot } from "../domain/task-scope.ts";
49
- import {
41
+ addArtifactScopeGroup,
42
+ addArtifactScopeProject,
50
43
  assertBodyBounds,
51
44
  assertLabelsBounds,
52
45
  assertTitleBounds,
53
46
  assignArtifactProject,
54
47
  type ListFilter,
55
48
  listScoped,
49
+ removeArtifactScopeGroup,
50
+ removeArtifactScopeProject,
51
+ replaceArtifactScopeGroups,
52
+ replaceArtifactScopeProjects,
56
53
  requireKind,
57
54
  runTransition,
55
+ setArtifactScopeNone,
58
56
  type TransitionTable,
59
57
  type UpdateContentInput,
60
58
  } from "../domain-service-shared.ts";
61
- import type { ProjectRegistryStore } from "../ports/project-registry-store.ts";
59
+ import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
60
+ import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
61
+ import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
62
+ import {
63
+ BLUEPRINT_INPUT_TYPES,
64
+ type BlueprintArgumentValue,
65
+ type BlueprintInputType,
66
+ validateArgumentValue,
67
+ } from "./blueprint-definition.ts";
62
68
 
63
69
  export interface PlaybookArgument {
64
70
  name: string;
@@ -280,11 +286,7 @@ export function createPlaybook(
280
286
  context,
281
287
  );
282
288
  if (input.projectReferences !== undefined && input.projectReferences.length > 0) {
283
- if (input.projectReferences.length > ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT) {
284
- throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT} entries`);
285
- }
286
- const ids = input.projectReferences.map((reference) => resolveProjectReference(registry!, reference).id);
287
- scopes.replaceProjects(playbook.id, ids, "explicit");
289
+ replaceArtifactScopeProjects(scopes, registry!, playbook.id, input.projectReferences);
288
290
  } else {
289
291
  scopes.assign(playbook.id, projectRoot, projectRoot === undefined ? "unscoped" : "explicit");
290
292
  }
@@ -320,7 +322,13 @@ export function playbookScope(artifacts: ArtifactStore, scopes: ArtifactScopeSto
320
322
 
321
323
  export function setPlaybookGlobal(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string): ArtifactScope {
322
324
  requireKind(artifacts, id, "playbook");
323
- return scopes.setGlobal(id, "explicit");
325
+ return scopes.setAll(id, "explicit");
326
+ }
327
+
328
+ /** Fully hides this Playbook -- never applicable, never injected via before_agent_start, regardless of project. */
329
+ export function setPlaybookNone(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string): ArtifactScope {
330
+ requireKind(artifacts, id, "playbook");
331
+ return setArtifactScopeNone(scopes, id);
324
332
  }
325
333
 
326
334
  export function replacePlaybookProjects(
@@ -331,12 +339,7 @@ export function replacePlaybookProjects(
331
339
  projectReferences: readonly string[],
332
340
  ): ArtifactScope {
333
341
  requireKind(artifacts, id, "playbook");
334
- if (projectReferences.length === 0) throw new Error("projectReferences must be non-empty; use playbooks.set_global to clear scoping");
335
- if (projectReferences.length > ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT) {
336
- throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT} entries`);
337
- }
338
- const ids = projectReferences.map((reference) => resolveProjectReference(registry, reference).id);
339
- return scopes.replaceProjects(id, ids, "explicit");
342
+ return replaceArtifactScopeProjects(scopes, registry, id, projectReferences);
340
343
  }
341
344
 
342
345
  export function addPlaybookProject(
@@ -347,8 +350,7 @@ export function addPlaybookProject(
347
350
  projectReference: string,
348
351
  ): ArtifactScope {
349
352
  requireKind(artifacts, id, "playbook");
350
- const project = resolveProjectReference(registry, projectReference);
351
- return scopes.addProject(id, project.id, "explicit");
353
+ return addArtifactScopeProject(scopes, registry, id, projectReference);
352
354
  }
353
355
 
354
356
  export function removePlaybookProject(
@@ -359,8 +361,41 @@ export function removePlaybookProject(
359
361
  projectReference: string,
360
362
  ): ArtifactScope {
361
363
  requireKind(artifacts, id, "playbook");
362
- const project = resolveProjectReference(registry, projectReference);
363
- return scopes.removeProject(id, project.id);
364
+ return removeArtifactScopeProject(scopes, registry, id, projectReference);
365
+ }
366
+
367
+ /** Scope-group ('nested scope') siblings of replacePlaybookProjects/addPlaybookProject/removePlaybookProject. */
368
+ export function replacePlaybookGroups(
369
+ artifacts: ArtifactStore,
370
+ scopes: ArtifactScopeStore,
371
+ scopeGroups: ScopeGroupStore,
372
+ id: string,
373
+ groupReferences: readonly string[],
374
+ ): ArtifactScope {
375
+ requireKind(artifacts, id, "playbook");
376
+ return replaceArtifactScopeGroups(scopes, scopeGroups, id, groupReferences);
377
+ }
378
+
379
+ export function addPlaybookGroup(
380
+ artifacts: ArtifactStore,
381
+ scopes: ArtifactScopeStore,
382
+ scopeGroups: ScopeGroupStore,
383
+ id: string,
384
+ groupReference: string,
385
+ ): ArtifactScope {
386
+ requireKind(artifacts, id, "playbook");
387
+ return addArtifactScopeGroup(scopes, scopeGroups, id, groupReference);
388
+ }
389
+
390
+ export function removePlaybookGroup(
391
+ artifacts: ArtifactStore,
392
+ scopes: ArtifactScopeStore,
393
+ scopeGroups: ScopeGroupStore,
394
+ id: string,
395
+ groupReference: string,
396
+ ): ArtifactScope {
397
+ requireKind(artifacts, id, "playbook");
398
+ return removeArtifactScopeGroup(scopes, scopeGroups, id, groupReference);
364
399
  }
365
400
 
366
401
  export function showPlaybook(artifacts: ArtifactStore, id: string): Artifact {
@@ -9,20 +9,20 @@ import {
9
9
  SKILL_WORKFLOW_MAX_NESTING_DEPTH,
10
10
  TASK_EXECUTION_MAX_EDGES,
11
11
  } from "../constants.ts";
12
+ import { validateChecklist } from "../task/checklist.ts";
13
+ import { projectTaskExecution, TaskExecutionBoundExceededError, type TaskExecutionPlan } from "../task/task-execution.ts";
14
+ 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";
12
19
  import {
13
20
  type BlueprintArgumentValue,
14
21
  type BlueprintDefinition,
15
22
  type CallBlueprint,
16
23
  resolveBlueprintArguments,
17
24
  validateBlueprintDefinition,
18
- } from "../domain/blueprint-definition.ts";
19
- import { validateChecklist } from "../domain/checklist.ts";
20
- import type { TaskEventContext } from "../domain/task-event.ts";
21
- import { normalizeProjectRoot } from "../domain/task-scope.ts";
22
- import type { TaskEventStore } from "../stores/task-event-store.ts";
23
- import type { TaskScopeStore } from "../stores/task-scope-store.ts";
24
- import { projectTaskExecution, TaskExecutionBoundExceededError, type TaskExecutionPlan } from "../task/task-execution.ts";
25
- import type { TaskGraph, TaskNode, TaskStatus } from "../task/task-service.ts";
25
+ } from "./blueprint-definition.ts";
26
26
  import { compilePlaybookDefinition, type PlaybookExternalLink } from "./playbook-definition.ts";
27
27
 
28
28
  const RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { basename } from "node:path";
3
3
  import { TASK_PROJECT_ALIAS_MAX_COUNT } from "../constants.ts";
4
- import type { Project, RegisterProjectInput } from "../domain/project-registry.ts";
5
- import type { ProjectRegistryStore } from "../ports/project-registry-store.ts";
4
+ import type { Project, RegisterProjectInput } from "../project-registry/project-registry.ts";
5
+ import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
6
6
 
7
7
  export function uniqueAliases(values: readonly string[], name: string): string[] {
8
8
  const seen = new Set([name.trim().toLowerCase()]);
@@ -1,4 +1,4 @@
1
- import type { Project, RegisterProjectInput } from "../domain/project-registry.ts";
1
+ import type { Project, RegisterProjectInput } from "../project-registry/project-registry.ts";
2
2
 
3
3
  /**
4
4
  * Kind-neutral project identity, shared by Task scope and every non-Task artifact scope
@@ -2,9 +2,9 @@ import { randomUUID } from "node:crypto";
2
2
  import { basename } from "node:path";
3
3
  import type { Db } from "../db.ts";
4
4
  import { inTransaction } from "../db.ts";
5
- import type { Project, RegisterProjectInput } from "../domain/project-registry.ts";
6
- import type { ProjectRegistryStore } from "../ports/project-registry-store.ts";
7
- import { uniqueAliases } from "./in-memory-project-registry-store.ts";
5
+ import { uniqueAliases } from "../project-registry/in-memory-project-registry-store.ts";
6
+ import type { Project, RegisterProjectInput } from "../project-registry/project-registry.ts";
7
+ import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
8
8
 
9
9
  interface ProjectRow {
10
10
  id: string;
@@ -10,26 +10,29 @@ import { requireLocallyOwnedContent } from "../artifact/artifact.ts";
10
10
  import type { ArtifactEventContext } from "../artifact/artifact-event.ts";
11
11
  import type { ArtifactScope, ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
12
12
  import type { ArtifactStore } from "../artifact/artifact-store.ts";
13
+ import { RULE_TEXT_HARD_LIMIT_CHARACTERS, RULE_TEXT_SOFT_TARGET_CHARACTERS } from "../constants.ts";
13
14
  import {
14
- ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT,
15
- RULE_TEXT_HARD_LIMIT_CHARACTERS,
16
- RULE_TEXT_SOFT_TARGET_CHARACTERS,
17
- } from "../constants.ts";
18
- import { resolveProjectReference } from "../domain/project-registry.ts";
19
- import { normalizeProjectRoot } from "../domain/task-scope.ts";
20
- import {
15
+ addArtifactScopeGroup,
16
+ addArtifactScopeProject,
21
17
  assertLabelsBounds,
22
18
  assertTitleBounds,
23
19
  assignArtifactProject,
24
20
  type ListFilter,
25
21
  listScoped,
22
+ removeArtifactScopeGroup,
23
+ removeArtifactScopeProject,
24
+ replaceArtifactScopeGroups,
25
+ replaceArtifactScopeProjects,
26
26
  requireContentUpdateFields,
27
27
  requireKind,
28
28
  runTransition,
29
+ setArtifactScopeNone,
29
30
  type TransitionTable,
30
31
  type UpdateContentInput,
31
32
  } from "../domain-service-shared.ts";
32
- import type { ProjectRegistryStore } from "../ports/project-registry-store.ts";
33
+ import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
34
+ import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
35
+ import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
33
36
 
34
37
  export interface CreateRuleInput {
35
38
  title: string;
@@ -156,11 +159,7 @@ export function createRule(
156
159
  context,
157
160
  );
158
161
  if (input.projectReferences !== undefined && input.projectReferences.length > 0) {
159
- if (input.projectReferences.length > ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT) {
160
- throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT} entries`);
161
- }
162
- const ids = input.projectReferences.map((reference) => resolveProjectReference(registry!, reference).id);
163
- scopes.replaceProjects(rule.id, ids, "explicit");
162
+ replaceArtifactScopeProjects(scopes, registry!, rule.id, input.projectReferences);
164
163
  } else {
165
164
  scopes.assign(rule.id, projectRoot, projectRoot === undefined ? "unscoped" : "explicit");
166
165
  }
@@ -195,7 +194,13 @@ export function ruleScope(artifacts: ArtifactStore, scopes: ArtifactScopeStore,
195
194
 
196
195
  export function setRuleGlobal(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string): ArtifactScope {
197
196
  requireKind(artifacts, id, "rule");
198
- return scopes.setGlobal(id, "explicit");
197
+ return scopes.setAll(id, "explicit");
198
+ }
199
+
200
+ /** Fully hides this Rule -- never applicable, never injected, regardless of project. */
201
+ export function setRuleNone(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string): ArtifactScope {
202
+ requireKind(artifacts, id, "rule");
203
+ return setArtifactScopeNone(scopes, id);
199
204
  }
200
205
 
201
206
  export function replaceRuleProjects(
@@ -206,12 +211,7 @@ export function replaceRuleProjects(
206
211
  projectReferences: readonly string[],
207
212
  ): ArtifactScope {
208
213
  requireKind(artifacts, id, "rule");
209
- if (projectReferences.length === 0) throw new Error("projectReferences must be non-empty; use rules.set_global to clear scoping");
210
- if (projectReferences.length > ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT) {
211
- throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT} entries`);
212
- }
213
- const ids = projectReferences.map((reference) => resolveProjectReference(registry, reference).id);
214
- return scopes.replaceProjects(id, ids, "explicit");
214
+ return replaceArtifactScopeProjects(scopes, registry, id, projectReferences);
215
215
  }
216
216
 
217
217
  export function addRuleProject(
@@ -222,8 +222,7 @@ export function addRuleProject(
222
222
  projectReference: string,
223
223
  ): ArtifactScope {
224
224
  requireKind(artifacts, id, "rule");
225
- const project = resolveProjectReference(registry, projectReference);
226
- return scopes.addProject(id, project.id, "explicit");
225
+ return addArtifactScopeProject(scopes, registry, id, projectReference);
227
226
  }
228
227
 
229
228
  export function removeRuleProject(
@@ -234,8 +233,41 @@ export function removeRuleProject(
234
233
  projectReference: string,
235
234
  ): ArtifactScope {
236
235
  requireKind(artifacts, id, "rule");
237
- const project = resolveProjectReference(registry, projectReference);
238
- return scopes.removeProject(id, project.id);
236
+ return removeArtifactScopeProject(scopes, registry, id, projectReference);
237
+ }
238
+
239
+ /** Scope-group ('nested scope') siblings of replaceRuleProjects/addRuleProject/removeRuleProject. */
240
+ export function replaceRuleGroups(
241
+ artifacts: ArtifactStore,
242
+ scopes: ArtifactScopeStore,
243
+ scopeGroups: ScopeGroupStore,
244
+ id: string,
245
+ groupReferences: readonly string[],
246
+ ): ArtifactScope {
247
+ requireKind(artifacts, id, "rule");
248
+ return replaceArtifactScopeGroups(scopes, scopeGroups, id, groupReferences);
249
+ }
250
+
251
+ export function addRuleGroup(
252
+ artifacts: ArtifactStore,
253
+ scopes: ArtifactScopeStore,
254
+ scopeGroups: ScopeGroupStore,
255
+ id: string,
256
+ groupReference: string,
257
+ ): ArtifactScope {
258
+ requireKind(artifacts, id, "rule");
259
+ return addArtifactScopeGroup(scopes, scopeGroups, id, groupReference);
260
+ }
261
+
262
+ export function removeRuleGroup(
263
+ artifacts: ArtifactStore,
264
+ scopes: ArtifactScopeStore,
265
+ scopeGroups: ScopeGroupStore,
266
+ id: string,
267
+ groupReference: string,
268
+ ): ArtifactScope {
269
+ requireKind(artifacts, id, "rule");
270
+ return removeArtifactScopeGroup(scopes, scopeGroups, id, groupReference);
239
271
  }
240
272
 
241
273
  /** The extra.scope run-gating check alone -- a Rule with no extra.scope always passes this; one with a skill-run/playbook-run scope passes only while its run owns activeTaskId. Both a workflow-definition target's own run scope ("skill-run", written by workflow-execution.ts's runWorkflowSteps for that target kind) and a Playbook's own run scope ("playbook-run", same call for a Playbook target) are recognized -- confirmed live that only "skill-run" was ever checked here, silently breaking Playbook-run-scoped rule injection since Playbook gained its own doc/rule structured steps. */
@@ -0,0 +1,17 @@
1
+ import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
2
+ import { ScopeGroupInUseError } from "./scope-group.ts";
3
+ import type { ScopeGroupStore } from "./scope-group-store.ts";
4
+
5
+ /**
6
+ * The one real cross-store check a plain ScopeGroupStore.delete() cannot make on its own: whether
7
+ * any Doc/Rule/Playbook's own explicit scope still references this group. ScopeGroupStore.delete()
8
+ * itself only checks the self-consistency case (another group's own membership) -- deliberately,
9
+ * so the store's own port stays kind-agnostic and never depends on ArtifactScopeStore. This
10
+ * orchestration function is the one real caller that holds both.
11
+ */
12
+ export function deleteScopeGroup(scopeGroups: ScopeGroupStore, artifactScopes: ArtifactScopeStore, groupId: string): void {
13
+ if (artifactScopes.referencesGroup(groupId)) {
14
+ throw new ScopeGroupInUseError(`scope group "${groupId}" is still referenced by at least one artifact's explicit scope`);
15
+ }
16
+ scopeGroups.delete(groupId);
17
+ }