@vibe-validate/history 0.12.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/README.md +239 -0
- package/dist/health-check.d.ts +12 -0
- package/dist/health-check.d.ts.map +1 -0
- package/dist/health-check.js +68 -0
- package/dist/health-check.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/pruner.d.ts +22 -0
- package/dist/pruner.d.ts.map +1 -0
- package/dist/pruner.js +107 -0
- package/dist/pruner.js.map +1 -0
- package/dist/reader.d.ts +35 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +86 -0
- package/dist/reader.js.map +1 -0
- package/dist/recorder.d.ts +22 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +151 -0
- package/dist/recorder.js.map +1 -0
- package/dist/truncate.d.ts +13 -0
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +39 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# @vibe-validate/history
|
|
2
|
+
|
|
3
|
+
Validation history tracking via git notes for vibe-validate.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Git Notes Storage**: Store validation results keyed by git tree hash
|
|
8
|
+
- **Distributed Cache**: Remember validation for EVERY tree hash
|
|
9
|
+
- **Worktree Stability Check**: Verify tree unchanged during validation
|
|
10
|
+
- **Multi-Run Support**: Handle flaky tests, multiple branches at same tree
|
|
11
|
+
- **Output Truncation**: Efficient storage (10KB max per step)
|
|
12
|
+
- **Proactive Health**: Warn when pruning recommended
|
|
13
|
+
- **Privacy-First**: Local by default, no auto-sharing
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @vibe-validate/history
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Record Validation History
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { recordValidationHistory, checkWorktreeStability } from '@vibe-validate/history';
|
|
27
|
+
import { getGitTreeHash } from '@vibe-validate/git';
|
|
28
|
+
|
|
29
|
+
// Get tree hash before validation
|
|
30
|
+
const treeHashBefore = await getGitTreeHash();
|
|
31
|
+
|
|
32
|
+
// Run validation
|
|
33
|
+
const result = await runValidation(config);
|
|
34
|
+
|
|
35
|
+
// Check stability (did tree change during validation?)
|
|
36
|
+
const stability = await checkWorktreeStability(treeHashBefore);
|
|
37
|
+
|
|
38
|
+
if (!stability.stable) {
|
|
39
|
+
console.warn('⚠️ Worktree changed during validation - not caching');
|
|
40
|
+
} else {
|
|
41
|
+
// Record to git notes
|
|
42
|
+
const recordResult = await recordValidationHistory(treeHashBefore, result);
|
|
43
|
+
|
|
44
|
+
if (recordResult.recorded) {
|
|
45
|
+
console.log('📝 History recorded');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Read Validation History
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { readHistoryNote, listHistoryTreeHashes } from '@vibe-validate/history';
|
|
54
|
+
|
|
55
|
+
// Read specific tree hash
|
|
56
|
+
const note = await readHistoryNote('abc123def456...');
|
|
57
|
+
|
|
58
|
+
if (note) {
|
|
59
|
+
console.log(`Found ${note.runs.length} validation runs for this tree`);
|
|
60
|
+
|
|
61
|
+
for (const run of note.runs) {
|
|
62
|
+
console.log(`- ${run.timestamp}: ${run.passed ? 'PASSED' : 'FAILED'}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// List all tree hashes with history
|
|
67
|
+
const treeHashes = await listHistoryTreeHashes();
|
|
68
|
+
console.log(`Total history: ${treeHashes.length} tree hashes`);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Health Check
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { checkHistoryHealth } from '@vibe-validate/history';
|
|
75
|
+
|
|
76
|
+
const health = await checkHistoryHealth();
|
|
77
|
+
|
|
78
|
+
if (health.shouldWarn) {
|
|
79
|
+
console.log(health.warningMessage);
|
|
80
|
+
// Example output:
|
|
81
|
+
// ℹ️ Validation history has grown large (127 tree hashes)
|
|
82
|
+
// Found 15 notes older than 90 days
|
|
83
|
+
// Consider pruning: vibe-validate history prune --older-than "90 days"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Prune Old History
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { pruneHistoryByAge } from '@vibe-validate/history';
|
|
91
|
+
|
|
92
|
+
// Prune notes older than 90 days (dry run)
|
|
93
|
+
const dryRunResult = await pruneHistoryByAge(90, {}, true);
|
|
94
|
+
console.log(`Would prune ${dryRunResult.notesPruned} notes`);
|
|
95
|
+
|
|
96
|
+
// Actually prune
|
|
97
|
+
const pruneResult = await pruneHistoryByAge(90);
|
|
98
|
+
console.log(`Pruned ${pruneResult.notesPruned} notes`);
|
|
99
|
+
console.log(`${pruneResult.notesRemaining} notes remaining`);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import type { HistoryConfig } from '@vibe-validate/history';
|
|
106
|
+
|
|
107
|
+
const config: HistoryConfig = {
|
|
108
|
+
enabled: true,
|
|
109
|
+
|
|
110
|
+
gitNotes: {
|
|
111
|
+
ref: 'vibe-validate/runs', // Git notes ref namespace
|
|
112
|
+
maxRunsPerTree: 10, // Keep last 10 runs per tree
|
|
113
|
+
maxOutputBytes: 10000, // 10KB max per step output
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
retention: {
|
|
117
|
+
warnAfterDays: 90, // Warn about notes >90 days old
|
|
118
|
+
warnAfterCount: 100, // Warn when >100 tree hashes
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### Types
|
|
126
|
+
|
|
127
|
+
- **ValidationRun**: Single validation run entry
|
|
128
|
+
- **HistoryNote**: Git note structure (array of runs per tree)
|
|
129
|
+
- **RecordResult**: Result of recording validation
|
|
130
|
+
- **StabilityCheck**: Worktree stability check result
|
|
131
|
+
- **HealthCheckResult**: History health check result
|
|
132
|
+
- **PruneResult**: Result of pruning operation
|
|
133
|
+
|
|
134
|
+
### Functions
|
|
135
|
+
|
|
136
|
+
#### Recording
|
|
137
|
+
- `recordValidationHistory(treeHash, result, config?)`: Record validation to git notes
|
|
138
|
+
- `checkWorktreeStability(treeHashBefore)`: Check if tree changed during validation
|
|
139
|
+
|
|
140
|
+
#### Reading
|
|
141
|
+
- `readHistoryNote(treeHash, notesRef?)`: Read note for specific tree hash
|
|
142
|
+
- `listHistoryTreeHashes(notesRef?)`: List all tree hashes with notes
|
|
143
|
+
- `getAllHistoryNotes(notesRef?)`: Get all history notes
|
|
144
|
+
- `hasHistoryForTree(treeHash, notesRef?)`: Check if history exists
|
|
145
|
+
|
|
146
|
+
#### Pruning
|
|
147
|
+
- `pruneHistoryByAge(olderThanDays, config?, dryRun?)`: Prune by age
|
|
148
|
+
- `pruneAllHistory(config?, dryRun?)`: Prune all history
|
|
149
|
+
|
|
150
|
+
#### Health
|
|
151
|
+
- `checkHistoryHealth(config?)`: Check history health
|
|
152
|
+
|
|
153
|
+
#### Utilities
|
|
154
|
+
- `truncateValidationOutput(result, maxBytes?)`: Truncate validation output
|
|
155
|
+
|
|
156
|
+
## Design
|
|
157
|
+
|
|
158
|
+
### Git Tree Hash Caching
|
|
159
|
+
|
|
160
|
+
Traditional validation caching (single state file):
|
|
161
|
+
- Only remembers ONE tree hash
|
|
162
|
+
- Switch branches → cache miss
|
|
163
|
+
- Revert changes → cache miss
|
|
164
|
+
|
|
165
|
+
Git notes caching (this package):
|
|
166
|
+
- Remembers EVERY tree hash
|
|
167
|
+
- Switch branches → cache hit (if tree unchanged)
|
|
168
|
+
- Revert changes → cache hit
|
|
169
|
+
- **Result**: Improved cache effectiveness
|
|
170
|
+
|
|
171
|
+
### Note Structure
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
# Git note: refs/notes/vibe-validate/runs → tree hash abc123
|
|
175
|
+
treeHash: "abc123def456..."
|
|
176
|
+
runs:
|
|
177
|
+
- id: "run-1729522215123"
|
|
178
|
+
timestamp: "2025-10-21T14:30:15.123Z"
|
|
179
|
+
duration: 2300
|
|
180
|
+
passed: true
|
|
181
|
+
branch: "feature/foo"
|
|
182
|
+
headCommit: "9abc3c4"
|
|
183
|
+
uncommittedChanges: false
|
|
184
|
+
result:
|
|
185
|
+
# Full validation result (output truncated to 10KB/step)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Worktree Stability
|
|
189
|
+
|
|
190
|
+
Critical safety feature: Verify tree unchanged during validation.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Before validation
|
|
194
|
+
const treeHashBefore = await getGitTreeHash();
|
|
195
|
+
|
|
196
|
+
// Run validation (potentially long-running)
|
|
197
|
+
const result = await runValidation();
|
|
198
|
+
|
|
199
|
+
// After validation - check stability
|
|
200
|
+
const stability = await checkWorktreeStability(treeHashBefore);
|
|
201
|
+
|
|
202
|
+
if (!stability.stable) {
|
|
203
|
+
// Tree changed during validation - don't cache
|
|
204
|
+
console.warn('⚠️ Worktree changed during validation');
|
|
205
|
+
return result; // Skip recording
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Safe to cache
|
|
209
|
+
await recordValidationHistory(treeHashBefore, result);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Privacy & Scope
|
|
213
|
+
|
|
214
|
+
**Current Scope**: Local user validation caching only
|
|
215
|
+
|
|
216
|
+
**What's recorded**:
|
|
217
|
+
- ✅ Tree hash (content-based, no PII)
|
|
218
|
+
- ✅ Timestamp
|
|
219
|
+
- ✅ Branch name
|
|
220
|
+
- ✅ HEAD commit
|
|
221
|
+
- ✅ Validation results (truncated output)
|
|
222
|
+
|
|
223
|
+
**What's NOT recorded** (privacy-first):
|
|
224
|
+
- ❌ Author name/email (already in git history)
|
|
225
|
+
- ❌ Machine hostname
|
|
226
|
+
- ❌ Environment variables
|
|
227
|
+
- ❌ File paths with usernames
|
|
228
|
+
|
|
229
|
+
**Sharing**: Local by default (no auto-push to remote)
|
|
230
|
+
|
|
231
|
+
## Future Extensions
|
|
232
|
+
|
|
233
|
+
- Team sharing (opt-in)
|
|
234
|
+
- Environment tracking (OS, Node version, CI matrix)
|
|
235
|
+
- Analytics export (JSONL for SQLite/DuckDB analysis)
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History health check utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { HealthCheckResult, HistoryConfig } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check validation history health
|
|
7
|
+
*
|
|
8
|
+
* @param config - History configuration
|
|
9
|
+
* @returns Health check result
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkHistoryHealth(config?: HistoryConfig): Promise<HealthCheckResult>;
|
|
12
|
+
//# sourceMappingURL=health-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../src/health-check.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAInE;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAkE5B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History health check utilities
|
|
3
|
+
*/
|
|
4
|
+
import { DEFAULT_HISTORY_CONFIG } from './types.js';
|
|
5
|
+
import { getAllHistoryNotes } from './reader.js';
|
|
6
|
+
/**
|
|
7
|
+
* Check validation history health
|
|
8
|
+
*
|
|
9
|
+
* @param config - History configuration
|
|
10
|
+
* @returns Health check result
|
|
11
|
+
*/
|
|
12
|
+
export async function checkHistoryHealth(config = {}) {
|
|
13
|
+
const mergedConfig = {
|
|
14
|
+
...DEFAULT_HISTORY_CONFIG,
|
|
15
|
+
...config,
|
|
16
|
+
retention: {
|
|
17
|
+
...DEFAULT_HISTORY_CONFIG.retention,
|
|
18
|
+
...config.retention,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
// Type assertion safe: DEFAULT_HISTORY_CONFIG is Required<HistoryConfig>
|
|
22
|
+
const warnAfterDays = (mergedConfig.retention.warnAfterDays ?? DEFAULT_HISTORY_CONFIG.retention.warnAfterDays);
|
|
23
|
+
const warnAfterCount = (mergedConfig.retention.warnAfterCount ?? DEFAULT_HISTORY_CONFIG.retention.warnAfterCount);
|
|
24
|
+
const allNotes = await getAllHistoryNotes(mergedConfig.gitNotes?.ref || DEFAULT_HISTORY_CONFIG.gitNotes.ref);
|
|
25
|
+
const totalNotes = allNotes.length;
|
|
26
|
+
const cutoffTime = Date.now() - warnAfterDays * 24 * 60 * 60 * 1000;
|
|
27
|
+
let oldNotesCount = 0;
|
|
28
|
+
for (const note of allNotes) {
|
|
29
|
+
if (!note.runs || note.runs.length === 0) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Check oldest run
|
|
33
|
+
const oldestRun = note.runs[0];
|
|
34
|
+
const oldestTimestamp = new Date(oldestRun.timestamp).getTime();
|
|
35
|
+
if (oldestTimestamp < cutoffTime) {
|
|
36
|
+
oldNotesCount++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Determine if we should warn
|
|
40
|
+
const shouldWarnCount = totalNotes > warnAfterCount;
|
|
41
|
+
const shouldWarnAge = oldNotesCount > 0;
|
|
42
|
+
const shouldWarn = shouldWarnCount || shouldWarnAge;
|
|
43
|
+
let warningMessage;
|
|
44
|
+
if (shouldWarnCount && shouldWarnAge) {
|
|
45
|
+
warningMessage =
|
|
46
|
+
`ℹ️ Validation history has grown large (${totalNotes} tree hashes)\n` +
|
|
47
|
+
` Found ${oldNotesCount} notes older than ${warnAfterDays} days\n` +
|
|
48
|
+
` Consider pruning: vibe-validate history prune --older-than "${warnAfterDays} days"`;
|
|
49
|
+
}
|
|
50
|
+
else if (shouldWarnCount) {
|
|
51
|
+
warningMessage =
|
|
52
|
+
`ℹ️ Validation history has grown large (${totalNotes} tree hashes)\n` +
|
|
53
|
+
` Consider pruning: vibe-validate history prune --older-than "${warnAfterDays} days"`;
|
|
54
|
+
}
|
|
55
|
+
else if (shouldWarnAge) {
|
|
56
|
+
warningMessage =
|
|
57
|
+
`ℹ️ Found validation history older than ${warnAfterDays} days\n` +
|
|
58
|
+
` ${oldNotesCount} tree hashes can be pruned\n` +
|
|
59
|
+
` Run: vibe-validate history prune --older-than "${warnAfterDays} days"`;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
totalNotes,
|
|
63
|
+
oldNotesCount,
|
|
64
|
+
shouldWarn,
|
|
65
|
+
warningMessage,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=health-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-check.js","sourceRoot":"","sources":["../src/health-check.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAwB,EAAE;IAE1B,MAAM,YAAY,GAAG;QACnB,GAAG,sBAAsB;QACzB,GAAG,MAAM;QACT,SAAS,EAAE;YACT,GAAG,sBAAsB,CAAC,SAAS;YACnC,GAAG,MAAM,CAAC,SAAS;SACpB;KACF,CAAC;IAEF,yEAAyE;IACzE,MAAM,aAAa,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,aAAa,IAAI,sBAAsB,CAAC,SAAS,CAAC,aAAa,CAAW,CAAC;IACzH,MAAM,cAAc,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,cAAc,IAAI,sBAAsB,CAAC,SAAS,CAAC,cAAc,CAAW,CAAC;IAE5H,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CACvC,YAAY,CAAC,QAAQ,EAAE,GAAG,IAAI,sBAAsB,CAAC,QAAQ,CAAC,GAAG,CAClE,CAAC;IAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAEpE,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,SAAS;QACX,CAAC;QAED,mBAAmB;QACnB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,eAAe,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;QAEhE,IAAI,eAAe,GAAG,UAAU,EAAE,CAAC;YACjC,aAAa,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,MAAM,eAAe,GAAG,UAAU,GAAG,cAAc,CAAC;IACpD,MAAM,aAAa,GAAG,aAAa,GAAG,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,eAAe,IAAI,aAAa,CAAC;IAEpD,IAAI,cAAkC,CAAC;IAEvC,IAAI,eAAe,IAAI,aAAa,EAAE,CAAC;QACrC,cAAc;YACZ,2CAA2C,UAAU,iBAAiB;gBACtE,YAAY,aAAa,qBAAqB,aAAa,SAAS;gBACpE,kEAAkE,aAAa,QAAQ,CAAC;IAC5F,CAAC;SAAM,IAAI,eAAe,EAAE,CAAC;QAC3B,cAAc;YACZ,2CAA2C,UAAU,iBAAiB;gBACtE,kEAAkE,aAAa,QAAQ,CAAC;IAC5F,CAAC;SAAM,IAAI,aAAa,EAAE,CAAC;QACzB,cAAc;YACZ,2CAA2C,aAAa,SAAS;gBACjE,MAAM,aAAa,8BAA8B;gBACjD,qDAAqD,aAAa,QAAQ,CAAC;IAC/E,CAAC;IAED,OAAO;QACL,UAAU;QACV,aAAa;QACb,UAAU;QACV,cAAc;KACf,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation history tracking via git notes
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export type { ValidationRun, HistoryNote, RecordResult, StabilityCheck, HistoryConfig, HealthCheckResult, PruneResult, } from './types.js';
|
|
7
|
+
export { DEFAULT_HISTORY_CONFIG } from './types.js';
|
|
8
|
+
export { recordValidationHistory, checkWorktreeStability, } from './recorder.js';
|
|
9
|
+
export { readHistoryNote, listHistoryTreeHashes, getAllHistoryNotes, hasHistoryForTree, } from './reader.js';
|
|
10
|
+
export { pruneHistoryByAge, pruneAllHistory } from './pruner.js';
|
|
11
|
+
export { checkHistoryHealth } from './health-check.js';
|
|
12
|
+
export { truncateValidationOutput } from './truncate.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation history tracking via git notes
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { DEFAULT_HISTORY_CONFIG } from './types.js';
|
|
7
|
+
// Recorder
|
|
8
|
+
export { recordValidationHistory, checkWorktreeStability, } from './recorder.js';
|
|
9
|
+
// Reader
|
|
10
|
+
export { readHistoryNote, listHistoryTreeHashes, getAllHistoryNotes, hasHistoryForTree, } from './reader.js';
|
|
11
|
+
// Pruner
|
|
12
|
+
export { pruneHistoryByAge, pruneAllHistory } from './pruner.js';
|
|
13
|
+
// Health check
|
|
14
|
+
export { checkHistoryHealth } from './health-check.js';
|
|
15
|
+
// Truncate
|
|
16
|
+
export { truncateValidationOutput } from './truncate.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,WAAW;AACX,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,SAAS;AACT,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,SAAS;AACT,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEjE,eAAe;AACf,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,WAAW;AACX,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/pruner.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History pruning utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { PruneResult, HistoryConfig } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Prune validation history older than specified days
|
|
7
|
+
*
|
|
8
|
+
* @param olderThanDays - Remove notes older than this many days
|
|
9
|
+
* @param config - History configuration
|
|
10
|
+
* @param dryRun - If true, don't actually delete (default: false)
|
|
11
|
+
* @returns Prune result
|
|
12
|
+
*/
|
|
13
|
+
export declare function pruneHistoryByAge(olderThanDays: number, config?: HistoryConfig, dryRun?: boolean): Promise<PruneResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Prune all validation history
|
|
16
|
+
*
|
|
17
|
+
* @param config - History configuration
|
|
18
|
+
* @param dryRun - If true, don't actually delete (default: false)
|
|
19
|
+
* @returns Prune result
|
|
20
|
+
*/
|
|
21
|
+
export declare function pruneAllHistory(config?: HistoryConfig, dryRun?: boolean): Promise<PruneResult>;
|
|
22
|
+
//# sourceMappingURL=pruner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pruner.d.ts","sourceRoot":"","sources":["../src/pruner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAW7D;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,aAAkB,EAC1B,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,WAAW,CAAC,CAsDtB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,MAAM,GAAE,aAAkB,EAC1B,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,WAAW,CAAC,CAwCtB"}
|
package/dist/pruner.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History pruning utilities
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { DEFAULT_HISTORY_CONFIG } from './types.js';
|
|
6
|
+
import { getAllHistoryNotes } from './reader.js';
|
|
7
|
+
const GIT_TIMEOUT = 30000;
|
|
8
|
+
const GIT_OPTIONS = {
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
timeout: GIT_TIMEOUT,
|
|
11
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Prune validation history older than specified days
|
|
15
|
+
*
|
|
16
|
+
* @param olderThanDays - Remove notes older than this many days
|
|
17
|
+
* @param config - History configuration
|
|
18
|
+
* @param dryRun - If true, don't actually delete (default: false)
|
|
19
|
+
* @returns Prune result
|
|
20
|
+
*/
|
|
21
|
+
export async function pruneHistoryByAge(olderThanDays, config = {}, dryRun = false) {
|
|
22
|
+
const mergedConfig = {
|
|
23
|
+
...DEFAULT_HISTORY_CONFIG,
|
|
24
|
+
...config,
|
|
25
|
+
gitNotes: {
|
|
26
|
+
...DEFAULT_HISTORY_CONFIG.gitNotes,
|
|
27
|
+
...config.gitNotes,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const notesRef = mergedConfig.gitNotes.ref;
|
|
31
|
+
const cutoffTime = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
|
|
32
|
+
let notesPruned = 0;
|
|
33
|
+
let runsPruned = 0;
|
|
34
|
+
const prunedTreeHashes = [];
|
|
35
|
+
const allNotes = await getAllHistoryNotes(notesRef);
|
|
36
|
+
const notesRemaining = allNotes.length;
|
|
37
|
+
for (const note of allNotes) {
|
|
38
|
+
if (!note.runs || note.runs.length === 0) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Get oldest run timestamp
|
|
42
|
+
const oldestRun = note.runs[0];
|
|
43
|
+
const oldestTimestamp = new Date(oldestRun.timestamp).getTime();
|
|
44
|
+
if (oldestTimestamp < cutoffTime) {
|
|
45
|
+
// All runs in this note are old - delete entire note
|
|
46
|
+
if (!dryRun) {
|
|
47
|
+
try {
|
|
48
|
+
execSync(`git notes --ref=${notesRef} remove ${note.treeHash}`, { ...GIT_OPTIONS, stdio: 'ignore' });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Ignore errors (note might not exist)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
notesPruned++;
|
|
55
|
+
runsPruned += note.runs.length;
|
|
56
|
+
prunedTreeHashes.push(note.treeHash);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
notesPruned,
|
|
61
|
+
runsPruned,
|
|
62
|
+
notesRemaining: notesRemaining - notesPruned,
|
|
63
|
+
prunedTreeHashes,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Prune all validation history
|
|
68
|
+
*
|
|
69
|
+
* @param config - History configuration
|
|
70
|
+
* @param dryRun - If true, don't actually delete (default: false)
|
|
71
|
+
* @returns Prune result
|
|
72
|
+
*/
|
|
73
|
+
export async function pruneAllHistory(config = {}, dryRun = false) {
|
|
74
|
+
const mergedConfig = {
|
|
75
|
+
...DEFAULT_HISTORY_CONFIG,
|
|
76
|
+
...config,
|
|
77
|
+
gitNotes: {
|
|
78
|
+
...DEFAULT_HISTORY_CONFIG.gitNotes,
|
|
79
|
+
...config.gitNotes,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const notesRef = mergedConfig.gitNotes.ref;
|
|
83
|
+
const allNotes = await getAllHistoryNotes(notesRef);
|
|
84
|
+
let notesPruned = 0;
|
|
85
|
+
let runsPruned = 0;
|
|
86
|
+
const prunedTreeHashes = [];
|
|
87
|
+
for (const note of allNotes) {
|
|
88
|
+
if (!dryRun) {
|
|
89
|
+
try {
|
|
90
|
+
execSync(`git notes --ref=${notesRef} remove ${note.treeHash}`, { ...GIT_OPTIONS, stdio: 'ignore' });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Ignore errors
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
notesPruned++;
|
|
97
|
+
runsPruned += note.runs?.length || 0;
|
|
98
|
+
prunedTreeHashes.push(note.treeHash);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
notesPruned,
|
|
102
|
+
runsPruned,
|
|
103
|
+
notesRemaining: 0,
|
|
104
|
+
prunedTreeHashes,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=pruner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pruner.js","sourceRoot":"","sources":["../src/pruner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,WAAW,GAAG;IAClB,QAAQ,EAAE,MAAe;IACzB,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAA+B;CAChE,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,aAAqB,EACrB,SAAwB,EAAE,EAC1B,SAAkB,KAAK;IAEvB,MAAM,YAAY,GAAG;QACnB,GAAG,sBAAsB;QACzB,GAAG,MAAM;QACT,QAAQ,EAAE;YACR,GAAG,sBAAsB,CAAC,QAAQ;YAClC,GAAG,MAAM,CAAC,QAAQ;SACnB;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAEpE,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,gBAAgB,GAAa,EAAE,CAAC;IAEtC,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACpD,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEvC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,SAAS;QACX,CAAC;QAED,2BAA2B;QAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,eAAe,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;QAEhE,IAAI,eAAe,GAAG,UAAU,EAAE,CAAC;YACjC,qDAAqD;YACrD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACH,QAAQ,CACN,mBAAmB,QAAQ,WAAW,IAAI,CAAC,QAAQ,EAAE,EACrD,EAAE,GAAG,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpC,CAAC;gBACJ,CAAC;gBAAC,MAAM,CAAC;oBACP,uCAAuC;gBACzC,CAAC;YACH,CAAC;YAED,WAAW,EAAE,CAAC;YACd,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAC/B,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO;QACL,WAAW;QACX,UAAU;QACV,cAAc,EAAE,cAAc,GAAG,WAAW;QAC5C,gBAAgB;KACjB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAAwB,EAAE,EAC1B,SAAkB,KAAK;IAEvB,MAAM,YAAY,GAAG;QACnB,GAAG,sBAAsB;QACzB,GAAG,MAAM;QACT,QAAQ,EAAE;YACR,GAAG,sBAAsB,CAAC,QAAQ;YAClC,GAAG,MAAM,CAAC,QAAQ;SACnB;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC;IAE3C,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,gBAAgB,GAAa,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,QAAQ,CACN,mBAAmB,QAAQ,WAAW,IAAI,CAAC,QAAQ,EAAE,EACrD,EAAE,GAAG,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpC,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,gBAAgB;YAClB,CAAC;QACH,CAAC;QAED,WAAW,EAAE,CAAC;QACd,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;QACrC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,OAAO;QACL,WAAW;QACX,UAAU;QACV,cAAc,EAAE,CAAC;QACjB,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git notes reader
|
|
3
|
+
*/
|
|
4
|
+
import type { HistoryNote } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Read validation history note for a tree hash
|
|
7
|
+
*
|
|
8
|
+
* @param treeHash - Git tree hash
|
|
9
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
10
|
+
* @returns History note or null if not found
|
|
11
|
+
*/
|
|
12
|
+
export declare function readHistoryNote(treeHash: string, notesRef?: string): Promise<HistoryNote | null>;
|
|
13
|
+
/**
|
|
14
|
+
* List all tree hashes with validation history
|
|
15
|
+
*
|
|
16
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
17
|
+
* @returns Array of tree hashes with notes
|
|
18
|
+
*/
|
|
19
|
+
export declare function listHistoryTreeHashes(notesRef?: string): Promise<string[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Get all validation history notes
|
|
22
|
+
*
|
|
23
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
24
|
+
* @returns Array of all history notes
|
|
25
|
+
*/
|
|
26
|
+
export declare function getAllHistoryNotes(notesRef?: string): Promise<HistoryNote[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Check if validation history exists for a tree hash
|
|
29
|
+
*
|
|
30
|
+
* @param treeHash - Git tree hash
|
|
31
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
32
|
+
* @returns True if history exists
|
|
33
|
+
*/
|
|
34
|
+
export declare function hasHistoryForTree(treeHash: string, notesRef?: string): Promise<boolean>;
|
|
35
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAA6B,GACtC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAa7B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,GAAE,MAA6B,GACtC,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBnB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,GAAE,MAA6B,GACtC,OAAO,CAAC,WAAW,EAAE,CAAC,CAYxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAA6B,GACtC,OAAO,CAAC,OAAO,CAAC,CAGlB"}
|
package/dist/reader.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git notes reader
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { parse as parseYaml } from 'yaml';
|
|
6
|
+
const GIT_TIMEOUT = 30000;
|
|
7
|
+
const GIT_OPTIONS = {
|
|
8
|
+
encoding: 'utf8',
|
|
9
|
+
timeout: GIT_TIMEOUT,
|
|
10
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Read validation history note for a tree hash
|
|
14
|
+
*
|
|
15
|
+
* @param treeHash - Git tree hash
|
|
16
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
17
|
+
* @returns History note or null if not found
|
|
18
|
+
*/
|
|
19
|
+
export async function readHistoryNote(treeHash, notesRef = 'vibe-validate/runs') {
|
|
20
|
+
try {
|
|
21
|
+
const yaml = execSync(`git notes --ref=${notesRef} show ${treeHash}`, GIT_OPTIONS);
|
|
22
|
+
const note = parseYaml(yaml);
|
|
23
|
+
return note;
|
|
24
|
+
}
|
|
25
|
+
catch (_error) {
|
|
26
|
+
// Note doesn't exist - this is fine
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* List all tree hashes with validation history
|
|
32
|
+
*
|
|
33
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
34
|
+
* @returns Array of tree hashes with notes
|
|
35
|
+
*/
|
|
36
|
+
export async function listHistoryTreeHashes(notesRef = 'vibe-validate/runs') {
|
|
37
|
+
try {
|
|
38
|
+
const output = execSync(`git notes --ref=${notesRef} list`, GIT_OPTIONS);
|
|
39
|
+
if (!output.trim()) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
// Output format: "<note-sha> <tree-hash>"
|
|
43
|
+
const treeHashes = output
|
|
44
|
+
.trim()
|
|
45
|
+
.split('\n')
|
|
46
|
+
.map((line) => {
|
|
47
|
+
const parts = line.split(' ');
|
|
48
|
+
return parts[1]; // tree hash
|
|
49
|
+
})
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
return treeHashes;
|
|
52
|
+
}
|
|
53
|
+
catch (_error) {
|
|
54
|
+
// No notes exist yet
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get all validation history notes
|
|
60
|
+
*
|
|
61
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
62
|
+
* @returns Array of all history notes
|
|
63
|
+
*/
|
|
64
|
+
export async function getAllHistoryNotes(notesRef = 'vibe-validate/runs') {
|
|
65
|
+
const treeHashes = await listHistoryTreeHashes(notesRef);
|
|
66
|
+
const notes = [];
|
|
67
|
+
for (const treeHash of treeHashes) {
|
|
68
|
+
const note = await readHistoryNote(treeHash, notesRef);
|
|
69
|
+
if (note) {
|
|
70
|
+
notes.push(note);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return notes;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if validation history exists for a tree hash
|
|
77
|
+
*
|
|
78
|
+
* @param treeHash - Git tree hash
|
|
79
|
+
* @param notesRef - Git notes ref (default: vibe-validate/runs)
|
|
80
|
+
* @returns True if history exists
|
|
81
|
+
*/
|
|
82
|
+
export async function hasHistoryForTree(treeHash, notesRef = 'vibe-validate/runs') {
|
|
83
|
+
const note = await readHistoryNote(treeHash, notesRef);
|
|
84
|
+
return note !== null;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAG1C,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,WAAW,GAAG;IAClB,QAAQ,EAAE,MAAe;IACzB,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAA+B;CAChE,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,WAAmB,oBAAoB;IAEvC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CACnB,mBAAmB,QAAQ,SAAS,QAAQ,EAAE,EAC9C,WAAW,CACZ,CAAC;QAEF,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAgB,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,oCAAoC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,WAAmB,oBAAoB;IAEvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,mBAAmB,QAAQ,OAAO,EAAE,WAAW,CAAC,CAAC;QAEzE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,0CAA0C;QAC1C,MAAM,UAAU,GAAG,MAAM;aACtB,IAAI,EAAE;aACN,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC9B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY;QAC/B,CAAC,CAAC;aACD,MAAM,CAAC,OAAO,CAAC,CAAC;QAEnB,OAAO,UAAU,CAAC;IACpB,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,qBAAqB;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB,oBAAoB;IAEvC,MAAM,UAAU,GAAG,MAAM,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,KAAK,GAAkB,EAAE,CAAC;IAEhC,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACvD,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,WAAmB,oBAAoB;IAEvC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACvD,OAAO,IAAI,KAAK,IAAI,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git notes recorder
|
|
3
|
+
*/
|
|
4
|
+
import type { ValidationResult } from '@vibe-validate/core';
|
|
5
|
+
import type { RecordResult, StabilityCheck, HistoryConfig } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Record validation result to git notes
|
|
8
|
+
*
|
|
9
|
+
* @param treeHash - Git tree hash
|
|
10
|
+
* @param result - Validation result
|
|
11
|
+
* @param config - History configuration
|
|
12
|
+
* @returns Record result
|
|
13
|
+
*/
|
|
14
|
+
export declare function recordValidationHistory(treeHash: string, result: ValidationResult, config?: HistoryConfig): Promise<RecordResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Check worktree stability (compare tree hash before and after)
|
|
17
|
+
*
|
|
18
|
+
* @param treeHashBefore - Tree hash before validation
|
|
19
|
+
* @returns Stability check result
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkWorktreeStability(treeHashBefore: string): Promise<StabilityCheck>;
|
|
22
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAGV,YAAY,EACZ,cAAc,EACd,aAAa,EACd,MAAM,YAAY,CAAC;AAuCpB;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,EACxB,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,YAAY,CAAC,CA8FvB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CAQzB"}
|
package/dist/recorder.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git notes recorder
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { writeFileSync, unlinkSync } from 'fs';
|
|
6
|
+
import { tmpdir } from 'os';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
9
|
+
import { getGitTreeHash, hasWorkingTreeChanges } from '@vibe-validate/git';
|
|
10
|
+
import { DEFAULT_HISTORY_CONFIG } from './types.js';
|
|
11
|
+
import { truncateValidationOutput } from './truncate.js';
|
|
12
|
+
import { readHistoryNote } from './reader.js';
|
|
13
|
+
const GIT_TIMEOUT = 30000;
|
|
14
|
+
const GIT_OPTIONS = {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
timeout: GIT_TIMEOUT,
|
|
17
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Get current branch name
|
|
21
|
+
*
|
|
22
|
+
* @returns Branch name or 'detached' if in detached HEAD state
|
|
23
|
+
*/
|
|
24
|
+
async function getCurrentBranch() {
|
|
25
|
+
try {
|
|
26
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', GIT_OPTIONS).trim();
|
|
27
|
+
return branch === 'HEAD' ? 'detached' : branch;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 'unknown';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get HEAD commit SHA
|
|
35
|
+
*
|
|
36
|
+
* @returns Commit SHA or 'none' if no commits
|
|
37
|
+
*/
|
|
38
|
+
async function getHeadCommit() {
|
|
39
|
+
try {
|
|
40
|
+
return execSync('git rev-parse HEAD', GIT_OPTIONS).trim();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return 'none';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Record validation result to git notes
|
|
48
|
+
*
|
|
49
|
+
* @param treeHash - Git tree hash
|
|
50
|
+
* @param result - Validation result
|
|
51
|
+
* @param config - History configuration
|
|
52
|
+
* @returns Record result
|
|
53
|
+
*/
|
|
54
|
+
export async function recordValidationHistory(treeHash, result, config = {}) {
|
|
55
|
+
const mergedConfig = {
|
|
56
|
+
...DEFAULT_HISTORY_CONFIG,
|
|
57
|
+
...config,
|
|
58
|
+
gitNotes: {
|
|
59
|
+
...DEFAULT_HISTORY_CONFIG.gitNotes,
|
|
60
|
+
...config.gitNotes,
|
|
61
|
+
},
|
|
62
|
+
retention: {
|
|
63
|
+
...DEFAULT_HISTORY_CONFIG.retention,
|
|
64
|
+
...config.retention,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
// Type assertions safe: DEFAULT_HISTORY_CONFIG is Required<HistoryConfig>
|
|
68
|
+
const notesRef = (mergedConfig.gitNotes.ref ?? DEFAULT_HISTORY_CONFIG.gitNotes.ref);
|
|
69
|
+
const maxRunsPerTree = (mergedConfig.gitNotes.maxRunsPerTree ?? DEFAULT_HISTORY_CONFIG.gitNotes.maxRunsPerTree);
|
|
70
|
+
const maxOutputBytes = (mergedConfig.gitNotes.maxOutputBytes ?? DEFAULT_HISTORY_CONFIG.gitNotes.maxOutputBytes);
|
|
71
|
+
try {
|
|
72
|
+
// 1. Read existing note (if any)
|
|
73
|
+
const existingNote = await readHistoryNote(treeHash, notesRef);
|
|
74
|
+
// 2. Create new run entry
|
|
75
|
+
const newRun = {
|
|
76
|
+
id: `run-${Date.now()}`,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
duration: result.passed ? 0 : 0, // Will be calculated from result if available
|
|
79
|
+
passed: result.passed,
|
|
80
|
+
branch: await getCurrentBranch(),
|
|
81
|
+
headCommit: await getHeadCommit(),
|
|
82
|
+
uncommittedChanges: await hasWorkingTreeChanges(),
|
|
83
|
+
result: truncateValidationOutput(result, maxOutputBytes),
|
|
84
|
+
};
|
|
85
|
+
// Calculate duration from result phases if available (convert to milliseconds)
|
|
86
|
+
if (result.phases && result.phases.length > 0) {
|
|
87
|
+
newRun.duration = result.phases.reduce((total, phase) => total + (phase.durationSecs || 0) * 1000, 0);
|
|
88
|
+
}
|
|
89
|
+
// 3. Append or create
|
|
90
|
+
let note;
|
|
91
|
+
if (existingNote) {
|
|
92
|
+
note = {
|
|
93
|
+
...existingNote,
|
|
94
|
+
runs: [...existingNote.runs, newRun],
|
|
95
|
+
};
|
|
96
|
+
// Prune: keep last N runs
|
|
97
|
+
if (note.runs.length > maxRunsPerTree) {
|
|
98
|
+
note.runs = note.runs.slice(-maxRunsPerTree);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
note = {
|
|
103
|
+
treeHash,
|
|
104
|
+
runs: [newRun],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// 4. Write note to temp file (use cross-platform temp directory)
|
|
108
|
+
const tempFile = join(tmpdir(), `note.vibe-validate.${treeHash.slice(0, 12)}.${process.pid}.yaml`);
|
|
109
|
+
try {
|
|
110
|
+
writeFileSync(tempFile, stringifyYaml(note), 'utf8');
|
|
111
|
+
// 5. Add note to git (force overwrite)
|
|
112
|
+
execSync(`git notes --ref=${notesRef} add -f -F "${tempFile}" ${treeHash}`, { ...GIT_OPTIONS, stdio: 'ignore' });
|
|
113
|
+
return {
|
|
114
|
+
recorded: true,
|
|
115
|
+
treeHash,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
// Cleanup temp file
|
|
120
|
+
try {
|
|
121
|
+
unlinkSync(tempFile);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Ignore cleanup errors
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
130
|
+
return {
|
|
131
|
+
recorded: false,
|
|
132
|
+
reason: errorMessage,
|
|
133
|
+
treeHash,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check worktree stability (compare tree hash before and after)
|
|
139
|
+
*
|
|
140
|
+
* @param treeHashBefore - Tree hash before validation
|
|
141
|
+
* @returns Stability check result
|
|
142
|
+
*/
|
|
143
|
+
export async function checkWorktreeStability(treeHashBefore) {
|
|
144
|
+
const treeHashAfter = await getGitTreeHash();
|
|
145
|
+
return {
|
|
146
|
+
stable: treeHashBefore === treeHashAfter,
|
|
147
|
+
treeHashBefore,
|
|
148
|
+
treeHashAfter,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output truncation utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { ValidationResult } from '@vibe-validate/core';
|
|
5
|
+
/**
|
|
6
|
+
* Truncate validation result output to max bytes
|
|
7
|
+
*
|
|
8
|
+
* @param result - Validation result to truncate
|
|
9
|
+
* @param maxBytes - Maximum bytes per step output (default: 10000)
|
|
10
|
+
* @returns Truncated validation result
|
|
11
|
+
*/
|
|
12
|
+
export declare function truncateValidationOutput(result: ValidationResult, maxBytes?: number): ValidationResult;
|
|
13
|
+
//# sourceMappingURL=truncate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,GAAE,MAAc,GACvB,gBAAgB,CAgClB"}
|
package/dist/truncate.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output truncation utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Truncate validation result output to max bytes
|
|
6
|
+
*
|
|
7
|
+
* @param result - Validation result to truncate
|
|
8
|
+
* @param maxBytes - Maximum bytes per step output (default: 10000)
|
|
9
|
+
* @returns Truncated validation result
|
|
10
|
+
*/
|
|
11
|
+
export function truncateValidationOutput(result, maxBytes = 10000) {
|
|
12
|
+
// Deep clone to avoid mutating original
|
|
13
|
+
const truncated = JSON.parse(JSON.stringify(result));
|
|
14
|
+
// Truncate phase outputs
|
|
15
|
+
if (truncated.phases) {
|
|
16
|
+
for (const phase of truncated.phases) {
|
|
17
|
+
if (phase.steps) {
|
|
18
|
+
for (const step of phase.steps) {
|
|
19
|
+
if (step.output && step.output.length > maxBytes) {
|
|
20
|
+
const originalLength = step.output.length;
|
|
21
|
+
step.output =
|
|
22
|
+
step.output.slice(0, maxBytes) +
|
|
23
|
+
`\n\n[... truncated ${originalLength - maxBytes} bytes]`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Truncate failed step output
|
|
30
|
+
if (truncated.failedStepOutput &&
|
|
31
|
+
truncated.failedStepOutput.length > maxBytes) {
|
|
32
|
+
const originalLength = truncated.failedStepOutput.length;
|
|
33
|
+
truncated.failedStepOutput =
|
|
34
|
+
truncated.failedStepOutput.slice(0, maxBytes) +
|
|
35
|
+
`\n\n[... truncated ${originalLength - maxBytes} bytes]`;
|
|
36
|
+
}
|
|
37
|
+
return truncated;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=truncate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAwB,EACxB,WAAmB,KAAK;IAExB,wCAAwC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAqB,CAAC;IAEzE,yBAAyB;IACzB,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;YACrC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAChB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;oBAC/B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;wBACjD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;wBAC1C,IAAI,CAAC,MAAM;4BACT,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;gCAC9B,sBAAsB,cAAc,GAAG,QAAQ,SAAS,CAAC;oBAC7D,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,IACE,SAAS,CAAC,gBAAgB;QAC1B,SAAS,CAAC,gBAAgB,CAAC,MAAM,GAAG,QAAQ,EAC5C,CAAC;QACD,MAAM,cAAc,GAAG,SAAS,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACzD,SAAS,CAAC,gBAAgB;YACxB,SAAS,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;gBAC7C,sBAAsB,cAAc,GAAG,QAAQ,SAAS,CAAC;IAC7D,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation history types
|
|
3
|
+
*/
|
|
4
|
+
import type { ValidationResult } from '@vibe-validate/core';
|
|
5
|
+
/**
|
|
6
|
+
* Single validation run entry
|
|
7
|
+
*/
|
|
8
|
+
export interface ValidationRun {
|
|
9
|
+
/** Unique run ID (run-{timestamp}) */
|
|
10
|
+
id: string;
|
|
11
|
+
/** ISO 8601 timestamp */
|
|
12
|
+
timestamp: string;
|
|
13
|
+
/** Duration in milliseconds */
|
|
14
|
+
duration: number;
|
|
15
|
+
/** Did validation pass? */
|
|
16
|
+
passed: boolean;
|
|
17
|
+
/** Branch name at time of validation */
|
|
18
|
+
branch: string;
|
|
19
|
+
/** HEAD commit SHA at time of validation */
|
|
20
|
+
headCommit: string;
|
|
21
|
+
/** Were there uncommitted changes? */
|
|
22
|
+
uncommittedChanges: boolean;
|
|
23
|
+
/** Full validation result (with truncated output) */
|
|
24
|
+
result: ValidationResult;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Git note structure (stored as YAML)
|
|
28
|
+
*/
|
|
29
|
+
export interface HistoryNote {
|
|
30
|
+
/** Tree hash this note is attached to */
|
|
31
|
+
treeHash: string;
|
|
32
|
+
/** Array of validation runs for this tree */
|
|
33
|
+
runs: ValidationRun[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Result of recording validation history
|
|
37
|
+
*/
|
|
38
|
+
export interface RecordResult {
|
|
39
|
+
/** Was history successfully recorded? */
|
|
40
|
+
recorded: boolean;
|
|
41
|
+
/** Reason if not recorded */
|
|
42
|
+
reason?: string;
|
|
43
|
+
/** Tree hash that was recorded (or attempted) */
|
|
44
|
+
treeHash: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Worktree stability check result
|
|
48
|
+
*/
|
|
49
|
+
export interface StabilityCheck {
|
|
50
|
+
/** Is worktree stable? */
|
|
51
|
+
stable: boolean;
|
|
52
|
+
/** Tree hash before validation */
|
|
53
|
+
treeHashBefore: string;
|
|
54
|
+
/** Tree hash after validation */
|
|
55
|
+
treeHashAfter: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* History configuration
|
|
59
|
+
*/
|
|
60
|
+
export interface HistoryConfig {
|
|
61
|
+
/** Enable history recording */
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
/** Git notes configuration */
|
|
64
|
+
gitNotes?: {
|
|
65
|
+
/** Git ref namespace */
|
|
66
|
+
ref?: string;
|
|
67
|
+
/** Max runs to keep per tree */
|
|
68
|
+
maxRunsPerTree?: number;
|
|
69
|
+
/** Truncate output to max bytes */
|
|
70
|
+
maxOutputBytes?: number;
|
|
71
|
+
};
|
|
72
|
+
/** Retention policy */
|
|
73
|
+
retention?: {
|
|
74
|
+
/** Warn after this many days */
|
|
75
|
+
warnAfterDays?: number;
|
|
76
|
+
/** Warn after this many total notes */
|
|
77
|
+
warnAfterCount?: number;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Default history configuration
|
|
82
|
+
*/
|
|
83
|
+
export declare const DEFAULT_HISTORY_CONFIG: Required<HistoryConfig>;
|
|
84
|
+
/**
|
|
85
|
+
* Health check result
|
|
86
|
+
*/
|
|
87
|
+
export interface HealthCheckResult {
|
|
88
|
+
/** Total number of tree hashes with notes */
|
|
89
|
+
totalNotes: number;
|
|
90
|
+
/** Number of notes older than retention policy */
|
|
91
|
+
oldNotesCount: number;
|
|
92
|
+
/** Should warn user about cleanup? */
|
|
93
|
+
shouldWarn: boolean;
|
|
94
|
+
/** Warning message (if any) */
|
|
95
|
+
warningMessage?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Prune result
|
|
99
|
+
*/
|
|
100
|
+
export interface PruneResult {
|
|
101
|
+
/** Number of notes pruned */
|
|
102
|
+
notesPruned: number;
|
|
103
|
+
/** Number of runs pruned (across all notes) */
|
|
104
|
+
runsPruned: number;
|
|
105
|
+
/** Number of notes remaining */
|
|
106
|
+
notesRemaining: number;
|
|
107
|
+
/** Tree hashes that were pruned */
|
|
108
|
+
prunedTreeHashes: string[];
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IAEX,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAElB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IAEjB,2BAA2B;IAC3B,MAAM,EAAE,OAAO,CAAC;IAEhB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IAEf,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IAEnB,sCAAsC;IACtC,kBAAkB,EAAE,OAAO,CAAC;IAE5B,qDAAqD;IACrD,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IAEjB,6CAA6C;IAC7C,IAAI,EAAE,aAAa,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAElB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,MAAM,EAAE,OAAO,CAAC;IAEhB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAC;IAEvB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE;QACT,wBAAwB;QACxB,GAAG,CAAC,EAAE,MAAM,CAAC;QAEb,gCAAgC;QAChC,cAAc,CAAC,EAAE,MAAM,CAAC;QAExB,mCAAmC;QACnC,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAEF,uBAAuB;IACvB,SAAS,CAAC,EAAE;QACV,gCAAgC;QAChC,aAAa,CAAC,EAAE,MAAM,CAAC;QAEvB,uCAAuC;QACvC,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,aAAa,CAW1D,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IAEnB,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAC;IAEtB,sCAAsC;IACtC,UAAU,EAAE,OAAO,CAAC;IAEpB,+BAA+B;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IAEpB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IAEnB,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IAEvB,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation history types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Default history configuration
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_HISTORY_CONFIG = {
|
|
8
|
+
enabled: true,
|
|
9
|
+
gitNotes: {
|
|
10
|
+
ref: 'vibe-validate/runs',
|
|
11
|
+
maxRunsPerTree: 10,
|
|
12
|
+
maxOutputBytes: 10000,
|
|
13
|
+
},
|
|
14
|
+
retention: {
|
|
15
|
+
warnAfterDays: 90,
|
|
16
|
+
warnAfterCount: 100,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAqGH;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAA4B;IAC7D,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE;QACR,GAAG,EAAE,oBAAoB;QACzB,cAAc,EAAE,EAAE;QAClB,cAAc,EAAE,KAAK;KACtB;IACD,SAAS,EAAE;QACT,aAAa,EAAE,EAAE;QACjB,cAAc,EAAE,GAAG;KACpB;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibe-validate/history",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Validation history tracking via git notes for vibe-validate",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"validation",
|
|
27
|
+
"history",
|
|
28
|
+
"git-notes",
|
|
29
|
+
"caching",
|
|
30
|
+
"sdlc"
|
|
31
|
+
],
|
|
32
|
+
"author": "Jeff Dutton",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/jdutton/vibe-validate.git",
|
|
37
|
+
"directory": "packages/history"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.0.0"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@vibe-validate/git": "workspace:*",
|
|
47
|
+
"@vibe-validate/core": "workspace:*",
|
|
48
|
+
"yaml": "^2.3.4"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"typescript": "^5.6.0",
|
|
53
|
+
"vitest": "^2.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|