@happyvertical/smrt-prompts 0.51.21 → 0.51.29

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 (40) hide show
  1. package/AGENTS.md +34 -1
  2. package/README.md +148 -2
  3. package/dist/__smrt-register__.d.ts +2 -0
  4. package/dist/__smrt-register__.d.ts.map +1 -0
  5. package/dist/cache.d.ts +33 -0
  6. package/dist/cache.d.ts.map +1 -0
  7. package/dist/collections/PromptOverrideCollection.d.ts +45 -0
  8. package/dist/collections/PromptOverrideCollection.d.ts.map +1 -0
  9. package/dist/index.d.ts +12 -174
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +298 -30
  12. package/dist/index.js.map +1 -1
  13. package/dist/manifest.json +117 -28
  14. package/dist/models/PromptOverride.d.ts +32 -0
  15. package/dist/models/PromptOverride.d.ts.map +1 -0
  16. package/dist/prompt-override-service.d.ts +56 -0
  17. package/dist/prompt-override-service.d.ts.map +1 -0
  18. package/dist/prompt-registry.d.ts +13 -0
  19. package/dist/prompt-registry.d.ts.map +1 -0
  20. package/dist/prompt-resolver.d.ts +3 -0
  21. package/dist/prompt-resolver.d.ts.map +1 -0
  22. package/dist/prompt-settings.d.ts +188 -0
  23. package/dist/prompt-settings.d.ts.map +1 -0
  24. package/dist/smrt-knowledge.json +108 -248
  25. package/dist/svelte/components/PromptSettingsPanel.svelte +353 -0
  26. package/dist/svelte/components/PromptSettingsPanel.svelte.d.ts +52 -0
  27. package/dist/svelte/components/PromptSettingsPanel.svelte.d.ts.map +1 -0
  28. package/dist/svelte/index.d.ts +17 -0
  29. package/dist/svelte/index.d.ts.map +1 -0
  30. package/dist/svelte/index.js +14 -0
  31. package/dist/svelte/types.d.ts +75 -0
  32. package/dist/svelte/types.d.ts.map +1 -0
  33. package/dist/svelte/types.js +42 -0
  34. package/dist/types.d.ts +107 -110
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +6 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/utils.d.ts +29 -0
  39. package/dist/utils.d.ts.map +1 -0
  40. package/package.json +27 -8
@@ -3,26 +3,33 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-prompts",
6
- "packageVersion": "0.51.21",
6
+ "packageVersion": "0.51.29",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "37a72f3e7c90995d64c9f85ab7015996dfb34ab1da3175a09e10df3c3a96f1c2",
11
- "packageJson": "3533a95d111ce54ed6a6deb9847735c81837cbba5f61153bc3f6bef40ecb0721",
12
- "agents": "f273f3ce4ed80310d9afdea484314a455a4f7927f79ad8d92a4dff37c20233fa"
10
+ "manifest": "5f2cdbd24dd366b89f64319285fe0241935c2f4fd082ee92c04cfb9b2fb5e398",
11
+ "packageJson": "62e12bb3d7649acc05d4b2ac3ef343427a7c4e55be6fa6b48d353a692f5fa853",
12
+ "agents": "45d48bbf65b540f94cd3a9c261f21c883d3f5fef0681931d5438842cf1727a66"
13
13
  },
14
14
  "exports": [
15
15
  ".",
16
16
  "./manifest",
17
- "./manifest.json"
17
+ "./manifest.json",
18
+ "./svelte"
18
19
  ],
19
20
  "dependencies": {
20
21
  "@happyvertical/smrt-config": "workspace:*",
21
22
  "@happyvertical/smrt-core": "workspace:*",
22
23
  "@happyvertical/smrt-tenancy": "workspace:*",
24
+ "@happyvertical/smrt-ui": "workspace:*",
23
25
  "@happyvertical/sql": "catalog:",
24
26
  "@happyvertical/smrt-vitest": "workspace:*",
27
+ "@sveltejs/package": "^2.5.8",
28
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
25
29
  "@types/node": "24.13.6",
30
+ "jsdom": "^29.1.1",
31
+ "svelte": "^5.57.1",
32
+ "svelte-check": "^4.7.6",
26
33
  "typescript": "5.9.3",
27
34
  "vite": "8.1.4",
28
35
  "vitest": "4.1.10"
@@ -31,6 +38,7 @@
31
38
  "@happyvertical/smrt-config",
32
39
  "@happyvertical/smrt-core",
33
40
  "@happyvertical/smrt-tenancy",
41
+ "@happyvertical/smrt-ui",
34
42
  "@happyvertical/smrt-vitest"
35
43
  ],
36
44
  "sdkDependencies": [
@@ -58,11 +66,25 @@
58
66
  "fields": [],
59
67
  "relationships": [],
60
68
  "methods": [
69
+ "findByScope",
61
70
  "getAppOverride",
71
+ "getOverrideMapForKeys",
62
72
  "getResolutionLayers",
63
- "getTenantOverride"
73
+ "getTenantOverride",
74
+ "removeOverride",
75
+ "setTemplateOverride"
64
76
  ],
65
77
  "methodSignatures": [
78
+ {
79
+ "name": "findByScope",
80
+ "async": true,
81
+ "params": [
82
+ "key: string",
83
+ "scopeType: PromptOverrideScopeType",
84
+ "scopeId: string"
85
+ ],
86
+ "returns": "Promise<PromptOverride | null>"
87
+ },
66
88
  {
67
89
  "name": "getAppOverride",
68
90
  "async": true,
@@ -72,6 +94,16 @@
72
94
  ],
73
95
  "returns": "Promise<PromptOverride | null>"
74
96
  },
97
+ {
98
+ "name": "getOverrideMapForKeys",
99
+ "async": true,
100
+ "params": [
101
+ "keys: string[]",
102
+ "scopeType: PromptOverrideScopeType",
103
+ "scopeId: string"
104
+ ],
105
+ "returns": "Promise<Map<string, PromptOverride>>"
106
+ },
75
107
  {
76
108
  "name": "getResolutionLayers",
77
109
  "async": true,
@@ -91,67 +123,78 @@
91
123
  "options?: object"
92
124
  ],
93
125
  "returns": "Promise<PromptOverride | null>"
94
- }
95
- ],
96
- "surfaces": [
97
- {
98
- "kind": "api",
99
- "name": "promptoverrides.getAppOverride",
100
- "operation": "getAppOverride",
101
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection",
102
- "path": "/promptoverrides/getAppOverride",
103
- "method": "POST"
104
126
  },
105
127
  {
106
- "kind": "api",
107
- "name": "promptoverrides.getTenantOverride",
108
- "operation": "getTenantOverride",
109
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection",
110
- "path": "/promptoverrides/getTenantOverride",
111
- "method": "POST"
128
+ "name": "removeOverride",
129
+ "async": true,
130
+ "params": [
131
+ "key: string",
132
+ "scopeType: PromptOverrideScopeType",
133
+ "scopeId: string"
134
+ ],
135
+ "returns": "Promise<boolean>"
112
136
  },
137
+ {
138
+ "name": "setTemplateOverride",
139
+ "async": true,
140
+ "params": [
141
+ "key: string",
142
+ "scopeType: PromptOverrideScopeType",
143
+ "scopeId: string",
144
+ "template: string | null"
145
+ ],
146
+ "returns": "Promise<PromptOverride>"
147
+ }
148
+ ],
149
+ "surfaces": [],
150
+ "withheldSurfaces": [
113
151
  {
114
152
  "kind": "api",
115
- "name": "promptoverrides.getResolutionLayers",
116
- "operation": "getResolutionLayers",
117
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection",
118
- "path": "/promptoverrides/getResolutionLayers",
119
- "method": "POST"
153
+ "operation": "findByScope",
154
+ "code": "api-disabled",
155
+ "reason": "api is disabled",
156
+ "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
120
157
  },
121
158
  {
122
- "kind": "cli",
123
- "name": "promptoverridecollection_getAppOverride",
159
+ "kind": "api",
124
160
  "operation": "getAppOverride",
161
+ "code": "api-disabled",
162
+ "reason": "api is disabled",
125
163
  "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
126
164
  },
127
165
  {
128
- "kind": "cli",
129
- "name": "promptoverridecollection_getTenantOverride",
130
- "operation": "getTenantOverride",
166
+ "kind": "api",
167
+ "operation": "getOverrideMapForKeys",
168
+ "code": "api-disabled",
169
+ "reason": "api is disabled",
131
170
  "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
132
171
  },
133
172
  {
134
- "kind": "cli",
135
- "name": "promptoverridecollection_getResolutionLayers",
173
+ "kind": "api",
136
174
  "operation": "getResolutionLayers",
175
+ "code": "api-disabled",
176
+ "reason": "api is disabled",
137
177
  "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
138
178
  },
139
179
  {
140
- "kind": "mcp",
141
- "name": "promptoverridecollection_getappoverride",
142
- "operation": "getAppOverride",
180
+ "kind": "api",
181
+ "operation": "getTenantOverride",
182
+ "code": "api-disabled",
183
+ "reason": "api is disabled",
143
184
  "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
144
185
  },
145
186
  {
146
- "kind": "mcp",
147
- "name": "promptoverridecollection_gettenantoverride",
148
- "operation": "getTenantOverride",
187
+ "kind": "api",
188
+ "operation": "removeOverride",
189
+ "code": "api-disabled",
190
+ "reason": "api is disabled",
149
191
  "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
150
192
  },
151
193
  {
152
- "kind": "mcp",
153
- "name": "promptoverridecollection_getresolutionlayers",
154
- "operation": "getResolutionLayers",
194
+ "kind": "api",
195
+ "operation": "setTemplateOverride",
196
+ "code": "api-disabled",
197
+ "reason": "api is disabled",
155
198
  "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
156
199
  }
157
200
  ],
@@ -246,105 +289,41 @@
246
289
  "key",
247
290
  "context"
248
291
  ],
249
- "surfaces": [
250
- {
251
- "kind": "api",
252
- "name": "promptoverrides.list",
253
- "operation": "list",
254
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
255
- "path": "/promptoverrides",
256
- "method": "GET"
257
- },
258
- {
259
- "kind": "api",
260
- "name": "promptoverrides.get",
261
- "operation": "get",
262
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
263
- "path": "/promptoverrides/[id]",
264
- "method": "GET"
265
- },
266
- {
267
- "kind": "api",
268
- "name": "promptoverrides.create",
269
- "operation": "create",
270
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
271
- "path": "/promptoverrides",
272
- "method": "POST"
273
- },
274
- {
275
- "kind": "api",
276
- "name": "promptoverrides.update",
277
- "operation": "update",
278
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
279
- "path": "/promptoverrides/[id]",
280
- "method": "PATCH"
281
- },
292
+ "surfaces": [],
293
+ "withheldSurfaces": [
282
294
  {
283
295
  "kind": "api",
284
- "name": "promptoverrides.delete",
285
296
  "operation": "delete",
286
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
287
- "path": "/promptoverrides/[id]",
288
- "method": "DELETE"
289
- },
290
- {
291
- "kind": "cli",
292
- "name": "promptoverride_list",
293
- "operation": "list",
294
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
295
- },
296
- {
297
- "kind": "cli",
298
- "name": "promptoverride_get",
299
- "operation": "get",
300
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
301
- },
302
- {
303
- "kind": "cli",
304
- "name": "promptoverride_create",
305
- "operation": "create",
306
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
307
- },
308
- {
309
- "kind": "cli",
310
- "name": "promptoverride_update",
311
- "operation": "update",
297
+ "code": "api-disabled",
298
+ "reason": "api is disabled",
312
299
  "objectName": "@happyvertical/smrt-prompts:PromptOverride"
313
300
  },
314
- {
315
- "kind": "cli",
316
- "name": "promptoverride_delete",
317
- "operation": "delete",
318
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
319
- }
320
- ],
321
- "withheldSurfaces": [
322
301
  {
323
302
  "kind": "api",
324
303
  "operation": "getParams",
325
- "code": "not-included",
326
- "reason": "not listed in api.include",
304
+ "code": "api-disabled",
305
+ "reason": "api is disabled",
327
306
  "objectName": "@happyvertical/smrt-prompts:PromptOverride"
328
307
  },
329
308
  {
330
309
  "kind": "api",
331
310
  "operation": "save",
332
- "code": "lifecycle-method",
333
- "reason": "`save` is a framework lifecycle method, not a distinct operation",
311
+ "code": "api-disabled",
312
+ "reason": "api is disabled",
334
313
  "objectName": "@happyvertical/smrt-prompts:PromptOverride"
335
314
  },
336
315
  {
337
316
  "kind": "api",
338
317
  "operation": "setParams",
339
- "code": "not-included",
340
- "reason": "not listed in api.include",
318
+ "code": "api-disabled",
319
+ "reason": "api is disabled",
341
320
  "objectName": "@happyvertical/smrt-prompts:PromptOverride"
342
321
  },
343
322
  {
344
323
  "kind": "api",
345
324
  "operation": "toPromptLayer",
346
- "code": "not-included",
347
- "reason": "not listed in api.include",
325
+ "code": "api-disabled",
326
+ "reason": "api is disabled",
348
327
  "objectName": "@happyvertical/smrt-prompts:PromptOverride"
349
328
  }
350
329
  ],
@@ -355,147 +334,28 @@
355
334
  "risks": []
356
335
  }
357
336
  ],
358
- "surfaces": [
359
- {
360
- "kind": "api",
361
- "name": "promptoverrides.getAppOverride",
362
- "operation": "getAppOverride",
363
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection",
364
- "path": "/promptoverrides/getAppOverride",
365
- "method": "POST"
366
- },
367
- {
368
- "kind": "api",
369
- "name": "promptoverrides.getTenantOverride",
370
- "operation": "getTenantOverride",
371
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection",
372
- "path": "/promptoverrides/getTenantOverride",
373
- "method": "POST"
374
- },
375
- {
376
- "kind": "api",
377
- "name": "promptoverrides.getResolutionLayers",
378
- "operation": "getResolutionLayers",
379
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection",
380
- "path": "/promptoverrides/getResolutionLayers",
381
- "method": "POST"
382
- },
383
- {
384
- "kind": "cli",
385
- "name": "promptoverridecollection_getAppOverride",
386
- "operation": "getAppOverride",
387
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
388
- },
389
- {
390
- "kind": "cli",
391
- "name": "promptoverridecollection_getTenantOverride",
392
- "operation": "getTenantOverride",
393
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
394
- },
395
- {
396
- "kind": "cli",
397
- "name": "promptoverridecollection_getResolutionLayers",
398
- "operation": "getResolutionLayers",
399
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
400
- },
401
- {
402
- "kind": "mcp",
403
- "name": "promptoverridecollection_getappoverride",
404
- "operation": "getAppOverride",
405
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
406
- },
407
- {
408
- "kind": "mcp",
409
- "name": "promptoverridecollection_gettenantoverride",
410
- "operation": "getTenantOverride",
411
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
412
- },
413
- {
414
- "kind": "mcp",
415
- "name": "promptoverridecollection_getresolutionlayers",
416
- "operation": "getResolutionLayers",
417
- "objectName": "@happyvertical/smrt-prompts:PromptOverrideCollection"
418
- },
419
- {
420
- "kind": "api",
421
- "name": "promptoverrides.list",
422
- "operation": "list",
423
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
424
- "path": "/promptoverrides",
425
- "method": "GET"
426
- },
427
- {
428
- "kind": "api",
429
- "name": "promptoverrides.get",
430
- "operation": "get",
431
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
432
- "path": "/promptoverrides/[id]",
433
- "method": "GET"
434
- },
435
- {
436
- "kind": "api",
437
- "name": "promptoverrides.create",
438
- "operation": "create",
439
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
440
- "path": "/promptoverrides",
441
- "method": "POST"
442
- },
443
- {
444
- "kind": "api",
445
- "name": "promptoverrides.update",
446
- "operation": "update",
447
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
448
- "path": "/promptoverrides/[id]",
449
- "method": "PATCH"
450
- },
451
- {
452
- "kind": "api",
453
- "name": "promptoverrides.delete",
454
- "operation": "delete",
455
- "objectName": "@happyvertical/smrt-prompts:PromptOverride",
456
- "path": "/promptoverrides/[id]",
457
- "method": "DELETE"
458
- },
337
+ "surfaces": [],
338
+ "prompts": [
459
339
  {
460
- "kind": "cli",
461
- "name": "promptoverride_list",
462
- "operation": "list",
463
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
340
+ "filePath": "src/generated-surfaces.test.ts"
464
341
  },
465
342
  {
466
- "kind": "cli",
467
- "name": "promptoverride_get",
468
- "operation": "get",
469
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
343
+ "filePath": "src/index.ts"
470
344
  },
471
345
  {
472
- "kind": "cli",
473
- "name": "promptoverride_create",
474
- "operation": "create",
475
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
346
+ "filePath": "src/prompt-override-service.test.ts"
476
347
  },
477
348
  {
478
- "kind": "cli",
479
- "name": "promptoverride_update",
480
- "operation": "update",
481
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
349
+ "filePath": "src/prompt-registry.ts"
482
350
  },
483
351
  {
484
- "kind": "cli",
485
- "name": "promptoverride_delete",
486
- "operation": "delete",
487
- "objectName": "@happyvertical/smrt-prompts:PromptOverride"
488
- }
489
- ],
490
- "prompts": [
491
- {
492
- "filePath": "src/index.ts"
352
+ "filePath": "src/prompt-resolver.test.ts"
493
353
  },
494
354
  {
495
- "filePath": "src/prompt-registry.ts"
355
+ "filePath": "src/prompt-settings.test.ts"
496
356
  },
497
357
  {
498
- "filePath": "src/prompt-resolver.test.ts"
358
+ "filePath": "src/public-surface.test.ts"
499
359
  }
500
360
  ],
501
361
  "relationshipsV2": {
@@ -506,5 +366,5 @@
506
366
  "polymorphicAssociations": 0,
507
367
  "uuidColumns": 2
508
368
  },
509
- "agentDoc": "# smrt-prompts\n\nSMRT prompt registry and tenant-aware prompt override package. Code defines defaults; config layers and DB-stored overrides personalize at app and tenant levels.\n\n## Core pieces\n\n- `definePrompt()` registers code defaults in a global process registry (`globalThis.__smrtPromptRegistry`)\n- `resolvePrompt()` merges code defaults, file/config overrides (via `@happyvertical/smrt-config`), stored app-level overrides, stored tenant-level overrides, and a runtime override\n- `PromptOverride` (`_smrt_prompt_overrides` table) stores partial app-level and tenant-level overrides with write-time validation\n- `PromptOverrideCollection` exposes the standard SmrtCollection CRUD surface\n\n## Resolution layers (priority low → high)\n\n1. Code default — registered via `definePrompt({ key, template, ai })`\n2. File/config override — `getPackageConfig<PromptPackageConfig>('prompts', defaults)`\n3. App-level stored override — `PromptOverride` row with `tenantId = null`\n4. Tenant-level stored override — `PromptOverride` row with current tenant\n5. Runtime override — passed to `resolvePrompt({ overrides })`\n\nEach layer can override any subset of fields (template, profile, model, params). Inheritance is field-by-field.\n\n## Conventions\n\n- **Namespace prompt keys** by package or domain: `projects.issue.incorporateFeedback`, `content.summarize.headline`\n- **Stored overrides use nullable fields** so inheritance stays field-by-field — null means \"use the lower layer\"\n- **Provider selection is indirect** in v1: prompts select named profiles, and profiles resolve to provider/model in `smrt-config`\n- **`editable` flags are enforced on `PromptOverride.save()`** — definitions can lock specific fields against tenant override\n\n## Caching\n\n`resolvePrompt()` results are cached per `(key, tenantId)` with a TTL. The cache is invalidated on `PromptOverride.save()` and `.delete()`. Use `clearPromptCache()` for manual invalidation in tests.\n\n**A monotonic per-`(db, key)` invalidation generation guards the cache write.**\nA resolution captures `getPromptCacheGeneration(key, db)` before its\nasynchronous layer loads and hands it back to `setCachedPromptBase()`; a\nconcurrent `save()` / `delete()` bumps the generation and the in-flight\nresolution is then refused the cache write instead of repopulating the key it\njust invalidated with the pre-write value. Without it, \"a stale entry is never\nserved after a write\" held only until a read raced a write, and then failed for\nthe full 30s TTL (a raced `delete()` resurrected the deleted override).\nGenerations are tracked per `(db, key)`, not per tenant, because an app-level\nrow is inherited by every tenant. `clearPromptCache()` raises a single floor\n(`clearedThrough`) rather than resetting or per-key bumping: a key that has\nnever been invalidated has no map entry and reads as generation 0, so a per-key\nbump cannot reach it and a resolution that started before the clear would write\nits pre-clear value back. `smrt-languages` carries the same mechanism, keyed\nadditionally by locale. `smrt-playbooks` has the generation but not the floor\n(#2716).\n\n## Related\n\n- `@happyvertical/smrt-languages` — parallel package for language strings (uses the same architecture pattern)\n- `@happyvertical/smrt-features` — parallel package for feature flags\n"
369
+ "agentDoc": "# smrt-prompts\n\nSMRT prompt registry and tenant-aware prompt override package. Code defines defaults; config layers and DB-stored overrides personalize at app and tenant levels.\n\n## Core pieces\n\n- `definePrompt()` registers code defaults in a global process registry (`globalThis.__smrtPromptRegistry`)\n- `resolvePrompt()` merges code defaults, file/config overrides (via `@happyvertical/smrt-config`), stored app-level overrides, stored tenant-level overrides, and a runtime override\n- `PromptOverride` (`_smrt_prompt_overrides` table) stores partial app-level and tenant-level overrides with write-time validation\n- `PromptOverrideCollection` — collection API, plus `setTemplateOverride()` / `removeOverride()` / `findByScope()` / `getOverrideMapForKeys()` for the write and enumeration paths below\n- `PromptOverrideService` — the only write path a host should use directly; asks a host-supplied authorizer before every write and fails closed (mirrors `FeatureOverrideService`, #3013); before authorizing it rejects any scope the resolver never reads (`InvalidPromptScopeError`: unknown type, app id other than `__app__`, blank/untrimmed/`__app__` tenant id), so a malformed tenant write cannot reach the app-wide row\n- `PromptSettingsService` — read/write helper for a management screen: `listPromptSettings()` returns one `PromptSettingsRow` per registered key (the effective template, `appTemplate`/`tenantTemplate` for the raw override at each scope, `appDefaultTemplate`/`inheritedTemplate` for what reverting each scope produces, and `supplyingLevel`); `setPromptOverride()` refuses unknown keys and fields the definition marks not editable, then delegates to `PromptOverrideService` (#3053)\n- `./svelte` subpath — `PromptSettingsPanel`, the presentational management panel (#3053)\n\n## Resolution layers (priority low → high)\n\n1. Code default — registered via `definePrompt({ key, template, ai })`\n2. File/config override — `getPackageConfig<PromptPackageConfig>('prompts', defaults)`\n3. App-level stored override — `PromptOverride` row with `tenantId = null`\n4. Tenant-level stored override — `PromptOverride` row with current tenant\n5. Runtime override — passed to `resolvePrompt({ overrides })`\n\nEach layer can override any subset of fields (template, profile, model, params). Inheritance is field-by-field.\n\n## Conventions\n\n- **Namespace prompt keys** by package or domain: `projects.issue.incorporateFeedback`, `content.summarize.headline`\n- **Stored overrides use nullable fields** so inheritance stays field-by-field — null means \"use the lower layer\"\n- **Provider selection is indirect** in v1: prompts select named profiles, and profiles resolve to provider/model in `smrt-config`\n- **`editable` flags are enforced on `PromptOverride.save()`** — definitions can lock specific fields against tenant override\n\n## Generated surfaces\n\n`PromptOverride` ships with generated REST routes, MCP tools, and CLI commands\nclosed (`api: false`, `cli: false`, `mcp: false`, mirrors `FeatureOverride`,\n#3013). Override rows are authorization state for every scope and `tenantId` is\nan ordinary settable field, not request-derived tenant scoping — a generated\nauthentication-only surface would let any signed-in principal set another\ntenant's prompt text. Hosts write overrides server-side with\n`PromptOverrideService` (which requires an explicit authorizer for every\nwrite) or `PromptOverrideCollection.setTemplateOverride()` /\n`removeOverride()` only after their own authorization decision.\n\n## Management UI (`./svelte`)\n\n- Built like every other Svelte-shipping package here (`smrt-features`/`smrt-tenancy` are the closest models): `vite build --mode library && svelte-package -i src/svelte -o dist/svelte`, an `./svelte` export with a `svelte` condition, `svelte` as an **optional** peer dependency, plus `scripts/prune-svelte-package-artifacts.js` so `svelte-package` does not copy `__tests__/` into the published `dist/svelte`. The root export stays Svelte-free so server-only consumers are unaffected.\n- `PromptSettingsPanel` is presentational: rows and callbacks in, no fetching, no authorization. `src/svelte/types.ts` deliberately imports nothing from `smrt-core` or the `@smrt()` models, so the browser bundle stays thin; `PromptSettingsRow` is structurally assignable to `PromptSettingsView`.\n- Each row is its own `<form>` with a checkbox + `<textarea>` pair per scope (`tenantOverride`/`tenantTemplate`, and `appOverride`/`appTemplate` when `showApp` is set). The checkbox's presence in the submitted `FormData` — not whether the textarea is disabled — decides override vs. revert, because a plain HTML submit cannot un-disable a field the browser already marked disabled. `onSave` reads the same `FormData` when driving the panel from a client handler instead of `formAction`.\n- Two invariants, both regression-tested. (1) The \"revert\" preview never claims the bare registry default: `tenantRevertPreview()` shows `inheritedTemplate` (registry + config + app override), falling back to `appDefaultTemplate` only when the row does not carry it, and `appRevertPreview()` shows `appDefaultTemplate` (registry + config, never the app override itself). `PromptSettingsService` computes both explicitly rather than deriving one from the other — an earlier draft reused the app-only merge as the \"no tenant requested\" effective value and broke both. (2) The rows remount on `contextKey ?? prompts`, so an unsaved edit cannot survive a reload or a tenant switch and be submitted against the new rows.\n- **Authorization stays with the consumer.** `PromptSettingsService` reads are unfiltered and every write goes through `PromptOverrideService`; constructing the settings service without an `authorize` option makes it read-only (writes fail closed). Never add an authorization decision to this package.\n- `vitest.config.ts` runs two projects (`server` node + `smrtVitestPlugin`, `svelte` jsdom + `@sveltejs/vite-plugin-svelte` with `conditions: ['browser']`) because the browser condition would send `smrt-core` to its browser build if applied globally.\n- `PromptSettingsService.create()` builds its `PromptOverrideCollection` with the caller's `defaultListLimit`/`maxListLimit` stripped before the bulk `getOverrideMapForKeys()` enumeration read that backs `listPromptSettings()` — the #3056 hazard class in `smrt-features`'s `FeatureOverrideCollection.getOverrideMap()`, applied here proactively rather than left for a follow-up issue. A host-configured list bound must never silently drop a key's override from a management screen.\n\n## Prompt keys are persisted identifiers\n\nOverrides are stored keyed by the exact `key` string and resolved by that\nstring at runtime. **Renaming a key orphans every existing override and\nsilently reverts every tenant to the registry default.** Treat a shipped key\nas immutable; a rename needs a data migration over\n`_smrt_prompt_overrides.key`, not an edit.\n\n## Caching\n\n`resolvePrompt()` results are cached per `(key, tenantId)` with a TTL. The cache is invalidated on `PromptOverride.save()` and `.delete()`. Use `clearPromptCache()` for manual invalidation in tests.\n\n**A monotonic per-`(db, key)` invalidation generation guards the cache write.**\nA resolution captures `getPromptCacheGeneration(key, db)` before its\nasynchronous layer loads and hands it back to `setCachedPromptBase()`; a\nconcurrent `save()` / `delete()` bumps the generation and the in-flight\nresolution is then refused the cache write instead of repopulating the key it\njust invalidated with the pre-write value. Without it, \"a stale entry is never\nserved after a write\" held only until a read raced a write, and then failed for\nthe full 30s TTL (a raced `delete()` resurrected the deleted override).\nGenerations are tracked per `(db, key)`, not per tenant, because an app-level\nrow is inherited by every tenant. `clearPromptCache()` raises a single floor\n(`clearedThrough`) rather than resetting or per-key bumping: a key that has\nnever been invalidated has no map entry and reads as generation 0, so a per-key\nbump cannot reach it and a resolution that started before the clear would write\nits pre-clear value back. `smrt-languages` carries the same mechanism, keyed\nadditionally by locale. `smrt-playbooks` has the generation but not the floor\n(#2716).\n\n## Related\n\n- `@happyvertical/smrt-languages` — parallel package for language strings (uses the same architecture pattern)\n- `@happyvertical/smrt-features` — parallel package for feature flags\n"
510
370
  }