prjct-cli 0.52.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,31 @@
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
+
3
29
  ## [0.52.0] - 2026-01-30
4
30
 
5
31
  ### Features
@@ -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
@@ -12332,11 +12332,51 @@ var init_prompt_builder = __esm({
12332
12332
  __name(this, "PromptBuilder");
12333
12333
  }
12334
12334
  _checklistsCache = null;
12335
+ _checklistsCacheTime = 0;
12335
12336
  _checklistRoutingCache = null;
12337
+ _checklistRoutingCacheTime = 0;
12336
12338
  _currentContext = null;
12337
12339
  _stateCache = /* @__PURE__ */ new Map();
12338
12340
  _stateCacheTTL = 5e3;
12339
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
+ }
12340
12380
  /**
12341
12381
  * Reset context (for testing)
12342
12382
  */
@@ -12351,9 +12391,14 @@ var init_prompt_builder = __esm({
12351
12391
  }
12352
12392
  /**
12353
12393
  * Load quality checklists from templates/checklists/
12394
+ * Uses lazy loading with TTL cache.
12395
+ * @see PRJ-76
12354
12396
  */
12355
12397
  loadChecklists() {
12356
- 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
+ }
12357
12402
  const checklistsDir = path25.join(__dirname, "..", "..", "templates", "checklists");
12358
12403
  const checklists = {};
12359
12404
  try {
@@ -12361,8 +12406,11 @@ var init_prompt_builder = __esm({
12361
12406
  const files = fs25.readdirSync(checklistsDir).filter((f) => f.endsWith(".md"));
12362
12407
  for (const file of files) {
12363
12408
  const name = file.replace(".md", "");
12364
- const content = fs25.readFileSync(path25.join(checklistsDir, file), "utf-8");
12365
- checklists[name] = content;
12409
+ const templatePath = path25.join(checklistsDir, file);
12410
+ const content = this.getTemplate(templatePath);
12411
+ if (content) {
12412
+ checklists[name] = content;
12413
+ }
12366
12414
  }
12367
12415
  }
12368
12416
  } catch (error) {
@@ -12371,6 +12419,7 @@ var init_prompt_builder = __esm({
12371
12419
  }
12372
12420
  }
12373
12421
  this._checklistsCache = checklists;
12422
+ this._checklistsCacheTime = now;
12374
12423
  return checklists;
12375
12424
  }
12376
12425
  /**
@@ -12468,9 +12517,14 @@ var init_prompt_builder = __esm({
12468
12517
  }
12469
12518
  /**
12470
12519
  * Load checklist routing template for Claude to decide which checklists apply
12520
+ * Uses lazy loading with TTL cache.
12521
+ * @see PRJ-76
12471
12522
  */
12472
12523
  loadChecklistRouting() {
12473
- 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
+ }
12474
12528
  const routingPath = path25.join(
12475
12529
  __dirname,
12476
12530
  "..",
@@ -12479,14 +12533,10 @@ var init_prompt_builder = __esm({
12479
12533
  "agentic",
12480
12534
  "checklist-routing.md"
12481
12535
  );
12482
- try {
12483
- if (fs25.existsSync(routingPath)) {
12484
- this._checklistRoutingCache = fs25.readFileSync(routingPath, "utf-8");
12485
- }
12486
- } catch (error) {
12487
- if (!isNotFoundError(error)) {
12488
- console.error(`Checklist routing warning: ${error.message}`);
12489
- }
12536
+ const content = this.getTemplate(routingPath);
12537
+ if (content) {
12538
+ this._checklistRoutingCache = content;
12539
+ this._checklistRoutingCacheTime = now;
12490
12540
  }
12491
12541
  return this._checklistRoutingCache || null;
12492
12542
  }
@@ -24664,7 +24714,7 @@ var require_package = __commonJS({
24664
24714
  "package.json"(exports, module) {
24665
24715
  module.exports = {
24666
24716
  name: "prjct-cli",
24667
- version: "0.52.0",
24717
+ version: "0.53.0",
24668
24718
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
24669
24719
  main: "core/index.ts",
24670
24720
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.52.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": {