@cat-factory/agents 0.6.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 (226) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agents/AiAgentExecutor.d.ts +72 -0
  3. package/dist/agents/AiAgentExecutor.d.ts.map +1 -0
  4. package/dist/agents/AiAgentExecutor.js +125 -0
  5. package/dist/agents/AiAgentExecutor.js.map +1 -0
  6. package/dist/agents/acceptance-prompts.d.ts +29 -0
  7. package/dist/agents/acceptance-prompts.d.ts.map +1 -0
  8. package/dist/agents/acceptance-prompts.js +112 -0
  9. package/dist/agents/acceptance-prompts.js.map +1 -0
  10. package/dist/agents/agent-catalog.d.ts +6 -0
  11. package/dist/agents/agent-catalog.d.ts.map +1 -0
  12. package/dist/agents/agent-catalog.js +197 -0
  13. package/dist/agents/agent-catalog.js.map +1 -0
  14. package/dist/agents/agent-configs.d.ts +17 -0
  15. package/dist/agents/agent-configs.d.ts.map +1 -0
  16. package/dist/agents/agent-configs.js +66 -0
  17. package/dist/agents/agent-configs.js.map +1 -0
  18. package/dist/agents/agent-routing.d.ts +57 -0
  19. package/dist/agents/agent-routing.d.ts.map +1 -0
  20. package/dist/agents/agent-routing.js +41 -0
  21. package/dist/agents/agent-routing.js.map +1 -0
  22. package/dist/agents/business-logic-prompts.d.ts +28 -0
  23. package/dist/agents/business-logic-prompts.d.ts.map +1 -0
  24. package/dist/agents/business-logic-prompts.js +96 -0
  25. package/dist/agents/business-logic-prompts.js.map +1 -0
  26. package/dist/agents/catalog.d.ts +6 -0
  27. package/dist/agents/catalog.d.ts.map +1 -0
  28. package/dist/agents/catalog.js +168 -0
  29. package/dist/agents/catalog.js.map +1 -0
  30. package/dist/agents/ci-gate.d.ts +2 -0
  31. package/dist/agents/ci-gate.d.ts.map +1 -0
  32. package/dist/agents/ci-gate.js +33 -0
  33. package/dist/agents/ci-gate.js.map +1 -0
  34. package/dist/agents/companion-prompts.d.ts +4 -0
  35. package/dist/agents/companion-prompts.d.ts.map +1 -0
  36. package/dist/agents/companion-prompts.js +27 -0
  37. package/dist/agents/companion-prompts.js.map +1 -0
  38. package/dist/agents/companions.d.ts +20 -0
  39. package/dist/agents/companions.d.ts.map +1 -0
  40. package/dist/agents/companions.js +38 -0
  41. package/dist/agents/companions.js.map +1 -0
  42. package/dist/agents/kinds/companions.d.ts +20 -0
  43. package/dist/agents/kinds/companions.d.ts.map +1 -0
  44. package/dist/agents/kinds/companions.js +39 -0
  45. package/dist/agents/kinds/companions.js.map +1 -0
  46. package/dist/agents/kinds/configs.d.ts +17 -0
  47. package/dist/agents/kinds/configs.d.ts.map +1 -0
  48. package/dist/agents/kinds/configs.js +66 -0
  49. package/dist/agents/kinds/configs.js.map +1 -0
  50. package/dist/agents/kinds/read-only.d.ts +13 -0
  51. package/dist/agents/kinds/read-only.d.ts.map +1 -0
  52. package/dist/agents/kinds/read-only.js +32 -0
  53. package/dist/agents/kinds/read-only.js.map +1 -0
  54. package/dist/agents/kinds/registry.d.ts +70 -0
  55. package/dist/agents/kinds/registry.d.ts.map +1 -0
  56. package/dist/agents/kinds/registry.js +51 -0
  57. package/dist/agents/kinds/registry.js.map +1 -0
  58. package/dist/agents/kinds/traits.d.ts +60 -0
  59. package/dist/agents/kinds/traits.d.ts.map +1 -0
  60. package/dist/agents/kinds/traits.js +123 -0
  61. package/dist/agents/kinds/traits.js.map +1 -0
  62. package/dist/agents/kinds/versions.d.ts +46 -0
  63. package/dist/agents/kinds/versions.d.ts.map +1 -0
  64. package/dist/agents/kinds/versions.js +22 -0
  65. package/dist/agents/kinds/versions.js.map +1 -0
  66. package/dist/agents/mock-prompts.d.ts +12 -0
  67. package/dist/agents/mock-prompts.d.ts.map +1 -0
  68. package/dist/agents/mock-prompts.js +61 -0
  69. package/dist/agents/mock-prompts.js.map +1 -0
  70. package/dist/agents/prompt-fragments.d.ts +17 -0
  71. package/dist/agents/prompt-fragments.d.ts.map +1 -0
  72. package/dist/agents/prompt-fragments.js +33 -0
  73. package/dist/agents/prompt-fragments.js.map +1 -0
  74. package/dist/agents/prompt-shared.d.ts +7 -0
  75. package/dist/agents/prompt-shared.d.ts.map +1 -0
  76. package/dist/agents/prompt-shared.js +10 -0
  77. package/dist/agents/prompt-shared.js.map +1 -0
  78. package/dist/agents/prompt-versions.d.ts +48 -0
  79. package/dist/agents/prompt-versions.d.ts.map +1 -0
  80. package/dist/agents/prompt-versions.js +55 -0
  81. package/dist/agents/prompt-versions.js.map +1 -0
  82. package/dist/agents/prompts/acceptance.d.ts +29 -0
  83. package/dist/agents/prompts/acceptance.d.ts.map +1 -0
  84. package/dist/agents/prompts/acceptance.js +112 -0
  85. package/dist/agents/prompts/acceptance.js.map +1 -0
  86. package/dist/agents/prompts/business-logic.d.ts +28 -0
  87. package/dist/agents/prompts/business-logic.d.ts.map +1 -0
  88. package/dist/agents/prompts/business-logic.js +98 -0
  89. package/dist/agents/prompts/business-logic.js.map +1 -0
  90. package/dist/agents/prompts/clarity.d.ts +10 -0
  91. package/dist/agents/prompts/clarity.d.ts.map +1 -0
  92. package/dist/agents/prompts/clarity.js +40 -0
  93. package/dist/agents/prompts/clarity.js.map +1 -0
  94. package/dist/agents/prompts/companion.d.ts +4 -0
  95. package/dist/agents/prompts/companion.d.ts.map +1 -0
  96. package/dist/agents/prompts/companion.js +61 -0
  97. package/dist/agents/prompts/companion.js.map +1 -0
  98. package/dist/agents/prompts/delivery-contract.d.ts +2 -0
  99. package/dist/agents/prompts/delivery-contract.d.ts.map +1 -0
  100. package/dist/agents/prompts/delivery-contract.js +33 -0
  101. package/dist/agents/prompts/delivery-contract.js.map +1 -0
  102. package/dist/agents/prompts/mock.d.ts +12 -0
  103. package/dist/agents/prompts/mock.d.ts.map +1 -0
  104. package/dist/agents/prompts/mock.js +61 -0
  105. package/dist/agents/prompts/mock.js.map +1 -0
  106. package/dist/agents/prompts/requirements.d.ts +13 -0
  107. package/dist/agents/prompts/requirements.d.ts.map +1 -0
  108. package/dist/agents/prompts/requirements.js +45 -0
  109. package/dist/agents/prompts/requirements.js.map +1 -0
  110. package/dist/agents/prompts/roles.d.ts +16 -0
  111. package/dist/agents/prompts/roles.d.ts.map +1 -0
  112. package/dist/agents/prompts/roles.js +74 -0
  113. package/dist/agents/prompts/roles.js.map +1 -0
  114. package/dist/agents/prompts/shared.d.ts +19 -0
  115. package/dist/agents/prompts/shared.d.ts.map +1 -0
  116. package/dist/agents/prompts/shared.js +25 -0
  117. package/dist/agents/prompts/shared.js.map +1 -0
  118. package/dist/agents/prompts/standard-templates.generated.d.ts +36 -0
  119. package/dist/agents/prompts/standard-templates.generated.d.ts.map +1 -0
  120. package/dist/agents/prompts/standard-templates.generated.js +122 -0
  121. package/dist/agents/prompts/standard-templates.generated.js.map +1 -0
  122. package/dist/agents/prompts/standard.d.ts +36 -0
  123. package/dist/agents/prompts/standard.d.ts.map +1 -0
  124. package/dist/agents/prompts/standard.js +208 -0
  125. package/dist/agents/prompts/standard.js.map +1 -0
  126. package/dist/agents/prompts/testing.d.ts +12 -0
  127. package/dist/agents/prompts/testing.d.ts.map +1 -0
  128. package/dist/agents/prompts/testing.js +94 -0
  129. package/dist/agents/prompts/testing.js.map +1 -0
  130. package/dist/agents/read-only.d.ts +13 -0
  131. package/dist/agents/read-only.d.ts.map +1 -0
  132. package/dist/agents/read-only.js +29 -0
  133. package/dist/agents/read-only.js.map +1 -0
  134. package/dist/agents/registry.d.ts +70 -0
  135. package/dist/agents/registry.d.ts.map +1 -0
  136. package/dist/agents/registry.js +51 -0
  137. package/dist/agents/registry.js.map +1 -0
  138. package/dist/agents/runtime/executor.d.ts +72 -0
  139. package/dist/agents/runtime/executor.d.ts.map +1 -0
  140. package/dist/agents/runtime/executor.js +125 -0
  141. package/dist/agents/runtime/executor.js.map +1 -0
  142. package/dist/agents/runtime/fragments.d.ts +17 -0
  143. package/dist/agents/runtime/fragments.d.ts.map +1 -0
  144. package/dist/agents/runtime/fragments.js +33 -0
  145. package/dist/agents/runtime/fragments.js.map +1 -0
  146. package/dist/agents/runtime/routing.d.ts +57 -0
  147. package/dist/agents/runtime/routing.d.ts.map +1 -0
  148. package/dist/agents/runtime/routing.js +41 -0
  149. package/dist/agents/runtime/routing.js.map +1 -0
  150. package/dist/agents/runtime/web-search.d.ts +43 -0
  151. package/dist/agents/runtime/web-search.d.ts.map +1 -0
  152. package/dist/agents/runtime/web-search.js +102 -0
  153. package/dist/agents/runtime/web-search.js.map +1 -0
  154. package/dist/agents/standard-prompt-templates.generated.d.ts +36 -0
  155. package/dist/agents/standard-prompt-templates.generated.d.ts.map +1 -0
  156. package/dist/agents/standard-prompt-templates.generated.js +122 -0
  157. package/dist/agents/standard-prompt-templates.generated.js.map +1 -0
  158. package/dist/agents/standard-prompts.d.ts +36 -0
  159. package/dist/agents/standard-prompts.d.ts.map +1 -0
  160. package/dist/agents/standard-prompts.js +202 -0
  161. package/dist/agents/standard-prompts.js.map +1 -0
  162. package/dist/agents/test-prompts.d.ts +12 -0
  163. package/dist/agents/test-prompts.d.ts.map +1 -0
  164. package/dist/agents/test-prompts.js +92 -0
  165. package/dist/agents/test-prompts.js.map +1 -0
  166. package/dist/agents/traits.d.ts +56 -0
  167. package/dist/agents/traits.d.ts.map +1 -0
  168. package/dist/agents/traits.js +100 -0
  169. package/dist/agents/traits.js.map +1 -0
  170. package/dist/agents/web-search.d.ts +43 -0
  171. package/dist/agents/web-search.d.ts.map +1 -0
  172. package/dist/agents/web-search.js +102 -0
  173. package/dist/agents/web-search.js.map +1 -0
  174. package/dist/fragmentLibrary/DeterministicFragmentSelector.d.ts +10 -0
  175. package/dist/fragmentLibrary/DeterministicFragmentSelector.d.ts.map +1 -0
  176. package/dist/fragmentLibrary/DeterministicFragmentSelector.js +12 -0
  177. package/dist/fragmentLibrary/DeterministicFragmentSelector.js.map +1 -0
  178. package/dist/fragmentLibrary/FragmentLibraryService.d.ts +55 -0
  179. package/dist/fragmentLibrary/FragmentLibraryService.d.ts.map +1 -0
  180. package/dist/fragmentLibrary/FragmentLibraryService.js +203 -0
  181. package/dist/fragmentLibrary/FragmentLibraryService.js.map +1 -0
  182. package/dist/fragmentLibrary/FragmentSourceService.d.ts +51 -0
  183. package/dist/fragmentLibrary/FragmentSourceService.d.ts.map +1 -0
  184. package/dist/fragmentLibrary/FragmentSourceService.js +181 -0
  185. package/dist/fragmentLibrary/FragmentSourceService.js.map +1 -0
  186. package/dist/fragmentLibrary/LlmFragmentSelector.d.ts +21 -0
  187. package/dist/fragmentLibrary/LlmFragmentSelector.d.ts.map +1 -0
  188. package/dist/fragmentLibrary/LlmFragmentSelector.js +87 -0
  189. package/dist/fragmentLibrary/LlmFragmentSelector.js.map +1 -0
  190. package/dist/fragmentLibrary/fragment-catalog.d.ts +43 -0
  191. package/dist/fragmentLibrary/fragment-catalog.d.ts.map +1 -0
  192. package/dist/fragmentLibrary/fragment-catalog.js +129 -0
  193. package/dist/fragmentLibrary/fragment-catalog.js.map +1 -0
  194. package/dist/fragmentLibrary/fragment-source.logic.d.ts +34 -0
  195. package/dist/fragmentLibrary/fragment-source.logic.d.ts.map +1 -0
  196. package/dist/fragmentLibrary/fragment-source.logic.js +172 -0
  197. package/dist/fragmentLibrary/fragment-source.logic.js.map +1 -0
  198. package/dist/index.d.ts +30 -0
  199. package/dist/index.d.ts.map +1 -0
  200. package/dist/index.js +43 -0
  201. package/dist/index.js.map +1 -0
  202. package/dist/providers/cache.d.ts +22 -0
  203. package/dist/providers/cache.d.ts.map +1 -0
  204. package/dist/providers/cache.js +64 -0
  205. package/dist/providers/cache.js.map +1 -0
  206. package/dist/providers/endpoints.d.ts +8 -0
  207. package/dist/providers/endpoints.d.ts.map +1 -0
  208. package/dist/providers/endpoints.js +23 -0
  209. package/dist/providers/endpoints.js.map +1 -0
  210. package/dist/providers/index.d.ts +6 -0
  211. package/dist/providers/index.d.ts.map +1 -0
  212. package/dist/providers/index.js +6 -0
  213. package/dist/providers/index.js.map +1 -0
  214. package/dist/providers/instrumented.d.ts +28 -0
  215. package/dist/providers/instrumented.d.ts.map +1 -0
  216. package/dist/providers/instrumented.js +147 -0
  217. package/dist/providers/instrumented.js.map +1 -0
  218. package/dist/providers/registry.d.ts +26 -0
  219. package/dist/providers/registry.d.ts.map +1 -0
  220. package/dist/providers/registry.js +32 -0
  221. package/dist/providers/registry.js.map +1 -0
  222. package/dist/providers/resolvers.d.ts +58 -0
  223. package/dist/providers/resolvers.d.ts.map +1 -0
  224. package/dist/providers/resolvers.js +78 -0
  225. package/dist/providers/resolvers.js.map +1 -0
  226. package/package.json +39 -0
