prjct-cli 0.51.0 → 0.53.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.53.0] - 2026-01-30
4
+
5
+ ### Features
6
+
7
+ - Lazy template loading with TTL cache - PRJ-76 (#79)
8
+
9
+
10
+ ## [0.53.0] - 2026-01-30
11
+
12
+ ### Features
13
+
14
+ - Lazy template loading with TTL cache - PRJ-76
15
+
16
+
17
+ ## [0.53.0] - 2026-01-30
18
+
19
+ ### Added
20
+
21
+ - **Lazy template loading with TTL cache** (PRJ-76)
22
+ - Templates now loaded on-demand with 60-second TTL cache
23
+ - Added `getTemplate()` method with per-file caching
24
+ - `loadChecklists()` and `loadChecklistRouting()` now use TTL cache
25
+ - Added `clearTemplateCache()` method for testing/forced refresh
26
+ - Reduces disk I/O for frequently accessed templates
27
+
28
+
29
+ ## [0.52.0] - 2026-01-30
30
+
31
+ ### Features
32
+
33
+ - Add confidence scores to all stored preferences - PRJ-104 (#78)
34
+
35
+
36
+ ## [0.52.0] - 2026-01-30
37
+
38
+ ### Added
39
+
40
+ - **Confidence scores for stored preferences** (PRJ-104)
41
+ - All preferences, decisions, and workflows now track confidence level
42
+ - Confidence: `low` (1-2 obs), `medium` (3-5 obs), `high` (6+ or confirmed)
43
+ - Added `confirmPreference()`, `confirmDecision()`, `confirmWorkflow()` methods
44
+ - User confirmation immediately sets confidence to `high`
45
+ - Added `calculateConfidence()` utility function
46
+
47
+
3
48
  ## [0.51.0] - 2026-01-30
4
49
 
5
50
  ### Features
@@ -22,6 +22,7 @@ import { appendJsonLine, getLastJsonLines } from '../utils/jsonl-helper'
22
22
 
23
23
  // Re-export types from canonical location
24
24
  export type {
25
+ ConfidenceLevel,
25
26
  Decision,
26
27
  HistoryEntry,
27
28
  HistoryEventType,
@@ -35,7 +36,7 @@ export type {
35
36
  Workflow,
36
37
  } from '../types/memory'
37
38
 
38
- export { MEMORY_TAGS } from '../types/memory'
39
+ export { calculateConfidence, MEMORY_TAGS } from '../types/memory'
39
40
 
40
41
  import type {
41
42
  HistoryEntry,
@@ -49,7 +50,7 @@ import type {
49
50
  Workflow,
50
51
  } from '../types/memory'
51
52
 
52
- import { MEMORY_TAGS } from '../types/memory'
53
+ import { calculateConfidence, MEMORY_TAGS } from '../types/memory'
53
54
 
54
55
  // =============================================================================
55
56
  // Base Store
@@ -306,7 +307,8 @@ export class PatternStore extends CachedStore<Patterns> {
306
307
  projectId: string,
307
308
  key: string,
308
309
  value: string,
309
- context: string = ''
310
+ context: string = '',
311
+ options: { userConfirmed?: boolean } = {}
310
312
  ): Promise<void> {
311
313
  const patterns = await this.load(projectId)
312
314
  const now = getTimestamp()
@@ -317,11 +319,14 @@ export class PatternStore extends CachedStore<Patterns> {
317
319
  count: 1,
318
320
  firstSeen: now,
319
321
  lastSeen: now,
320
- confidence: 'low',
322
+ confidence: options.userConfirmed ? 'high' : 'low',
321
323
  contexts: [context].filter(Boolean),
322
- }
324
+ userConfirmed: options.userConfirmed || false,
325
+ } as Patterns['decisions'][string]
323
326
  } else {
324
- const decision = patterns.decisions[key]
327
+ const decision = patterns.decisions[key] as Patterns['decisions'][string] & {
328
+ userConfirmed?: boolean
329
+ }
325
330
 
326
331
  if (decision.value === value) {
327
332
  decision.count++
@@ -329,23 +334,36 @@ export class PatternStore extends CachedStore<Patterns> {
329
334
  if (context && !decision.contexts.includes(context)) {
330
335
  decision.contexts.push(context)
331
336
  }
332
-
333
- if (decision.count >= 5) {
334
- decision.confidence = 'high'
335
- } else if (decision.count >= 3) {
336
- decision.confidence = 'medium'
337
+ if (options.userConfirmed) {
338
+ decision.userConfirmed = true
337
339
  }
340
+ decision.confidence = calculateConfidence(decision.count, decision.userConfirmed)
338
341
  } else {
339
342
  decision.value = value
340
343
  decision.count = 1
341
344
  decision.lastSeen = now
342
- decision.confidence = 'low'
345
+ decision.userConfirmed = options.userConfirmed || false
346
+ decision.confidence = options.userConfirmed ? 'high' : 'low'
343
347
  }
344
348
  }
345
349
 
346
350
  await this.save(projectId)
347
351
  }
348
352
 
353
+ async confirmDecision(projectId: string, key: string): Promise<boolean> {
354
+ const patterns = await this.load(projectId)
355
+ const decision = patterns.decisions[key] as
356
+ | (Patterns['decisions'][string] & { userConfirmed?: boolean })
357
+ | undefined
358
+ if (!decision) return false
359
+
360
+ decision.userConfirmed = true
361
+ decision.confidence = 'high'
362
+ decision.lastSeen = getTimestamp()
363
+ await this.save(projectId)
364
+ return true
365
+ }
366
+
349
367
  async getDecision(
350
368
  projectId: string,
351
369
  key: string
@@ -378,15 +396,31 @@ export class PatternStore extends CachedStore<Patterns> {
378
396
  count: 1,
379
397
  firstSeen: now,
380
398
  lastSeen: now,
399
+ confidence: 'low',
400
+ userConfirmed: false,
381
401
  }
382
402
  } else {
383
- patterns.workflows[workflowName].count++
384
- patterns.workflows[workflowName].lastSeen = now
403
+ const workflow = patterns.workflows[workflowName]
404
+ workflow.count++
405
+ workflow.lastSeen = now
406
+ workflow.confidence = calculateConfidence(workflow.count, workflow.userConfirmed)
385
407
  }
386
408
 
387
409
  await this.save(projectId)
388
410
  }
389
411
 
412
+ async confirmWorkflow(projectId: string, workflowName: string): Promise<boolean> {
413
+ const patterns = await this.load(projectId)
414
+ const workflow = patterns.workflows[workflowName]
415
+ if (!workflow) return false
416
+
417
+ workflow.userConfirmed = true
418
+ workflow.confidence = 'high'
419
+ workflow.lastSeen = getTimestamp()
420
+ await this.save(projectId)
421
+ return true
422
+ }
423
+
390
424
  async getWorkflow(projectId: string, workflowName: string): Promise<Workflow | null> {
391
425
  const patterns = await this.load(projectId)
392
426
  const workflow = patterns.workflows[workflowName]
@@ -395,12 +429,39 @@ export class PatternStore extends CachedStore<Patterns> {
395
429
  return workflow
396
430
  }
397
431
 
398
- async setPreference(projectId: string, key: string, value: Preference['value']): Promise<void> {
432
+ async setPreference(
433
+ projectId: string,
434
+ key: string,
435
+ value: Preference['value'],
436
+ options: { userConfirmed?: boolean } = {}
437
+ ): Promise<void> {
399
438
  const patterns = await this.load(projectId)
400
- patterns.preferences[key] = { value, updatedAt: getTimestamp() }
439
+ const existing = patterns.preferences[key]
440
+ const observationCount = existing ? existing.observationCount + 1 : 1
441
+ const userConfirmed = options.userConfirmed || existing?.userConfirmed || false
442
+
443
+ patterns.preferences[key] = {
444
+ value,
445
+ updatedAt: getTimestamp(),
446
+ confidence: calculateConfidence(observationCount, userConfirmed),
447
+ observationCount,
448
+ userConfirmed,
449
+ }
401
450
  await this.save(projectId)
402
451
  }
403
452
 
453
+ async confirmPreference(projectId: string, key: string): Promise<boolean> {
454
+ const patterns = await this.load(projectId)
455
+ const pref = patterns.preferences[key]
456
+ if (!pref) return false
457
+
458
+ pref.userConfirmed = true
459
+ pref.confidence = 'high'
460
+ pref.updatedAt = getTimestamp()
461
+ await this.save(projectId)
462
+ return true
463
+ }
464
+
404
465
  async getPreference(
405
466
  projectId: string,
406
467
  key: string,
@@ -857,14 +918,31 @@ export class MemorySystem {
857
918
  return this._patternStore.getWorkflow(projectId, workflowName)
858
919
  }
859
920
 
860
- setPreference(projectId: string, key: string, value: Preference['value']): Promise<void> {
861
- return this._patternStore.setPreference(projectId, key, value)
921
+ setPreference(
922
+ projectId: string,
923
+ key: string,
924
+ value: Preference['value'],
925
+ options?: { userConfirmed?: boolean }
926
+ ): Promise<void> {
927
+ return this._patternStore.setPreference(projectId, key, value, options)
862
928
  }
863
929
 
864
930
  getPreference(projectId: string, key: string, defaultValue?: unknown): Promise<unknown> {
865
931
  return this._patternStore.getPreference(projectId, key, defaultValue)
866
932
  }
867
933
 
934
+ confirmPreference(projectId: string, key: string): Promise<boolean> {
935
+ return this._patternStore.confirmPreference(projectId, key)
936
+ }
937
+
938
+ confirmDecision(projectId: string, key: string): Promise<boolean> {
939
+ return this._patternStore.confirmDecision(projectId, key)
940
+ }
941
+
942
+ confirmWorkflow(projectId: string, workflowName: string): Promise<boolean> {
943
+ return this._patternStore.confirmWorkflow(projectId, workflowName)
944
+ }
945
+
868
946
  getPatternsSummary(projectId: string) {
869
947
  return this._patternStore.getPatternsSummary(projectId)
870
948
  }
@@ -43,17 +43,73 @@ type Agent = PromptAgent
43
43
  type Context = PromptContext
44
44
  type State = PromptState
45
45
 
46
+ /**
47
+ * Cached template entry with TTL support
48
+ * @see PRJ-76
49
+ */
50
+ interface CachedTemplate {
51
+ content: string
52
+ loadedAt: number
53
+ }
54
+
46
55
  /**
47
56
  * Builds prompts for Claude using templates, context, and learned patterns.
48
57
  * Supports plan mode, think blocks, and quality checklists.
49
58
  * Auto-injects unified state and performance insights.
59
+ *
60
+ * Uses lazy loading for templates with 60s TTL cache.
61
+ * @see PRJ-76
50
62
  */
51
63
  class PromptBuilder {
52
64
  private _checklistsCache: Record<string, string> | null = null
65
+ private _checklistsCacheTime: number = 0
53
66
  private _checklistRoutingCache: string | null = null
67
+ private _checklistRoutingCacheTime: number = 0
54
68
  private _currentContext: Context | null = null
55
69
  private _stateCache: Map<string, { state: ProjectState; timestamp: number }> = new Map()
56
70
  private _stateCacheTTL = 5000 // 5 seconds
71
+ private _templateCache: Map<string, CachedTemplate> = new Map()
72
+ private readonly TEMPLATE_CACHE_TTL_MS = 60_000 // 60 seconds
73
+
74
+ /**
75
+ * Get a template with TTL caching.
76
+ * Returns cached content if within TTL, otherwise loads from disk.
77
+ * @see PRJ-76
78
+ */
79
+ getTemplate(templatePath: string): string | null {
80
+ const cached = this._templateCache.get(templatePath)
81
+ const now = Date.now()
82
+
83
+ if (cached && now - cached.loadedAt < this.TEMPLATE_CACHE_TTL_MS) {
84
+ return cached.content
85
+ }
86
+
87
+ try {
88
+ if (fs.existsSync(templatePath)) {
89
+ const content = fs.readFileSync(templatePath, 'utf-8')
90
+ this._templateCache.set(templatePath, { content, loadedAt: now })
91
+ return content
92
+ }
93
+ } catch (error) {
94
+ if (!isNotFoundError(error)) {
95
+ console.error(`Template loading warning: ${(error as Error).message}`)
96
+ }
97
+ }
98
+
99
+ return null
100
+ }
101
+
102
+ /**
103
+ * Clear the template cache (for testing or forced refresh)
104
+ * @see PRJ-76
105
+ */
106
+ clearTemplateCache(): void {
107
+ this._templateCache.clear()
108
+ this._checklistsCache = null
109
+ this._checklistsCacheTime = 0
110
+ this._checklistRoutingCache = null
111
+ this._checklistRoutingCacheTime = 0
112
+ }
57
113
 
58
114
  /**
59
115
  * Reset context (for testing)
@@ -71,9 +127,16 @@ class PromptBuilder {
71
127
 
72
128
  /**
73
129
  * Load quality checklists from templates/checklists/
130
+ * Uses lazy loading with TTL cache.
131
+ * @see PRJ-76
74
132
  */
75
133
  loadChecklists(): Record<string, string> {
76
- if (this._checklistsCache) return this._checklistsCache
134
+ const now = Date.now()
135
+
136
+ // Check if cache is still valid
137
+ if (this._checklistsCache && now - this._checklistsCacheTime < this.TEMPLATE_CACHE_TTL_MS) {
138
+ return this._checklistsCache
139
+ }
77
140
 
78
141
  const checklistsDir = path.join(__dirname, '..', '..', 'templates', 'checklists')
79
142
  const checklists: Record<string, string> = {}
@@ -83,8 +146,12 @@ class PromptBuilder {
83
146
  const files = fs.readdirSync(checklistsDir).filter((f) => f.endsWith('.md'))
84
147
  for (const file of files) {
85
148
  const name = file.replace('.md', '')
86
- const content = fs.readFileSync(path.join(checklistsDir, file), 'utf-8')
87
- checklists[name] = content
149
+ const templatePath = path.join(checklistsDir, file)
150
+ // Use getTemplate for individual files to leverage per-file caching
151
+ const content = this.getTemplate(templatePath)
152
+ if (content) {
153
+ checklists[name] = content
154
+ }
88
155
  }
89
156
  }
90
157
  } catch (error) {
@@ -95,6 +162,7 @@ class PromptBuilder {
95
162
  }
96
163
 
97
164
  this._checklistsCache = checklists
165
+ this._checklistsCacheTime = now
98
166
  return checklists
99
167
  }
100
168
 
@@ -215,9 +283,19 @@ class PromptBuilder {
215
283
 
216
284
  /**
217
285
  * Load checklist routing template for Claude to decide which checklists apply
286
+ * Uses lazy loading with TTL cache.
287
+ * @see PRJ-76
218
288
  */
219
289
  loadChecklistRouting(): string | null {
220
- if (this._checklistRoutingCache) return this._checklistRoutingCache
290
+ const now = Date.now()
291
+
292
+ // Check if cache is still valid
293
+ if (
294
+ this._checklistRoutingCache &&
295
+ now - this._checklistRoutingCacheTime < this.TEMPLATE_CACHE_TTL_MS
296
+ ) {
297
+ return this._checklistRoutingCache
298
+ }
221
299
 
222
300
  const routingPath = path.join(
223
301
  __dirname,
@@ -228,15 +306,11 @@ class PromptBuilder {
228
306
  'checklist-routing.md'
229
307
  )
230
308
 
231
- try {
232
- if (fs.existsSync(routingPath)) {
233
- this._checklistRoutingCache = fs.readFileSync(routingPath, 'utf-8')
234
- }
235
- } catch (error) {
236
- // Silent fail - checklist routing is optional
237
- if (!isNotFoundError(error)) {
238
- console.error(`Checklist routing warning: ${(error as Error).message}`)
239
- }
309
+ // Use getTemplate for consistent caching behavior
310
+ const content = this.getTemplate(routingPath)
311
+ if (content) {
312
+ this._checklistRoutingCache = content
313
+ this._checklistRoutingCacheTime = now
240
314
  }
241
315
 
242
316
  return this._checklistRoutingCache || null
@@ -43,6 +43,10 @@ export interface Memory {
43
43
  userTriggered: boolean
44
44
  createdAt: string
45
45
  updatedAt: string
46
+ /** Confidence level for this memory (optional for backward compatibility) */
47
+ confidence?: ConfidenceLevel
48
+ /** Number of times this memory was reinforced */
49
+ observationCount?: number
46
50
  }
47
51
 
48
52
  /**
@@ -123,8 +127,10 @@ export interface Decision {
123
127
  count: number
124
128
  firstSeen: string
125
129
  lastSeen: string
126
- confidence: 'low' | 'medium' | 'high'
130
+ confidence: ConfidenceLevel
127
131
  contexts: string[]
132
+ /** Whether user explicitly confirmed this decision */
133
+ userConfirmed?: boolean
128
134
  }
129
135
 
130
136
  /**
@@ -141,14 +147,47 @@ export interface Workflow {
141
147
  successRate?: number
142
148
  /** Steps in the workflow */
143
149
  steps?: string[]
150
+ /** Confidence level based on execution count */
151
+ confidence?: ConfidenceLevel
152
+ /** Whether user explicitly confirmed this workflow */
153
+ userConfirmed?: boolean
144
154
  }
145
155
 
146
156
  /**
147
- * A user preference value.
157
+ * Confidence level for stored preferences and decisions.
158
+ * @see PRJ-104
159
+ */
160
+ export type ConfidenceLevel = 'low' | 'medium' | 'high'
161
+
162
+ /**
163
+ * Calculate confidence level from observation count.
164
+ * - low: 1-2 observations
165
+ * - medium: 3-5 observations
166
+ * - high: 6+ observations or explicit user confirmation
167
+ */
168
+ export function calculateConfidence(
169
+ count: number,
170
+ userConfirmed: boolean = false
171
+ ): ConfidenceLevel {
172
+ if (userConfirmed) return 'high'
173
+ if (count >= 6) return 'high'
174
+ if (count >= 3) return 'medium'
175
+ return 'low'
176
+ }
177
+
178
+ /**
179
+ * A user preference value with confidence scoring.
180
+ * @see PRJ-104
148
181
  */
149
182
  export interface Preference {
150
183
  value: string | number | boolean
151
184
  updatedAt: string
185
+ /** Confidence level based on observations */
186
+ confidence: ConfidenceLevel
187
+ /** Number of times this preference was observed */
188
+ observationCount: number
189
+ /** Whether user explicitly confirmed this preference */
190
+ userConfirmed: boolean
152
191
  }
153
192
 
154
193
  /**
@@ -8568,6 +8568,12 @@ var init_jsonl_helper = __esm({
8568
8568
  });
8569
8569
 
8570
8570
  // core/types/memory.ts
8571
+ function calculateConfidence(count, userConfirmed = false) {
8572
+ if (userConfirmed) return "high";
8573
+ if (count >= 6) return "high";
8574
+ if (count >= 3) return "medium";
8575
+ return "low";
8576
+ }
8571
8577
  var MEMORY_TAGS;
8572
8578
  var init_memory = __esm({
8573
8579
  "core/types/memory.ts"() {
@@ -8591,6 +8597,7 @@ var init_memory = __esm({
8591
8597
  CONFIRMATION_LEVEL: "confirmation_level",
8592
8598
  AGENT_PREFERENCE: "agent_preference"
8593
8599
  };
8600
+ __name(calculateConfidence, "calculateConfidence");
8594
8601
  }
8595
8602
  });
8596
8603
 
@@ -8782,7 +8789,7 @@ var init_memory_system = __esm({
8782
8789
  async savePatterns(projectId) {
8783
8790
  return this.save(projectId);
8784
8791
  }
8785
- async recordDecision(projectId, key, value, context2 = "") {
8792
+ async recordDecision(projectId, key, value, context2 = "", options = {}) {
8786
8793
  const patterns = await this.load(projectId);
8787
8794
  const now = getTimestamp();
8788
8795
  if (!patterns.decisions[key]) {
@@ -8791,8 +8798,9 @@ var init_memory_system = __esm({
8791
8798
  count: 1,
8792
8799
  firstSeen: now,
8793
8800
  lastSeen: now,
8794
- confidence: "low",
8795
- contexts: [context2].filter(Boolean)
8801
+ confidence: options.userConfirmed ? "high" : "low",
8802
+ contexts: [context2].filter(Boolean),
8803
+ userConfirmed: options.userConfirmed || false
8796
8804
  };
8797
8805
  } else {
8798
8806
  const decision = patterns.decisions[key];
@@ -8802,20 +8810,30 @@ var init_memory_system = __esm({
8802
8810
  if (context2 && !decision.contexts.includes(context2)) {
8803
8811
  decision.contexts.push(context2);
8804
8812
  }
8805
- if (decision.count >= 5) {
8806
- decision.confidence = "high";
8807
- } else if (decision.count >= 3) {
8808
- decision.confidence = "medium";
8813
+ if (options.userConfirmed) {
8814
+ decision.userConfirmed = true;
8809
8815
  }
8816
+ decision.confidence = calculateConfidence(decision.count, decision.userConfirmed);
8810
8817
  } else {
8811
8818
  decision.value = value;
8812
8819
  decision.count = 1;
8813
8820
  decision.lastSeen = now;
8814
- decision.confidence = "low";
8821
+ decision.userConfirmed = options.userConfirmed || false;
8822
+ decision.confidence = options.userConfirmed ? "high" : "low";
8815
8823
  }
8816
8824
  }
8817
8825
  await this.save(projectId);
8818
8826
  }
8827
+ async confirmDecision(projectId, key) {
8828
+ const patterns = await this.load(projectId);
8829
+ const decision = patterns.decisions[key];
8830
+ if (!decision) return false;
8831
+ decision.userConfirmed = true;
8832
+ decision.confidence = "high";
8833
+ decision.lastSeen = getTimestamp();
8834
+ await this.save(projectId);
8835
+ return true;
8836
+ }
8819
8837
  async getDecision(projectId, key) {
8820
8838
  const patterns = await this.load(projectId);
8821
8839
  const decision = patterns.decisions[key];
@@ -8835,24 +8853,57 @@ var init_memory_system = __esm({
8835
8853
  ...pattern,
8836
8854
  count: 1,
8837
8855
  firstSeen: now,
8838
- lastSeen: now
8856
+ lastSeen: now,
8857
+ confidence: "low",
8858
+ userConfirmed: false
8839
8859
  };
8840
8860
  } else {
8841
- patterns.workflows[workflowName].count++;
8842
- patterns.workflows[workflowName].lastSeen = now;
8861
+ const workflow2 = patterns.workflows[workflowName];
8862
+ workflow2.count++;
8863
+ workflow2.lastSeen = now;
8864
+ workflow2.confidence = calculateConfidence(workflow2.count, workflow2.userConfirmed);
8843
8865
  }
8844
8866
  await this.save(projectId);
8845
8867
  }
8868
+ async confirmWorkflow(projectId, workflowName) {
8869
+ const patterns = await this.load(projectId);
8870
+ const workflow2 = patterns.workflows[workflowName];
8871
+ if (!workflow2) return false;
8872
+ workflow2.userConfirmed = true;
8873
+ workflow2.confidence = "high";
8874
+ workflow2.lastSeen = getTimestamp();
8875
+ await this.save(projectId);
8876
+ return true;
8877
+ }
8846
8878
  async getWorkflow(projectId, workflowName) {
8847
8879
  const patterns = await this.load(projectId);
8848
8880
  const workflow2 = patterns.workflows[workflowName];
8849
8881
  if (!workflow2 || workflow2.count < 3) return null;
8850
8882
  return workflow2;
8851
8883
  }
8852
- async setPreference(projectId, key, value) {
8884
+ async setPreference(projectId, key, value, options = {}) {
8885
+ const patterns = await this.load(projectId);
8886
+ const existing = patterns.preferences[key];
8887
+ const observationCount = existing ? existing.observationCount + 1 : 1;
8888
+ const userConfirmed = options.userConfirmed || existing?.userConfirmed || false;
8889
+ patterns.preferences[key] = {
8890
+ value,
8891
+ updatedAt: getTimestamp(),
8892
+ confidence: calculateConfidence(observationCount, userConfirmed),
8893
+ observationCount,
8894
+ userConfirmed
8895
+ };
8896
+ await this.save(projectId);
8897
+ }
8898
+ async confirmPreference(projectId, key) {
8853
8899
  const patterns = await this.load(projectId);
8854
- patterns.preferences[key] = { value, updatedAt: getTimestamp() };
8900
+ const pref = patterns.preferences[key];
8901
+ if (!pref) return false;
8902
+ pref.userConfirmed = true;
8903
+ pref.confidence = "high";
8904
+ pref.updatedAt = getTimestamp();
8855
8905
  await this.save(projectId);
8906
+ return true;
8856
8907
  }
8857
8908
  async getPreference(projectId, key, defaultValue = null) {
8858
8909
  const patterns = await this.load(projectId);
@@ -9169,12 +9220,21 @@ Context: ${context2}` : ""}`,
9169
9220
  getWorkflow(projectId, workflowName) {
9170
9221
  return this._patternStore.getWorkflow(projectId, workflowName);
9171
9222
  }
9172
- setPreference(projectId, key, value) {
9173
- return this._patternStore.setPreference(projectId, key, value);
9223
+ setPreference(projectId, key, value, options) {
9224
+ return this._patternStore.setPreference(projectId, key, value, options);
9174
9225
  }
9175
9226
  getPreference(projectId, key, defaultValue) {
9176
9227
  return this._patternStore.getPreference(projectId, key, defaultValue);
9177
9228
  }
9229
+ confirmPreference(projectId, key) {
9230
+ return this._patternStore.confirmPreference(projectId, key);
9231
+ }
9232
+ confirmDecision(projectId, key) {
9233
+ return this._patternStore.confirmDecision(projectId, key);
9234
+ }
9235
+ confirmWorkflow(projectId, workflowName) {
9236
+ return this._patternStore.confirmWorkflow(projectId, workflowName);
9237
+ }
9178
9238
  getPatternsSummary(projectId) {
9179
9239
  return this._patternStore.getPatternsSummary(projectId);
9180
9240
  }
@@ -12272,11 +12332,51 @@ var init_prompt_builder = __esm({
12272
12332
  __name(this, "PromptBuilder");
12273
12333
  }
12274
12334
  _checklistsCache = null;
12335
+ _checklistsCacheTime = 0;
12275
12336
  _checklistRoutingCache = null;
12337
+ _checklistRoutingCacheTime = 0;
12276
12338
  _currentContext = null;
12277
12339
  _stateCache = /* @__PURE__ */ new Map();
12278
12340
  _stateCacheTTL = 5e3;
12279
12341
  // 5 seconds
12342
+ _templateCache = /* @__PURE__ */ new Map();
12343
+ TEMPLATE_CACHE_TTL_MS = 6e4;
12344
+ // 60 seconds
12345
+ /**
12346
+ * Get a template with TTL caching.
12347
+ * Returns cached content if within TTL, otherwise loads from disk.
12348
+ * @see PRJ-76
12349
+ */
12350
+ getTemplate(templatePath) {
12351
+ const cached = this._templateCache.get(templatePath);
12352
+ const now = Date.now();
12353
+ if (cached && now - cached.loadedAt < this.TEMPLATE_CACHE_TTL_MS) {
12354
+ return cached.content;
12355
+ }
12356
+ try {
12357
+ if (fs25.existsSync(templatePath)) {
12358
+ const content = fs25.readFileSync(templatePath, "utf-8");
12359
+ this._templateCache.set(templatePath, { content, loadedAt: now });
12360
+ return content;
12361
+ }
12362
+ } catch (error) {
12363
+ if (!isNotFoundError(error)) {
12364
+ console.error(`Template loading warning: ${error.message}`);
12365
+ }
12366
+ }
12367
+ return null;
12368
+ }
12369
+ /**
12370
+ * Clear the template cache (for testing or forced refresh)
12371
+ * @see PRJ-76
12372
+ */
12373
+ clearTemplateCache() {
12374
+ this._templateCache.clear();
12375
+ this._checklistsCache = null;
12376
+ this._checklistsCacheTime = 0;
12377
+ this._checklistRoutingCache = null;
12378
+ this._checklistRoutingCacheTime = 0;
12379
+ }
12280
12380
  /**
12281
12381
  * Reset context (for testing)
12282
12382
  */
@@ -12291,9 +12391,14 @@ var init_prompt_builder = __esm({
12291
12391
  }
12292
12392
  /**
12293
12393
  * Load quality checklists from templates/checklists/
12394
+ * Uses lazy loading with TTL cache.
12395
+ * @see PRJ-76
12294
12396
  */
12295
12397
  loadChecklists() {
12296
- if (this._checklistsCache) return this._checklistsCache;
12398
+ const now = Date.now();
12399
+ if (this._checklistsCache && now - this._checklistsCacheTime < this.TEMPLATE_CACHE_TTL_MS) {
12400
+ return this._checklistsCache;
12401
+ }
12297
12402
  const checklistsDir = path25.join(__dirname, "..", "..", "templates", "checklists");
12298
12403
  const checklists = {};
12299
12404
  try {
@@ -12301,8 +12406,11 @@ var init_prompt_builder = __esm({
12301
12406
  const files = fs25.readdirSync(checklistsDir).filter((f) => f.endsWith(".md"));
12302
12407
  for (const file of files) {
12303
12408
  const name = file.replace(".md", "");
12304
- const content = fs25.readFileSync(path25.join(checklistsDir, file), "utf-8");
12305
- checklists[name] = content;
12409
+ const templatePath = path25.join(checklistsDir, file);
12410
+ const content = this.getTemplate(templatePath);
12411
+ if (content) {
12412
+ checklists[name] = content;
12413
+ }
12306
12414
  }
12307
12415
  }
12308
12416
  } catch (error) {
@@ -12311,6 +12419,7 @@ var init_prompt_builder = __esm({
12311
12419
  }
12312
12420
  }
12313
12421
  this._checklistsCache = checklists;
12422
+ this._checklistsCacheTime = now;
12314
12423
  return checklists;
12315
12424
  }
12316
12425
  /**
@@ -12408,9 +12517,14 @@ var init_prompt_builder = __esm({
12408
12517
  }
12409
12518
  /**
12410
12519
  * Load checklist routing template for Claude to decide which checklists apply
12520
+ * Uses lazy loading with TTL cache.
12521
+ * @see PRJ-76
12411
12522
  */
12412
12523
  loadChecklistRouting() {
12413
- if (this._checklistRoutingCache) return this._checklistRoutingCache;
12524
+ const now = Date.now();
12525
+ if (this._checklistRoutingCache && now - this._checklistRoutingCacheTime < this.TEMPLATE_CACHE_TTL_MS) {
12526
+ return this._checklistRoutingCache;
12527
+ }
12414
12528
  const routingPath = path25.join(
12415
12529
  __dirname,
12416
12530
  "..",
@@ -12419,14 +12533,10 @@ var init_prompt_builder = __esm({
12419
12533
  "agentic",
12420
12534
  "checklist-routing.md"
12421
12535
  );
12422
- try {
12423
- if (fs25.existsSync(routingPath)) {
12424
- this._checklistRoutingCache = fs25.readFileSync(routingPath, "utf-8");
12425
- }
12426
- } catch (error) {
12427
- if (!isNotFoundError(error)) {
12428
- console.error(`Checklist routing warning: ${error.message}`);
12429
- }
12536
+ const content = this.getTemplate(routingPath);
12537
+ if (content) {
12538
+ this._checklistRoutingCache = content;
12539
+ this._checklistRoutingCacheTime = now;
12430
12540
  }
12431
12541
  return this._checklistRoutingCache || null;
12432
12542
  }
@@ -24604,7 +24714,7 @@ var require_package = __commonJS({
24604
24714
  "package.json"(exports, module) {
24605
24715
  module.exports = {
24606
24716
  name: "prjct-cli",
24607
- version: "0.51.0",
24717
+ version: "0.53.0",
24608
24718
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
24609
24719
  main: "core/index.ts",
24610
24720
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {