specsmd 0.1.61 → 0.1.62

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.
@@ -1127,12 +1127,6 @@ function createDashboardApp(deps) {
1127
1127
  columns: Math.max(1, stdout.columns || process.stdout.columns || 120),
1128
1128
  rows: Math.max(1, stdout.rows || process.stdout.rows || 40)
1129
1129
  });
1130
-
1131
- // Resize in some terminals can leave stale frame rows behind.
1132
- // Keep the clear operation minimal to avoid triggering scrollback churn.
1133
- if (typeof stdout.write === 'function' && stdout.isTTY !== false) {
1134
- stdout.write('\u001B[H\u001B[J');
1135
- }
1136
1130
  };
1137
1131
 
1138
1132
  updateSize();
@@ -6,6 +6,73 @@ const {
6
6
  loadGitCommitPreview
7
7
  } = require('../git/changes');
8
8
 
9
+ const MAX_PREVIEW_CACHE_ENTRIES = 64;
10
+ const previewContentCache = new Map();
11
+
12
+ function getFilePreviewCacheKey(filePath) {
13
+ if (typeof filePath !== 'string' || filePath.trim() === '') {
14
+ return null;
15
+ }
16
+
17
+ try {
18
+ const stat = fs.statSync(filePath);
19
+ return `file:${filePath}:${stat.size}:${Math.floor(stat.mtimeMs)}`;
20
+ } catch {
21
+ return `file:${filePath}:missing`;
22
+ }
23
+ }
24
+
25
+ function getGitPreviewCacheKey(fileEntry, isGitCommitPreview) {
26
+ if (!fileEntry || typeof fileEntry !== 'object') {
27
+ return null;
28
+ }
29
+
30
+ const repoRoot = typeof fileEntry.repoRoot === 'string' ? fileEntry.repoRoot : '';
31
+ if (isGitCommitPreview) {
32
+ const commitHash = typeof fileEntry.commitHash === 'string' ? fileEntry.commitHash : '';
33
+ return `git-commit:${repoRoot}:${commitHash}`;
34
+ }
35
+
36
+ const bucket = typeof fileEntry.bucket === 'string' ? fileEntry.bucket : '';
37
+ const relativePath = typeof fileEntry.relativePath === 'string' ? fileEntry.relativePath : '';
38
+ const pathValue = typeof fileEntry.path === 'string' ? fileEntry.path : '';
39
+ return `git-diff:${repoRoot}:${bucket}:${relativePath}:${pathValue}`;
40
+ }
41
+
42
+ function getCachedPreviewContent(cacheKey) {
43
+ if (!cacheKey || !previewContentCache.has(cacheKey)) {
44
+ return null;
45
+ }
46
+
47
+ const cached = previewContentCache.get(cacheKey);
48
+ previewContentCache.delete(cacheKey);
49
+ previewContentCache.set(cacheKey, cached);
50
+ return Array.isArray(cached) ? cached : null;
51
+ }
52
+
53
+ function setCachedPreviewContent(cacheKey, lines) {
54
+ if (!cacheKey || !Array.isArray(lines)) {
55
+ return;
56
+ }
57
+
58
+ if (previewContentCache.has(cacheKey)) {
59
+ previewContentCache.delete(cacheKey);
60
+ }
61
+ previewContentCache.set(cacheKey, lines);
62
+
63
+ while (previewContentCache.size > MAX_PREVIEW_CACHE_ENTRIES) {
64
+ const oldest = previewContentCache.keys().next().value;
65
+ if (!oldest) {
66
+ break;
67
+ }
68
+ previewContentCache.delete(oldest);
69
+ }
70
+ }
71
+
72
+ function clearPreviewContentCache() {
73
+ previewContentCache.clear();
74
+ }
75
+
9
76
  function buildPreviewLines(fileEntry, width, scrollOffset, options = {}) {
10
77
  const fullDocument = options?.fullDocument === true;
11
78
 
@@ -16,24 +83,31 @@ function buildPreviewLines(fileEntry, width, scrollOffset, options = {}) {
16
83
  const isGitFilePreview = fileEntry.previewType === 'git-diff';
17
84
  const isGitCommitPreview = fileEntry.previewType === 'git-commit-diff';
18
85
  const isGitPreview = isGitFilePreview || isGitCommitPreview;
19
- let rawLines = [];
20
- if (isGitPreview) {
21
- const diffText = isGitCommitPreview
22
- ? loadGitCommitPreview(fileEntry)
23
- : loadGitDiffPreview(fileEntry);
24
- rawLines = String(diffText || '').split(/\r?\n/);
25
- } else {
26
- let content;
27
- try {
28
- content = fs.readFileSync(fileEntry.path, 'utf8');
29
- } catch (error) {
30
- return [{
31
- text: truncate(`Unable to read ${fileEntry.label || fileEntry.path}: ${error.message}`, width),
32
- color: 'red',
33
- bold: false
34
- }];
86
+ const cacheKey = isGitPreview
87
+ ? getGitPreviewCacheKey(fileEntry, isGitCommitPreview)
88
+ : getFilePreviewCacheKey(fileEntry.path);
89
+ let rawLines = getCachedPreviewContent(cacheKey);
90
+ if (!rawLines) {
91
+ if (isGitPreview) {
92
+ const diffText = isGitCommitPreview
93
+ ? loadGitCommitPreview(fileEntry)
94
+ : loadGitDiffPreview(fileEntry);
95
+ rawLines = String(diffText || '').split(/\r?\n/);
96
+ } else {
97
+ let content;
98
+ try {
99
+ content = fs.readFileSync(fileEntry.path, 'utf8');
100
+ } catch (error) {
101
+ return [{
102
+ text: truncate(`Unable to read ${fileEntry.label || fileEntry.path}: ${error.message}`, width),
103
+ color: 'red',
104
+ bold: false
105
+ }];
106
+ }
107
+ rawLines = String(content).split(/\r?\n/);
35
108
  }
36
- rawLines = String(content).split(/\r?\n/);
109
+
110
+ setCachedPreviewContent(cacheKey, rawLines);
37
111
  }
38
112
 
39
113
  const headLine = {
@@ -141,5 +215,6 @@ function allocateSingleColumnPanels(candidates, rowsBudget) {
141
215
 
142
216
  module.exports = {
143
217
  buildPreviewLines,
144
- allocateSingleColumnPanels
218
+ allocateSingleColumnPanels,
219
+ clearPreviewContentCache
145
220
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.61",
3
+ "version": "0.1.62",
4
4
  "description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {