prjct-cli 1.7.2 → 1.7.4

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.
@@ -7572,28 +7572,16 @@ var init_context_builder = __esm({
7572
7572
  init_config_manager();
7573
7573
  init_path_manager();
7574
7574
  init_fs();
7575
+ init_cache();
7575
7576
  ContextBuilder = class {
7576
7577
  static {
7577
7578
  __name(this, "ContextBuilder");
7578
7579
  }
7579
7580
  _cache;
7580
- _cacheTimeout;
7581
- _lastCacheTime;
7582
- _mtimes;
7581
+ _currentProjectId;
7583
7582
  constructor() {
7584
- this._cache = /* @__PURE__ */ new Map();
7585
- this._cacheTimeout = 5e3;
7586
- this._lastCacheTime = null;
7587
- this._mtimes = /* @__PURE__ */ new Map();
7588
- }
7589
- /**
7590
- * Clear cache if stale or force clear
7591
- */
7592
- _clearCacheIfStale(force = false) {
7593
- if (force || !this._lastCacheTime || Date.now() - this._lastCacheTime > this._cacheTimeout) {
7594
- this._cache.clear();
7595
- this._lastCacheTime = Date.now();
7596
- }
7583
+ this._cache = new TTLCache({ ttl: 5e3, maxSize: 200 });
7584
+ this._currentProjectId = null;
7597
7585
  }
7598
7586
  /**
7599
7587
  * Build full project context for Claude
@@ -7601,6 +7589,10 @@ var init_context_builder = __esm({
7601
7589
  async build(projectPath, commandParams = {}) {
7602
7590
  const projectId = await config_manager_default.getProjectId(projectPath);
7603
7591
  const globalPath = path_manager_default.getGlobalProjectPath(projectId);
7592
+ if (this._currentProjectId !== null && this._currentProjectId !== projectId) {
7593
+ this._cache.clear();
7594
+ }
7595
+ this._currentProjectId = projectId;
7604
7596
  return {
7605
7597
  // Project identification
7606
7598
  projectId,
@@ -7636,23 +7628,20 @@ var init_context_builder = __esm({
7636
7628
  * Uses Promise.all() for 40-60% faster file I/O
7637
7629
  */
7638
7630
  async loadState(context2, onlyKeys = null) {
7639
- this._clearCacheIfStale();
7640
7631
  const state = {};
7641
7632
  const entries = Object.entries(context2.paths);
7642
7633
  const filteredEntries = onlyKeys ? entries.filter(([key]) => onlyKeys.includes(key)) : entries;
7643
7634
  for (const [, filePath] of filteredEntries) {
7644
- if (this._cache.has(filePath)) {
7635
+ const cachedEntry = this._cache.get(filePath);
7636
+ if (cachedEntry !== null) {
7645
7637
  try {
7646
7638
  const stat = await fs21.stat(filePath);
7647
- const cachedMtime = this._mtimes.get(filePath);
7648
- if (!cachedMtime || stat.mtimeMs > cachedMtime) {
7639
+ if (!cachedEntry.mtime || stat.mtimeMs > cachedEntry.mtime) {
7649
7640
  this._cache.delete(filePath);
7650
- this._mtimes.delete(filePath);
7651
7641
  }
7652
7642
  } catch (error) {
7653
7643
  if (isNotFoundError(error)) {
7654
7644
  this._cache.delete(filePath);
7655
- this._mtimes.delete(filePath);
7656
7645
  } else {
7657
7646
  throw error;
7658
7647
  }
@@ -7661,8 +7650,9 @@ var init_context_builder = __esm({
7661
7650
  }
7662
7651
  const uncachedEntries = [];
7663
7652
  for (const [key, filePath] of filteredEntries) {
7664
- if (this._cache.has(filePath)) {
7665
- state[key] = this._cache.get(filePath);
7653
+ const cachedEntry = this._cache.get(filePath);
7654
+ if (cachedEntry !== null) {
7655
+ state[key] = cachedEntry.content;
7666
7656
  } else {
7667
7657
  uncachedEntries.push([key, filePath]);
7668
7658
  }
@@ -7685,10 +7675,7 @@ var init_context_builder = __esm({
7685
7675
  const results = await Promise.all(readPromises);
7686
7676
  for (const { key, filePath, content, mtime } of results) {
7687
7677
  state[key] = content;
7688
- this._cache.set(filePath, content);
7689
- if (mtime) {
7690
- this._mtimes.set(filePath, mtime);
7691
- }
7678
+ this._cache.set(filePath, { content, mtime });
7692
7679
  }
7693
7680
  }
7694
7681
  return state;
@@ -7733,12 +7720,12 @@ var init_context_builder = __esm({
7733
7720
  * Utility for custom file sets
7734
7721
  */
7735
7722
  async batchRead(filePaths) {
7736
- this._clearCacheIfStale();
7737
7723
  const results = /* @__PURE__ */ new Map();
7738
7724
  const uncachedPaths = [];
7739
7725
  for (const filePath of filePaths) {
7740
- if (this._cache.has(filePath)) {
7741
- results.set(filePath, this._cache.get(filePath));
7726
+ const cachedEntry = this._cache.get(filePath);
7727
+ if (cachedEntry !== null) {
7728
+ results.set(filePath, cachedEntry.content);
7742
7729
  } else {
7743
7730
  uncachedPaths.push(filePath);
7744
7731
  }
@@ -7758,7 +7745,7 @@ var init_context_builder = __esm({
7758
7745
  const readResults = await Promise.all(readPromises);
7759
7746
  for (const { filePath, content } of readResults) {
7760
7747
  results.set(filePath, content);
7761
- this._cache.set(filePath, content);
7748
+ this._cache.set(filePath, { content, mtime: null });
7762
7749
  }
7763
7750
  }
7764
7751
  return results;
@@ -7773,7 +7760,8 @@ var init_context_builder = __esm({
7773
7760
  * Force clear entire cache
7774
7761
  */
7775
7762
  clearCache() {
7776
- this._clearCacheIfStale(true);
7763
+ this._cache.clear();
7764
+ this._currentProjectId = null;
7777
7765
  }
7778
7766
  /**
7779
7767
  * Check file existence
@@ -7793,11 +7781,7 @@ var init_context_builder = __esm({
7793
7781
  * Get cache stats (for debugging/metrics)
7794
7782
  */
7795
7783
  getCacheStats() {
7796
- return {
7797
- size: this._cache.size,
7798
- lastRefresh: this._lastCacheTime,
7799
- timeout: this._cacheTimeout
7800
- };
7784
+ return this._cache.stats();
7801
7785
  }
7802
7786
  };
7803
7787
  contextBuilder = new ContextBuilder();
@@ -10094,10 +10078,12 @@ var init_memory_system = __esm({
10094
10078
  return getLastJsonLines(sessionPath, limit);
10095
10079
  }
10096
10080
  };
10097
- PatternStore = class extends CachedStore {
10081
+ PatternStore = class _PatternStore extends CachedStore {
10098
10082
  static {
10099
10083
  __name(this, "PatternStore");
10100
10084
  }
10085
+ static MAX_CONTEXTS = 20;
10086
+ static ARCHIVE_AGE_DAYS = 90;
10101
10087
  getFilename() {
10102
10088
  return "patterns.json";
10103
10089
  }
@@ -10110,6 +10096,13 @@ var init_memory_system = __esm({
10110
10096
  counters: {}
10111
10097
  };
10112
10098
  }
10099
+ afterLoad(patterns) {
10100
+ for (const decision of Object.values(patterns.decisions)) {
10101
+ if (decision.contexts.length > _PatternStore.MAX_CONTEXTS) {
10102
+ decision.contexts = decision.contexts.slice(-_PatternStore.MAX_CONTEXTS);
10103
+ }
10104
+ }
10105
+ }
10113
10106
  // Convenience alias for backward compatibility
10114
10107
  async loadPatterns(projectId) {
10115
10108
  return this.load(projectId);
@@ -10137,6 +10130,9 @@ var init_memory_system = __esm({
10137
10130
  decision.lastSeen = now;
10138
10131
  if (context2 && !decision.contexts.includes(context2)) {
10139
10132
  decision.contexts.push(context2);
10133
+ if (decision.contexts.length > _PatternStore.MAX_CONTEXTS) {
10134
+ decision.contexts = decision.contexts.slice(-_PatternStore.MAX_CONTEXTS);
10135
+ }
10140
10136
  }
10141
10137
  if (options.userConfirmed) {
10142
10138
  decision.userConfirmed = true;
@@ -10246,6 +10242,39 @@ var init_memory_system = __esm({
10246
10242
  preferences: Object.keys(patterns.preferences).length
10247
10243
  };
10248
10244
  }
10245
+ _getArchivePath(projectId) {
10246
+ const basePath = path22.join(path_manager_default.getGlobalProjectPath(projectId), "memory");
10247
+ return path22.join(basePath, "patterns-archive.json");
10248
+ }
10249
+ async archiveStaleDecisions(projectId) {
10250
+ const patterns = await this.load(projectId);
10251
+ const now = Date.now();
10252
+ const cutoff = _PatternStore.ARCHIVE_AGE_DAYS * 24 * 60 * 60 * 1e3;
10253
+ const staleKeys = [];
10254
+ for (const [key, decision] of Object.entries(patterns.decisions)) {
10255
+ const lastSeenMs = new Date(decision.lastSeen).getTime();
10256
+ if (now - lastSeenMs > cutoff) {
10257
+ staleKeys.push(key);
10258
+ }
10259
+ }
10260
+ if (staleKeys.length === 0) return 0;
10261
+ const archivePath = this._getArchivePath(projectId);
10262
+ let archive = {};
10263
+ try {
10264
+ const content = await fs24.readFile(archivePath, "utf-8");
10265
+ archive = JSON.parse(content);
10266
+ } catch (error) {
10267
+ if (!isNotFoundError(error)) throw error;
10268
+ }
10269
+ for (const key of staleKeys) {
10270
+ archive[key] = patterns.decisions[key];
10271
+ delete patterns.decisions[key];
10272
+ }
10273
+ await fs24.mkdir(path22.dirname(archivePath), { recursive: true });
10274
+ await fs24.writeFile(archivePath, JSON.stringify(archive, null, 2), "utf-8");
10275
+ await this.save(projectId);
10276
+ return staleKeys.length;
10277
+ }
10249
10278
  };
10250
10279
  SemanticMemories = class extends CachedStore {
10251
10280
  static {
@@ -10727,6 +10756,9 @@ Context: ${context2}` : ""}`,
10727
10756
  getPatternsSummary(projectId) {
10728
10757
  return this._patternStore.getPatternsSummary(projectId);
10729
10758
  }
10759
+ archiveStaleDecisions(projectId) {
10760
+ return this._patternStore.archiveStaleDecisions(projectId);
10761
+ }
10730
10762
  // ===========================================================================
10731
10763
  // TIER 3: History
10732
10764
  // ===========================================================================
@@ -14219,6 +14251,97 @@ var init_outcomes2 = __esm({
14219
14251
  }
14220
14252
  });
14221
14253
 
14254
+ // core/agentic/injection-validator.ts
14255
+ function truncateToTokenBudget(text, maxTokens) {
14256
+ const maxChars = maxTokens * CHARS_PER_TOKEN2;
14257
+ if (text.length <= maxChars) return text;
14258
+ return `${text.substring(0, maxChars)}
14259
+ ... (truncated to ~${maxTokens} tokens)`;
14260
+ }
14261
+ function estimateTokens(text) {
14262
+ return Math.ceil(text.length / CHARS_PER_TOKEN2);
14263
+ }
14264
+ function filterSkillsByDomains(skills, detectedDomains) {
14265
+ if (detectedDomains.length === 0 || skills.length === 0) return skills;
14266
+ const relevantKeywords = /* @__PURE__ */ new Set();
14267
+ for (const domain of detectedDomains) {
14268
+ const keywords = DOMAIN_KEYWORDS3[domain.toLowerCase()];
14269
+ if (keywords) {
14270
+ for (const kw of keywords) relevantKeywords.add(kw);
14271
+ }
14272
+ relevantKeywords.add(domain.toLowerCase());
14273
+ }
14274
+ return skills.filter((skill) => {
14275
+ const text = `${skill.name} ${skill.content}`.toLowerCase();
14276
+ for (const kw of relevantKeywords) {
14277
+ if (text.includes(kw)) return true;
14278
+ }
14279
+ return false;
14280
+ });
14281
+ }
14282
+ var DEFAULT_BUDGETS, CHARS_PER_TOKEN2, DOMAIN_KEYWORDS3, InjectionBudgetTracker;
14283
+ var init_injection_validator = __esm({
14284
+ "core/agentic/injection-validator.ts"() {
14285
+ "use strict";
14286
+ DEFAULT_BUDGETS = {
14287
+ autoContext: 500,
14288
+ agentContent: 400,
14289
+ skillContent: 500,
14290
+ stateData: 1e3,
14291
+ memories: 600,
14292
+ totalPrompt: 8e3
14293
+ };
14294
+ CHARS_PER_TOKEN2 = 4;
14295
+ __name(truncateToTokenBudget, "truncateToTokenBudget");
14296
+ __name(estimateTokens, "estimateTokens");
14297
+ DOMAIN_KEYWORDS3 = {
14298
+ frontend: ["react", "vue", "svelte", "css", "html", "ui", "component", "frontend", "web", "dom"],
14299
+ backend: ["api", "server", "backend", "endpoint", "route", "middleware", "database", "sql"],
14300
+ testing: ["test", "spec", "jest", "vitest", "cypress", "playwright", "coverage", "assert"],
14301
+ devops: ["docker", "ci", "cd", "deploy", "kubernetes", "terraform", "pipeline", "github-actions"],
14302
+ docs: ["documentation", "readme", "guide", "tutorial", "markdown"],
14303
+ design: ["design", "ux", "ui", "figma", "wireframe", "layout", "accessibility"]
14304
+ };
14305
+ __name(filterSkillsByDomains, "filterSkillsByDomains");
14306
+ InjectionBudgetTracker = class {
14307
+ static {
14308
+ __name(this, "InjectionBudgetTracker");
14309
+ }
14310
+ used = 0;
14311
+ budgets;
14312
+ constructor(budgets = {}) {
14313
+ this.budgets = { ...DEFAULT_BUDGETS, ...budgets };
14314
+ }
14315
+ /** Add content and return it (possibly truncated to fit budget) */
14316
+ addSection(content, sectionBudget) {
14317
+ const truncated = truncateToTokenBudget(content, sectionBudget);
14318
+ const tokens = estimateTokens(truncated);
14319
+ if (this.used + tokens > this.budgets.totalPrompt) {
14320
+ const remaining = this.budgets.totalPrompt - this.used;
14321
+ if (remaining <= 0) return "";
14322
+ const fitted = truncateToTokenBudget(truncated, remaining);
14323
+ this.used += estimateTokens(fitted);
14324
+ return fitted;
14325
+ }
14326
+ this.used += tokens;
14327
+ return truncated;
14328
+ }
14329
+ /** Get remaining token budget */
14330
+ get remaining() {
14331
+ return Math.max(0, this.budgets.totalPrompt - this.used);
14332
+ }
14333
+ /** Get total tokens used */
14334
+ get totalUsed() {
14335
+ return this.used;
14336
+ }
14337
+ /** Get the budgets config */
14338
+ get config() {
14339
+ return this.budgets;
14340
+ }
14341
+ };
14342
+ }
14343
+ });
14344
+
14222
14345
  // core/agentic/prompt-builder.ts
14223
14346
  import fs29 from "node:fs/promises";
14224
14347
  import path28 from "node:path";
@@ -14231,6 +14354,7 @@ var init_prompt_builder = __esm({
14231
14354
  init_fs();
14232
14355
  init_fs_helpers();
14233
14356
  init_version();
14357
+ init_injection_validator();
14234
14358
  PromptBuilder = class {
14235
14359
  static {
14236
14360
  __name(this, "PromptBuilder");
@@ -14426,7 +14550,8 @@ var init_prompt_builder = __esm({
14426
14550
  }
14427
14551
  parts.push("---");
14428
14552
  parts.push("");
14429
- return parts.join("\n");
14553
+ const result = parts.join("\n");
14554
+ return truncateToTokenBudget(result, DEFAULT_BUDGETS.autoContext);
14430
14555
  }
14431
14556
  /**
14432
14557
  * Calculate elapsed time from ISO timestamp.
@@ -14560,8 +14685,10 @@ Apply specialized expertise. Read agent file for details if needed.
14560
14685
  parts.push(`Skills: ${agent2.skills.join(", ")}
14561
14686
  `);
14562
14687
  }
14563
- const truncatedContent = agent2.content.length > 1500 ? `${agent2.content.substring(0, 1500)}
14564
- ... (truncated, read full file for more)` : agent2.content;
14688
+ const truncatedContent = truncateToTokenBudget(
14689
+ agent2.content,
14690
+ DEFAULT_BUDGETS.agentContent
14691
+ );
14565
14692
  parts.push(`\`\`\`markdown
14566
14693
  ${truncatedContent}
14567
14694
  \`\`\`
@@ -14569,13 +14696,19 @@ ${truncatedContent}
14569
14696
  `);
14570
14697
  }
14571
14698
  }
14572
- if (orchestratorContext.skills.length > 0) {
14699
+ const relevantSkills = filterSkillsByDomains(
14700
+ orchestratorContext.skills,
14701
+ orchestratorContext.detectedDomains
14702
+ );
14703
+ if (relevantSkills.length > 0) {
14573
14704
  parts.push("### LOADED SKILLS (From Agent Frontmatter)\n\n");
14574
- for (const skill of orchestratorContext.skills) {
14705
+ for (const skill of relevantSkills) {
14575
14706
  parts.push(`#### Skill: ${skill.name}
14576
14707
  `);
14577
- const truncatedContent = skill.content.length > 2e3 ? `${skill.content.substring(0, 2e3)}
14578
- ... (truncated)` : skill.content;
14708
+ const truncatedContent = truncateToTokenBudget(
14709
+ skill.content,
14710
+ DEFAULT_BUDGETS.skillContent
14711
+ );
14579
14712
  parts.push(`\`\`\`markdown
14580
14713
  ${truncatedContent}
14581
14714
  \`\`\`
@@ -14803,28 +14936,20 @@ Show changes, list affected files, ask for confirmation.
14803
14936
  return parts.join("");
14804
14937
  }
14805
14938
  /**
14806
- * Filter state data to include only relevant portions for the prompt
14939
+ * Filter state data to include only relevant portions for the prompt.
14940
+ * Uses InjectionBudgetTracker to enforce cumulative token limits.
14807
14941
  */
14808
14942
  filterRelevantState(state) {
14809
14943
  if (!state || Object.keys(state).length === 0) return null;
14944
+ const tracker = new InjectionBudgetTracker({ totalPrompt: DEFAULT_BUDGETS.stateData });
14945
+ const criticalFiles = ["now", "next", "context", "analysis", "codePatterns"];
14810
14946
  const relevant = [];
14811
14947
  for (const [key, content] of Object.entries(state)) {
14812
14948
  if (content && content.trim()) {
14813
- const criticalFiles = ["now", "next", "context", "analysis", "codePatterns"];
14814
- if (criticalFiles.includes(key)) {
14815
- const display = content.length > 2e3 ? `${content.substring(0, 2e3)}
14816
- ... (truncated)` : content;
14817
- relevant.push(`### ${key}
14818
- ${display}`);
14819
- } else if (content.length < 1e3) {
14820
- relevant.push(`### ${key}
14821
- ${content}`);
14822
- } else {
14823
- relevant.push(
14824
- `### ${key}
14825
- ${content.substring(0, 500)}... (truncated, use Read tool for full content)`
14826
- );
14827
- }
14949
+ const sectionBudget = criticalFiles.includes(key) ? 500 : 250;
14950
+ const section = tracker.addSection(`### ${key}
14951
+ ${content}`, sectionBudget);
14952
+ if (section) relevant.push(section);
14828
14953
  }
14829
14954
  }
14830
14955
  return relevant.length > 0 ? relevant.join("\n\n") : null;
@@ -14865,7 +14990,8 @@ ${content.substring(0, 500)}... (truncated, use Read tool for full content)`
14865
14990
  Avoid:
14866
14991
  ${antiPatterns}`);
14867
14992
  }
14868
- const result = parts.join("\n").substring(0, 800);
14993
+ const joined = parts.join("\n");
14994
+ const result = truncateToTokenBudget(joined, 200);
14869
14995
  return result || null;
14870
14996
  }
14871
14997
  /**
@@ -16233,13 +16359,13 @@ var init_breakdown_service = __esm({
16233
16359
  });
16234
16360
 
16235
16361
  // core/services/context-selector.ts
16236
- var DEFAULT_TOKEN_BUDGET, DOMAIN_KEYWORDS3, ContextSelector, contextSelector;
16362
+ var DEFAULT_TOKEN_BUDGET, DOMAIN_KEYWORDS4, ContextSelector, contextSelector;
16237
16363
  var init_context_selector = __esm({
16238
16364
  "core/services/context-selector.ts"() {
16239
16365
  "use strict";
16240
16366
  init_index_storage();
16241
16367
  DEFAULT_TOKEN_BUDGET = 8e4;
16242
- DOMAIN_KEYWORDS3 = {
16368
+ DOMAIN_KEYWORDS4 = {
16243
16369
  payments: [
16244
16370
  "payment",
16245
16371
  "pay",
@@ -16422,7 +16548,7 @@ var init_context_selector = __esm({
16422
16548
  detectTaskDomains(description, projectDomains) {
16423
16549
  const normalizedDesc = description.toLowerCase();
16424
16550
  const detectedDomains = /* @__PURE__ */ new Set();
16425
- for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS3)) {
16551
+ for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS4)) {
16426
16552
  for (const keyword of keywords) {
16427
16553
  if (normalizedDesc.includes(keyword)) {
16428
16554
  detectedDomains.add(domain);
@@ -19043,8 +19169,8 @@ var init_analyzer2 = __esm({
19043
19169
 
19044
19170
  // core/services/diff-generator.ts
19045
19171
  import chalk11 from "chalk";
19046
- function estimateTokens(content) {
19047
- return Math.ceil(content.length / CHARS_PER_TOKEN2);
19172
+ function estimateTokens2(content) {
19173
+ return Math.ceil(content.length / CHARS_PER_TOKEN3);
19048
19174
  }
19049
19175
  function parseMarkdownSections(content) {
19050
19176
  const lines = content.split("\n");
@@ -19087,8 +19213,8 @@ function generateSyncDiff(oldContent, newContent) {
19087
19213
  modified: [],
19088
19214
  removed: [],
19089
19215
  preserved: [],
19090
- tokensBefore: estimateTokens(oldContent),
19091
- tokensAfter: estimateTokens(newContent),
19216
+ tokensBefore: estimateTokens2(oldContent),
19217
+ tokensAfter: estimateTokens2(newContent),
19092
19218
  tokenDelta: 0
19093
19219
  };
19094
19220
  diff.tokenDelta = diff.tokensAfter - diff.tokensBefore;
@@ -19242,12 +19368,12 @@ function formatFullDiff(diff, options = {}) {
19242
19368
  }
19243
19369
  return lines.join("\n");
19244
19370
  }
19245
- var CHARS_PER_TOKEN2;
19371
+ var CHARS_PER_TOKEN3;
19246
19372
  var init_diff_generator = __esm({
19247
19373
  "core/services/diff-generator.ts"() {
19248
19374
  "use strict";
19249
- CHARS_PER_TOKEN2 = 4;
19250
- __name(estimateTokens, "estimateTokens");
19375
+ CHARS_PER_TOKEN3 = 4;
19376
+ __name(estimateTokens2, "estimateTokens");
19251
19377
  __name(parseMarkdownSections, "parseMarkdownSections");
19252
19378
  __name(isPreservedSection, "isPreservedSection");
19253
19379
  __name(generateSyncDiff, "generateSyncDiff");
@@ -23429,7 +23555,7 @@ You are the ${name} expert for this project. Apply best practices for the detect
23429
23555
  * Token estimation: ~4 chars per token (industry standard)
23430
23556
  */
23431
23557
  async recordSyncMetrics(stats, contextFiles, agents, duration) {
23432
- const CHARS_PER_TOKEN3 = 4;
23558
+ const CHARS_PER_TOKEN4 = 4;
23433
23559
  let filteredChars = 0;
23434
23560
  for (const file of contextFiles) {
23435
23561
  try {
@@ -23452,7 +23578,7 @@ You are the ${name} expert for this project. Apply best practices for the detect
23452
23578
  });
23453
23579
  }
23454
23580
  }
23455
- const filteredSize = Math.floor(filteredChars / CHARS_PER_TOKEN3);
23581
+ const filteredSize = Math.floor(filteredChars / CHARS_PER_TOKEN4);
23456
23582
  const avgTokensPerFile = 500;
23457
23583
  const originalSize = stats.fileCount * avgTokensPerFile;
23458
23584
  const compressionRate = originalSize > 0 ? Math.max(0, (originalSize - filteredSize) / originalSize) : 0;
@@ -29016,7 +29142,7 @@ var require_package = __commonJS({
29016
29142
  "package.json"(exports, module) {
29017
29143
  module.exports = {
29018
29144
  name: "prjct-cli",
29019
- version: "1.7.2",
29145
+ version: "1.7.4",
29020
29146
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
29021
29147
  main: "core/index.ts",
29022
29148
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "1.7.2",
3
+ "version": "1.7.4",
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": {