@@ -0,0 +1,203 @@
1
+ import { FRAGMENTS } from '@cat-factory/prompt-fragments';
2
+ import { ValidationError } from '@cat-factory/kernel';
3
+ import { DeterministicFragmentSelector } from './DeterministicFragmentSelector.js';
4
+ import { entryToFragment, mergeCatalog, toSelectable, } from './fragment-catalog.js';
5
+ import { slugFromPath } from './fragment-source.logic.js';
6
+ /**
7
+ * The fragment library's management service (ADR 0006). It owns the per-tier CRUD
8
+ * (account / workspace) and resolves the merged catalog a workspace sees (built-in ∪
9
+ * account ∪ workspace, override-by-id, tombstone-suppressed). It still implements
10
+ * {@link FragmentResolver} (`resolveForRun`), but the execution engine NO LONGER
11
+ * consumes it: fragment delivery is now the service-scoped `serviceFragmentIds` folded
12
+ * into `code-aware` agents, so this is a management surface only.
13
+ */
14
+ export class FragmentLibraryService {
15
+ repo;
16
+ workspaces;
17
+ clock;
18
+ selector;
19
+ builtins;
20
+ constructor(deps) {
21
+ this.repo = deps.promptFragmentRepository;
22
+ this.workspaces = deps.workspaceRepository;
23
+ this.clock = deps.clock;
24
+ this.selector = deps.selector ?? new DeterministicFragmentSelector();
25
+ this.builtins = deps.builtins ?? FRAGMENTS;
26
+ }
27
+ /** This tier's hand-authored/sourced fragments (raw, not merged), newest first. */
28
+ async listTier(ownerKind, ownerId) {
29
+ const rows = await this.repo.listByOwner(ownerKind, ownerId);
30
+ return rows.sort((a, b) => b.updatedAt - a.updatedAt).map((row) => recordToWire(row));
31
+ }
32
+ /** Create a hand-authored fragment at a tier. */
33
+ async create(ownerKind, ownerId, input) {
34
+ const fragmentId = (input.id ?? slugFromPath(input.title)).trim();
35
+ if (!fragmentId)
36
+ throw new ValidationError('A fragment id (or a non-empty title) is required');
37
+ const now = this.clock.now();
38
+ const record = {
39
+ fragmentId,
40
+ ownerKind,
41
+ ownerId,
42
+ version: input.version ?? '1.0.0',
43
+ title: input.title.trim(),
44
+ category: input.category?.trim() || null,
45
+ summary: input.summary.trim(),
46
+ body: input.body.trim(),
47
+ appliesTo: input.appliesTo ?? null,
48
+ tags: input.tags && input.tags.length ? input.tags : null,
49
+ sourceId: null,
50
+ sourcePath: null,
51
+ sourceSha: null,
52
+ createdAt: now,
53
+ updatedAt: now,
54
+ deletedAt: null,
55
+ };
56
+ await this.repo.upsert(record);
57
+ return recordToWire(record);
58
+ }
59
+ /**
60
+ * Edit a fragment at a tier. If no row exists yet (e.g. shadowing a built-in or
61
+ * inherited account fragment), the patch must carry enough to stand alone
62
+ * (title, summary, body); otherwise it merges over the existing record.
63
+ */
64
+ async update(ownerKind, ownerId, fragmentId, patch) {
65
+ const existing = await this.repo.get(ownerKind, ownerId, fragmentId);
66
+ const now = this.clock.now();
67
+ const base = existing ?? {
68
+ fragmentId,
69
+ ownerKind,
70
+ ownerId,
71
+ version: '1.0.0',
72
+ title: fragmentId,
73
+ category: null,
74
+ summary: '',
75
+ body: '',
76
+ appliesTo: null,
77
+ tags: null,
78
+ sourceId: null,
79
+ sourcePath: null,
80
+ sourceSha: null,
81
+ createdAt: now,
82
+ updatedAt: now,
83
+ deletedAt: null,
84
+ };
85
+ const next = {
86
+ ...base,
87
+ version: patch.version?.trim() ?? base.version,
88
+ title: patch.title?.trim() ?? base.title,
89
+ category: patch.category !== undefined ? patch.category.trim() || null : base.category,
90
+ summary: patch.summary?.trim() ?? base.summary,
91
+ body: patch.body?.trim() ?? base.body,
92
+ appliesTo: patch.appliesTo !== undefined ? patch.appliesTo : base.appliesTo,
93
+ tags: patch.tags !== undefined ? (patch.tags.length ? patch.tags : null) : base.tags,
94
+ updatedAt: now,
95
+ deletedAt: null, // editing un-suppresses
96
+ };
97
+ if (!next.summary || !next.body) {
98
+ throw new ValidationError('A fragment needs a summary and a body');
99
+ }
100
+ await this.repo.upsert(next);
101
+ return recordToWire(next);
102
+ }
103
+ /**
104
+ * Tombstone a fragment at a tier. Suppresses an inherited built-in/account
105
+ * fragment, or removes a hand-authored one. Idempotent — writing a tombstone
106
+ * even when no row exists yet, so a tier can suppress something it inherits.
107
+ */
108
+ async remove(ownerKind, ownerId, fragmentId) {
109
+ const now = this.clock.now();
110
+ const existing = await this.repo.get(ownerKind, ownerId, fragmentId);
111
+ if (existing) {
112
+ await this.repo.softDelete(ownerKind, ownerId, fragmentId, now);
113
+ return;
114
+ }
115
+ await this.repo.upsert({
116
+ fragmentId,
117
+ ownerKind,
118
+ ownerId,
119
+ version: '1.0.0',
120
+ title: fragmentId,
121
+ category: null,
122
+ summary: '',
123
+ body: '',
124
+ appliesTo: null,
125
+ tags: null,
126
+ sourceId: null,
127
+ sourcePath: null,
128
+ sourceSha: null,
129
+ createdAt: now,
130
+ updatedAt: now,
131
+ deletedAt: now,
132
+ });
133
+ }
134
+ /** Resolve the merged catalog a workspace sees (built-in ∪ account ∪ workspace). */
135
+ async resolveCatalog(workspaceId) {
136
+ const accountId = await this.workspaces.accountOf(workspaceId);
137
+ const [accountRows, workspaceRows] = await Promise.all([
138
+ accountId ? this.repo.listByOwner('account', accountId, true) : Promise.resolve([]),
139
+ this.repo.listByOwner('workspace', workspaceId, true),
140
+ ]);
141
+ return mergeCatalog(this.builtins, accountRows, workspaceRows);
142
+ }
143
+ /** The merged catalog as wire {@link ResolvedFragment}s for the management UI. */
144
+ async resolvedCatalog(workspaceId) {
145
+ const entries = await this.resolveCatalog(workspaceId);
146
+ return entries.map((entry) => ({ ...entryToFragment(entry), tier: entry.tier }));
147
+ }
148
+ /** {@link FragmentResolver}: pick + resolve the fragments to inject for a run. */
149
+ async resolveForRun(input) {
150
+ const catalog = await this.resolveCatalog(input.workspaceId);
151
+ const byId = new Map(catalog.map((e) => [e.id, e]));
152
+ let picked = [];
153
+ try {
154
+ picked = await this.selector.select(catalog.map(toSelectable), {
155
+ workspaceId: input.workspaceId,
156
+ agentKind: input.agentKind,
157
+ blockType: input.blockType,
158
+ blockTitle: input.blockTitle,
159
+ blockDescription: input.blockDescription,
160
+ signals: input.signals,
161
+ });
162
+ }
163
+ catch {
164
+ picked = []; // selection never blocks a run; manual pins still apply
165
+ }
166
+ // Manual pins are authoritative and always included; the selector's picks
167
+ // union with them. Both are validated against the catalog so a stale id is
168
+ // simply dropped rather than breaking the run.
169
+ const selectedIds = [];
170
+ const seen = new Set();
171
+ for (const id of [...input.manualIds, ...picked]) {
172
+ if (seen.has(id) || !byId.has(id))
173
+ continue;
174
+ seen.add(id);
175
+ selectedIds.push(id);
176
+ }
177
+ // Emit in stable catalog order for replay-stable prompts.
178
+ const ordered = catalog.filter((e) => seen.has(e.id));
179
+ return {
180
+ fragments: ordered.map((e) => ({ id: e.id, body: e.body })),
181
+ selectedIds: ordered.map((e) => e.id),
182
+ };
183
+ }
184
+ }
185
+ function recordToWire(record) {
186
+ const fragment = {
187
+ id: record.fragmentId,
188
+ version: record.version,
189
+ title: record.title,
190
+ category: record.category ?? '',
191
+ summary: record.summary,
192
+ body: record.body,
193
+ };
194
+ if (record.appliesTo)
195
+ fragment.appliesTo = record.appliesTo;
196
+ if (record.tags && record.tags.length)
197
+ fragment.tags = record.tags;
198
+ if (record.sourceId && record.sourcePath !== null && record.sourceSha !== null) {
199
+ fragment.source = { sourceId: record.sourceId, path: record.sourcePath, sha: record.sourceSha };
200
+ }
201
+ return fragment;
202
+ }
203
+ //# sourceMappingURL=FragmentLibraryService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FragmentLibraryService.js","sourceRoot":"","sources":["../../src/fragmentLibrary/FragmentLibraryService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { FragmentOwnerKind, FragmentSource, FragmentSourceStatus, FragmentSyncResult, LinkFragmentSourceInput } from '@cat-factory/kernel';
2
+ import type { Clock, IdGenerator } from '@cat-factory/kernel';
3
+ import type { GitHubClient } from '@cat-factory/kernel';
4
+ import type { FragmentSourceRepository, PromptFragmentRepository } from '@cat-factory/kernel';
5
+ /**
6
+ * Resolve the GitHub App installation id that can read a tier's repos. A
7
+ * workspace-owned source reads through the workspace's installation; an
8
+ * account-owned one through the account's. Returns null when no installation is
9
+ * available, so a sync fails with a clear error rather than a silent empty pull.
10
+ */
11
+ export type ResolveFragmentInstallationId = (ownerKind: FragmentOwnerKind, ownerId: string) => Promise<number | null>;
12
+ export interface FragmentSourceServiceDependencies {
13
+ fragmentSourceRepository: FragmentSourceRepository;
14
+ promptFragmentRepository: PromptFragmentRepository;
15
+ githubClient: GitHubClient;
16
+ resolveInstallationId: ResolveFragmentInstallationId;
17
+ idGenerator: IdGenerator;
18
+ clock: Clock;
19
+ }
20
+ /**
21
+ * Repo-sourced fragments (ADR 0006 §3, §6): link a repo directory of Markdown
22
+ * guidelines to a tier, resync it (read the tree, upsert changed files, tombstone
23
+ * removed ones), and answer the cheap "check for changes" without writing. Reads
24
+ * go through the account's existing GitHub installation — no new credential store.
25
+ * Sync runs inline (bounded directory); the sha-keyed upsert is idempotent so it
26
+ * can later move behind a Workflow safely.
27
+ */
28
+ export declare class FragmentSourceService {
29
+ private readonly deps;
30
+ constructor(deps: FragmentSourceServiceDependencies);
31
+ /** Linked sources for a tier + their last-synced state. */
32
+ list(ownerKind: FragmentOwnerKind, ownerId: string): Promise<FragmentSource[]>;
33
+ /** Link a repo directory as a fragment source. Does not sync (call {@link sync}). */
34
+ link(ownerKind: FragmentOwnerKind, ownerId: string, input: LinkFragmentSourceInput): Promise<FragmentSource>;
35
+ /** Unlink a source and tombstone every fragment it produced. */
36
+ unlink(sourceId: string): Promise<void>;
37
+ /**
38
+ * Resync a source: list the directory, upsert every Markdown file whose blob
39
+ * sha changed, tombstone files removed upstream, and stamp the new tree digest.
40
+ * Idempotent — re-running with no upstream change touches nothing.
41
+ */
42
+ sync(sourceId: string): Promise<FragmentSyncResult>;
43
+ /** Cheap "check for changes": compare the remote tree digest to the stored one. */
44
+ status(sourceId: string): Promise<FragmentSourceStatus>;
45
+ private require;
46
+ /** List the source directory and keep only Markdown files (with their shas). */
47
+ private readMarkdown;
48
+ /** Read, parse and upsert one file as a fragment owned by the source's tier. */
49
+ private syncEntry;
50
+ }
51
+ //# sourceMappingURL=FragmentSourceService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FragmentSourceService.d.ts","sourceRoot":"","sources":["../../src/fragmentLibrary/FragmentSourceService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,qBAAqB,CAAA;AACzE,OAAO,KAAK,EAEV,wBAAwB,EAExB,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAQ5B;;;;;GAKG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC1C,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;AAE3B,MAAM,WAAW,iCAAiC;IAChD,wBAAwB,EAAE,wBAAwB,CAAA;IAClD,wBAAwB,EAAE,wBAAwB,CAAA;IAClD,YAAY,EAAE,YAAY,CAAA;IAC1B,qBAAqB,EAAE,6BAA6B,CAAA;IACpD,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;CACb;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,iCAAiC,EAAI;IAExE,2DAA2D;IACrD,IAAI,CAAC,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGnF;IAED,qFAAqF;IAC/E,IAAI,CACR,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAiBzB;IAED,gEAAgE;IAC1D,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5C;IAED;;;;OAIG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuCxD;IAED,mFAAmF;IAC7E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAwB5D;YAIa,OAAO;IAUrB,gFAAgF;YAClE,YAAY;IAgB1B,gFAAgF;YAClE,SAAS;CA2CxB"}
@@ -0,0 +1,181 @@
1
+ import { NotFoundError, ValidationError, assertFound } from '@cat-factory/kernel';
2
+ import { digestListing, isMarkdownFile, parseFragmentMarkdown, slugFromPath, } from './fragment-source.logic.js';
3
+ /**
4
+ * Repo-sourced fragments (ADR 0006 §3, §6): link a repo directory of Markdown
5
+ * guidelines to a tier, resync it (read the tree, upsert changed files, tombstone
6
+ * removed ones), and answer the cheap "check for changes" without writing. Reads
7
+ * go through the account's existing GitHub installation — no new credential store.
8
+ * Sync runs inline (bounded directory); the sha-keyed upsert is idempotent so it
9
+ * can later move behind a Workflow safely.
10
+ */
11
+ export class FragmentSourceService {
12
+ deps;
13
+ constructor(deps) {
14
+ this.deps = deps;
15
+ }
16
+ /** Linked sources for a tier + their last-synced state. */
17
+ async list(ownerKind, ownerId) {
18
+ const rows = await this.deps.fragmentSourceRepository.listByOwner(ownerKind, ownerId);
19
+ return rows.map(toWire);
20
+ }
21
+ /** Link a repo directory as a fragment source. Does not sync (call {@link sync}). */
22
+ async link(ownerKind, ownerId, input) {
23
+ const now = this.deps.clock.now();
24
+ const record = {
25
+ id: this.deps.idGenerator.next('frgsrc'),
26
+ ownerKind,
27
+ ownerId,
28
+ repoOwner: input.repoOwner.trim(),
29
+ repoName: input.repoName.trim(),
30
+ gitRef: input.gitRef?.trim() || 'HEAD',
31
+ dirPath: normalizeDir(input.dirPath),
32
+ lastSyncedSha: null,
33
+ lastSyncedAt: null,
34
+ createdAt: now,
35
+ deletedAt: null,
36
+ };
37
+ await this.deps.fragmentSourceRepository.upsert(record);
38
+ return toWire(record);
39
+ }
40
+ /** Unlink a source and tombstone every fragment it produced. */
41
+ async unlink(sourceId) {
42
+ const source = await this.require(sourceId);
43
+ const now = this.deps.clock.now();
44
+ const fragments = await this.deps.promptFragmentRepository.listBySource(sourceId);
45
+ for (const f of fragments) {
46
+ await this.deps.promptFragmentRepository.softDelete(f.ownerKind, f.ownerId, f.fragmentId, now);
47
+ }
48
+ await this.deps.fragmentSourceRepository.softDelete(source.id, now);
49
+ }
50
+ /**
51
+ * Resync a source: list the directory, upsert every Markdown file whose blob
52
+ * sha changed, tombstone files removed upstream, and stamp the new tree digest.
53
+ * Idempotent — re-running with no upstream change touches nothing.
54
+ */
55
+ async sync(sourceId) {
56
+ const source = await this.require(sourceId);
57
+ const entries = await this.readMarkdown(source);
58
+ const existing = await this.deps.promptFragmentRepository.listBySource(sourceId);
59
+ const existingByPath = new Map(existing.map((f) => [f.sourcePath ?? '', f]));
60
+ const now = this.deps.clock.now();
61
+ let upserted = 0;
62
+ let unchanged = 0;
63
+ const seenPaths = new Set();
64
+ for (const entry of entries) {
65
+ seenPaths.add(entry.path);
66
+ const prior = existingByPath.get(entry.path);
67
+ if (prior && prior.sourceSha === entry.sha) {
68
+ unchanged++;
69
+ continue;
70
+ }
71
+ await this.syncEntry(source, entry, prior, now);
72
+ upserted++;
73
+ }
74
+ // Tombstone fragments whose source file disappeared upstream.
75
+ let tombstoned = 0;
76
+ for (const f of existing) {
77
+ if (f.sourcePath && !seenPaths.has(f.sourcePath)) {
78
+ await this.deps.promptFragmentRepository.softDelete(f.ownerKind, f.ownerId, f.fragmentId, now);
79
+ tombstoned++;
80
+ }
81
+ }
82
+ const lastSyncedSha = digestListing(entries.map((e) => ({ path: e.path, sha: e.sha })));
83
+ await this.deps.fragmentSourceRepository.updateSyncState(source.id, lastSyncedSha, now);
84
+ return { upserted, tombstoned, unchanged, lastSyncedSha };
85
+ }
86
+ /** Cheap "check for changes": compare the remote tree digest to the stored one. */
87
+ async status(sourceId) {
88
+ const source = await this.require(sourceId);
89
+ const entries = await this.readMarkdown(source);
90
+ const existing = await this.deps.promptFragmentRepository.listBySource(sourceId);
91
+ const existingByPath = new Map(existing.map((f) => [f.sourcePath ?? '', f]));
92
+ let changedCount = 0;
93
+ const seen = new Set();
94
+ for (const entry of entries) {
95
+ seen.add(entry.path);
96
+ const prior = existingByPath.get(entry.path);
97
+ if (!prior || prior.sourceSha !== entry.sha)
98
+ changedCount++;
99
+ }
100
+ for (const f of existing) {
101
+ if (f.sourcePath && !seen.has(f.sourcePath))
102
+ changedCount++;
103
+ }
104
+ const remoteSha = digestListing(entries.map((e) => ({ path: e.path, sha: e.sha })));
105
+ return {
106
+ changed: remoteSha !== source.lastSyncedSha,
107
+ changedCount,
108
+ lastSyncedSha: source.lastSyncedSha,
109
+ remoteSha,
110
+ };
111
+ }
112
+ // --- internals ----------------------------------------------------------
113
+ async require(sourceId) {
114
+ const source = assertFound(await this.deps.fragmentSourceRepository.get(sourceId), 'FragmentSource', sourceId);
115
+ if (source.deletedAt !== null)
116
+ throw new NotFoundError('FragmentSource', sourceId);
117
+ return source;
118
+ }
119
+ /** List the source directory and keep only Markdown files (with their shas). */
120
+ async readMarkdown(source) {
121
+ const installationId = await this.deps.resolveInstallationId(source.ownerKind, source.ownerId);
122
+ if (installationId === null) {
123
+ throw new ValidationError('No GitHub installation is available for this scope; connect GitHub before syncing a source');
124
+ }
125
+ const entries = await this.deps.githubClient.listDirectory(installationId, { owner: source.repoOwner, repo: source.repoName }, source.dirPath, source.gitRef);
126
+ return entries.filter((e) => e.type === 'file' && isMarkdownFile(e.name));
127
+ }
128
+ /** Read, parse and upsert one file as a fragment owned by the source's tier. */
129
+ async syncEntry(source, entry, prior, now) {
130
+ const installationId = await this.deps.resolveInstallationId(source.ownerKind, source.ownerId);
131
+ if (installationId === null) {
132
+ throw new ValidationError('No GitHub installation is available for this scope');
133
+ }
134
+ const file = await this.deps.githubClient.getFileContent(installationId, { owner: source.repoOwner, repo: source.repoName }, entry.path, source.gitRef);
135
+ if (!file)
136
+ return;
137
+ const parsed = parseFragmentMarkdown(entry.path, file.content);
138
+ if (!parsed)
139
+ return;
140
+ // Sourced ids are namespaced so two sources can't collide; an explicit
141
+ // frontmatter `id` instead *shadows* a built-in/inherited fragment (ADR 0006).
142
+ const fragmentId = parsed.id?.trim() || `src:${source.id}:${slugFromPath(entry.path)}`;
143
+ const record = {
144
+ fragmentId,
145
+ ownerKind: source.ownerKind,
146
+ ownerId: source.ownerId,
147
+ version: prior?.version ?? '1.0.0',
148
+ title: parsed.title,
149
+ category: parsed.category ?? null,
150
+ summary: parsed.summary,
151
+ body: parsed.body,
152
+ appliesTo: parsed.appliesTo ?? null,
153
+ tags: parsed.tags && parsed.tags.length ? parsed.tags : null,
154
+ sourceId: source.id,
155
+ sourcePath: entry.path,
156
+ sourceSha: file.sha,
157
+ createdAt: prior?.createdAt ?? now,
158
+ updatedAt: now,
159
+ deletedAt: null,
160
+ };
161
+ await this.deps.promptFragmentRepository.upsert(record);
162
+ }
163
+ }
164
+ function normalizeDir(dirPath) {
165
+ return (dirPath ?? '').replace(/^\/+|\/+$/g, '');
166
+ }
167
+ function toWire(record) {
168
+ return {
169
+ id: record.id,
170
+ ownerKind: record.ownerKind,
171
+ ownerId: record.ownerId,
172
+ repoOwner: record.repoOwner,
173
+ repoName: record.repoName,
174
+ gitRef: record.gitRef,
175
+ dirPath: record.dirPath,
176
+ lastSyncedSha: record.lastSyncedSha,
177
+ lastSyncedAt: record.lastSyncedAt,
178
+ createdAt: record.createdAt,
179
+ };
180
+ }
181
+ //# sourceMappingURL=FragmentSourceService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FragmentSourceService.js","sourceRoot":"","sources":["../../src/fragmentLibrary/FragmentSourceService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type FragmentSelectionContext, type FragmentSelector, type ModelProvider, type ModelProviderResolver, type ModelRef, type SelectableFragment } from '@cat-factory/kernel';
2
+ export interface LlmFragmentSelectorDependencies {
3
+ /** Resolve a {@link ModelProvider} for the run's workspace scope (DB key pool). */
4
+ modelProviderResolver?: ModelProviderResolver;
5
+ /** Static provider (e.g. a fake in tests); used when no resolver is set. */
6
+ modelProvider?: ModelProvider;
7
+ modelRef: ModelRef;
8
+ }
9
+ /**
10
+ * LLM-backed {@link FragmentSelector} (ADR 0006 §5): asks the configured model to
11
+ * pick the relevant fragment ids from the candidates' summaries (bodies are never
12
+ * sent — summaries keep the call cheap). Degrades gracefully to the deterministic
13
+ * matcher if the model is unavailable or its response can't be parsed, so review
14
+ * never blocks on the selector and offline/test runs stay deterministic.
15
+ */
16
+ export declare class LlmFragmentSelector implements FragmentSelector {
17
+ private readonly deps;
18
+ constructor(deps: LlmFragmentSelectorDependencies);
19
+ select(candidates: SelectableFragment[], context: FragmentSelectionContext): Promise<string[]>;
20
+ }
21
+ //# sourceMappingURL=LlmFragmentSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LlmFragmentSelector.d.ts","sourceRoot":"","sources":["../../src/fragmentLibrary/LlmFragmentSelector.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAA;AAQ5B,MAAM,WAAW,+BAA+B;IAC9C,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;IAC7C,4EAA4E;IAC5E,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,QAAQ,EAAE,QAAQ,CAAA;CACnB;AA4CD;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,gBAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,+BAA+B,EAAI;IAEhE,MAAM,CACV,UAAU,EAAE,kBAAkB,EAAE,EAChC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBnB;CACF"}
@@ -0,0 +1,87 @@
1
+ import {} from '@cat-factory/kernel';
2
+ import { generateText } from 'ai';
3
+ import { catFactoryObservability } from '../providers/instrumented.js';
4
+ import { selectDeterministic } from './fragment-catalog.js';
5
+ const SYSTEM_PROMPT = 'You select which best-practice guideline fragments are relevant to a coding task. ' +
6
+ 'You are given the task context and a list of candidate fragments (id, title, summary, tags). ' +
7
+ 'Return ONLY a JSON array of the ids that are relevant — no prose, no code fences. ' +
8
+ 'Prefer precision: a frontend-only task should not pull database or backend guidelines.';
9
+ function buildPrompt(candidates, context) {
10
+ const list = candidates.map((c) => ({
11
+ id: c.id,
12
+ title: c.title,
13
+ summary: c.summary,
14
+ tags: c.tags ?? [],
15
+ }));
16
+ return [
17
+ `Agent role: ${context.agentKind}`,
18
+ `Block type: ${context.blockType}`,
19
+ `Block: ${context.blockTitle}`,
20
+ context.blockDescription ? `Description: ${context.blockDescription}` : '',
21
+ context.signals.length ? `Recent work / context:\n${context.signals.join('\n---\n')}` : '',
22
+ '',
23
+ 'Candidate fragments:',
24
+ JSON.stringify(list, null, 2),
25
+ '',
26
+ 'Respond with a JSON array of the relevant fragment ids, e.g. ["node.performance"].',
27
+ ]
28
+ .filter(Boolean)
29
+ .join('\n');
30
+ }
31
+ function extractIds(text, valid) {
32
+ const start = text.indexOf('[');
33
+ const end = text.lastIndexOf(']');
34
+ if (start === -1 || end <= start)
35
+ return null;
36
+ try {
37
+ const parsed = JSON.parse(text.slice(start, end + 1));
38
+ if (!Array.isArray(parsed))
39
+ return null;
40
+ return parsed.filter((id) => typeof id === 'string' && valid.has(id));
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ /**
47
+ * LLM-backed {@link FragmentSelector} (ADR 0006 §5): asks the configured model to
48
+ * pick the relevant fragment ids from the candidates' summaries (bodies are never
49
+ * sent — summaries keep the call cheap). Degrades gracefully to the deterministic
50
+ * matcher if the model is unavailable or its response can't be parsed, so review
51
+ * never blocks on the selector and offline/test runs stay deterministic.
52
+ */
53
+ export class LlmFragmentSelector {
54
+ deps;
55
+ constructor(deps) {
56
+ this.deps = deps;
57
+ }
58
+ async select(candidates, context) {
59
+ if (candidates.length === 0)
60
+ return [];
61
+ const fallback = () => selectDeterministic(candidates, context);
62
+ try {
63
+ const provider = this.deps.modelProviderResolver
64
+ ? await this.deps.modelProviderResolver.forScope({ workspaceId: context.workspaceId })
65
+ : this.deps.modelProvider;
66
+ if (!provider)
67
+ return fallback();
68
+ const model = provider.resolve(this.deps.modelRef);
69
+ const { text } = await generateText({
70
+ model,
71
+ system: SYSTEM_PROMPT,
72
+ prompt: buildPrompt(candidates, context),
73
+ temperature: 0,
74
+ // Headroom for a reasoning model's `<think>` before the (small) id list —
75
+ // a tight cap truncates the output before any ids are emitted.
76
+ maxOutputTokens: 5000,
77
+ providerOptions: catFactoryObservability({ agentKind: 'fragment-selector' }),
78
+ });
79
+ const ids = extractIds(text, new Set(candidates.map((c) => c.id)));
80
+ return ids ?? fallback();
81
+ }
82
+ catch {
83
+ return fallback();
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=LlmFragmentSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LlmFragmentSelector.js","sourceRoot":"","sources":["../../src/fragmentLibrary/LlmFragmentSelector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { PromptFragment } from '@cat-factory/contracts';
2
+ import type { AgentKind, BlockType, FragmentTier } from '@cat-factory/kernel';
3
+ import type { FragmentSelectionContext, SelectableFragment } from '@cat-factory/kernel';
4
+ import type { PromptFragmentRecord } from '@cat-factory/kernel';
5
+ /** A fragment after the three tiers are merged, carrying its winning tier. */
6
+ export interface ResolvedCatalogEntry {
7
+ id: string;
8
+ version: string;
9
+ title: string;
10
+ category: string | null;
11
+ summary: string;
12
+ body: string;
13
+ appliesTo: {
14
+ blockTypes?: BlockType[];
15
+ agentKinds?: AgentKind[];
16
+ } | null;
17
+ tags: string[] | null;
18
+ source: {
19
+ sourceId: string;
20
+ path: string;
21
+ sha: string;
22
+ } | null;
23
+ tier: FragmentTier;
24
+ }
25
+ /**
26
+ * Merge the three tiers into one catalog. Later tiers override earlier ones by
27
+ * stable id (workspace > account > built-in); a tombstoned record at any tier
28
+ * *suppresses* the id outright. Returns live entries sorted by id.
29
+ */
30
+ export declare function mergeCatalog(builtins: PromptFragment[], accountRows: PromptFragmentRecord[], workspaceRows: PromptFragmentRecord[]): ResolvedCatalogEntry[];
31
+ /** Reduce a resolved entry to the metadata the selector reasons over (no body). */
32
+ export declare function toSelectable(entry: ResolvedCatalogEntry): SelectableFragment;
33
+ /** A resolved entry → the wire {@link PromptFragment} (+ tier handled by caller). */
34
+ export declare function entryToFragment(entry: ResolvedCatalogEntry): PromptFragment;
35
+ /**
36
+ * The deterministic fallback selector (ADR 0006 §5): keep every candidate whose
37
+ * `appliesTo` gate admits the run, then — when any candidate carries tags —
38
+ * prefer those whose tags intersect the run's signals (block type, title,
39
+ * description, prior outputs). With no tags anywhere, the gate alone decides, so
40
+ * an offline/test run is fully deterministic and never blocks.
41
+ */
42
+ export declare function selectDeterministic(candidates: SelectableFragment[], context: FragmentSelectionContext): string[];
43
+ //# sourceMappingURL=fragment-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragment-catalog.d.ts","sourceRoot":"","sources":["../../src/fragmentLibrary/fragment-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAM/D,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE;QAAE,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;IACxE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC9D,IAAI,EAAE,YAAY,CAAA;CACnB;AAqCD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,cAAc,EAAE,EAC1B,WAAW,EAAE,oBAAoB,EAAE,EACnC,aAAa,EAAE,oBAAoB,EAAE,GACpC,oBAAoB,EAAE,CAMxB;AAgBD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,KAAK,EAAE,oBAAoB,GAAG,kBAAkB,CAU5E;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,cAAc,CAa3E;AAcD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,kBAAkB,EAAE,EAChC,OAAO,EAAE,wBAAwB,GAChC,MAAM,EAAE,CAqBV"}