@redaksjon/protokoll 0.0.11 ā 0.0.13
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/.cursor/rules/definition-of-done.md +1 -0
- package/.cursor/rules/no-emoticons.md +26 -12
- package/README.md +483 -69
- package/dist/agentic/executor.js +473 -41
- package/dist/agentic/executor.js.map +1 -1
- package/dist/agentic/index.js.map +1 -1
- package/dist/agentic/tools/lookup-person.js +123 -4
- package/dist/agentic/tools/lookup-person.js.map +1 -1
- package/dist/agentic/tools/lookup-project.js +139 -22
- package/dist/agentic/tools/lookup-project.js.map +1 -1
- package/dist/agentic/tools/route-note.js +5 -1
- package/dist/agentic/tools/route-note.js.map +1 -1
- package/dist/arguments.js +6 -3
- package/dist/arguments.js.map +1 -1
- package/dist/cli/action.js +704 -0
- package/dist/cli/action.js.map +1 -0
- package/dist/cli/config.js +482 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/context.js +466 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/feedback.js +858 -0
- package/dist/cli/feedback.js.map +1 -0
- package/dist/cli/index.js +103 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.js +572 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/transcript.js +199 -0
- package/dist/cli/transcript.js.map +1 -0
- package/dist/constants.js +12 -5
- package/dist/constants.js.map +1 -1
- package/dist/context/discovery.js +1 -1
- package/dist/context/discovery.js.map +1 -1
- package/dist/context/index.js +25 -1
- package/dist/context/index.js.map +1 -1
- package/dist/context/storage.js +57 -4
- package/dist/context/storage.js.map +1 -1
- package/dist/interactive/handler.js +310 -9
- package/dist/interactive/handler.js.map +1 -1
- package/dist/main.js +11 -1
- package/dist/main.js.map +1 -1
- package/dist/output/index.js.map +1 -1
- package/dist/output/manager.js +47 -2
- package/dist/output/manager.js.map +1 -1
- package/dist/phases/complete.js +38 -3
- package/dist/phases/complete.js.map +1 -1
- package/dist/phases/locate.js +1 -1
- package/dist/phases/locate.js.map +1 -1
- package/dist/pipeline/orchestrator.js +104 -31
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/protokoll.js +68 -2
- package/dist/protokoll.js.map +1 -1
- package/dist/reasoning/client.js +83 -0
- package/dist/reasoning/client.js.map +1 -1
- package/dist/reasoning/index.js +1 -0
- package/dist/reasoning/index.js.map +1 -1
- package/dist/routing/router.js +2 -2
- package/dist/routing/router.js.map +1 -1
- package/dist/util/media.js +1 -1
- package/dist/util/media.js.map +1 -1
- package/dist/util/metadata.js.map +1 -1
- package/dist/util/sound.js +116 -0
- package/dist/util/sound.js.map +1 -0
- package/dist/util/storage.js +3 -3
- package/dist/util/storage.js.map +1 -1
- package/docs/duplicate-question-prevention.md +117 -0
- package/docs/examples.md +152 -0
- package/docs/interactive-context-example.md +92 -0
- package/docs/package-lock.json +6 -0
- package/docs/package.json +3 -1
- package/eslint.config.mjs +1 -1
- package/guide/action.md +375 -0
- package/guide/config.md +207 -0
- package/guide/configuration.md +82 -67
- package/guide/context-commands.md +574 -0
- package/guide/context-system.md +20 -7
- package/guide/development.md +106 -4
- package/guide/feedback.md +335 -0
- package/guide/index.md +100 -4
- package/guide/interactive.md +15 -14
- package/guide/quickstart.md +21 -7
- package/guide/reasoning.md +18 -4
- package/guide/routing.md +192 -97
- package/package.json +2 -3
- package/scripts/copy-assets.mjs +47 -0
- package/scripts/coverage-priority.mjs +323 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +6 -13
- package/vitest.config.ts +5 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console, no-restricted-imports */
|
|
3
|
+
/**
|
|
4
|
+
* Coverage Priority Analyzer
|
|
5
|
+
*
|
|
6
|
+
* Parses lcov.info and ranks files by testing priority.
|
|
7
|
+
* Helps answer: "Where should I focus testing efforts next?"
|
|
8
|
+
*
|
|
9
|
+
* Usage: node scripts/coverage-priority.mjs [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --weights=branches,functions,lines Custom weights (default: 0.5,0.3,0.2)
|
|
13
|
+
* --min-lines=N Exclude files with fewer than N lines (default: 10)
|
|
14
|
+
* --json Output as JSON
|
|
15
|
+
* --top=N Show only top N files (default: all)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
import { resolve, dirname } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const LCOV_PATH = resolve(__dirname, '../coverage/lcov.info');
|
|
24
|
+
|
|
25
|
+
// Parse command line arguments
|
|
26
|
+
function parseArgs() {
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const options = {
|
|
29
|
+
weights: { branches: 0.5, functions: 0.3, lines: 0.2 },
|
|
30
|
+
minLines: 10,
|
|
31
|
+
json: false,
|
|
32
|
+
top: null,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (const arg of args) {
|
|
36
|
+
if (arg.startsWith('--weights=')) {
|
|
37
|
+
const [b, f, l] = arg.slice(10).split(',').map(Number);
|
|
38
|
+
options.weights = { branches: b, functions: f, lines: l };
|
|
39
|
+
} else if (arg.startsWith('--min-lines=')) {
|
|
40
|
+
options.minLines = parseInt(arg.slice(12), 10);
|
|
41
|
+
} else if (arg === '--json') {
|
|
42
|
+
options.json = true;
|
|
43
|
+
} else if (arg.startsWith('--top=')) {
|
|
44
|
+
options.top = parseInt(arg.slice(6), 10);
|
|
45
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
46
|
+
console.log(`
|
|
47
|
+
Coverage Priority Analyzer
|
|
48
|
+
|
|
49
|
+
Parses lcov.info and ranks files by testing priority.
|
|
50
|
+
Helps answer: "Where should I focus testing efforts next?"
|
|
51
|
+
|
|
52
|
+
Usage: node scripts/coverage-priority.mjs [options]
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
--weights=B,F,L Custom weights for branches,functions,lines (default: 0.5,0.3,0.2)
|
|
56
|
+
--min-lines=N Exclude files with fewer than N lines (default: 10)
|
|
57
|
+
--json Output as JSON for tooling
|
|
58
|
+
--top=N Show only top N priority files
|
|
59
|
+
--help, -h Show this help
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
node scripts/coverage-priority.mjs --top=10
|
|
63
|
+
node scripts/coverage-priority.mjs --weights=0.6,0.2,0.2 --json
|
|
64
|
+
node scripts/coverage-priority.mjs --min-lines=50
|
|
65
|
+
`);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return options;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parse lcov.info file
|
|
73
|
+
function parseLcov(content) {
|
|
74
|
+
const files = [];
|
|
75
|
+
let current = null;
|
|
76
|
+
|
|
77
|
+
for (const line of content.split('\n')) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
|
|
80
|
+
if (trimmed.startsWith('SF:')) {
|
|
81
|
+
current = {
|
|
82
|
+
file: trimmed.slice(3),
|
|
83
|
+
linesFound: 0,
|
|
84
|
+
linesHit: 0,
|
|
85
|
+
functionsFound: 0,
|
|
86
|
+
functionsHit: 0,
|
|
87
|
+
branchesFound: 0,
|
|
88
|
+
branchesHit: 0,
|
|
89
|
+
};
|
|
90
|
+
} else if (trimmed.startsWith('LF:')) {
|
|
91
|
+
current.linesFound = parseInt(trimmed.slice(3), 10);
|
|
92
|
+
} else if (trimmed.startsWith('LH:')) {
|
|
93
|
+
current.linesHit = parseInt(trimmed.slice(3), 10);
|
|
94
|
+
} else if (trimmed.startsWith('FNF:')) {
|
|
95
|
+
current.functionsFound = parseInt(trimmed.slice(4), 10);
|
|
96
|
+
} else if (trimmed.startsWith('FNH:')) {
|
|
97
|
+
current.functionsHit = parseInt(trimmed.slice(4), 10);
|
|
98
|
+
} else if (trimmed.startsWith('BRF:')) {
|
|
99
|
+
current.branchesFound = parseInt(trimmed.slice(4), 10);
|
|
100
|
+
} else if (trimmed.startsWith('BRH:')) {
|
|
101
|
+
current.branchesHit = parseInt(trimmed.slice(4), 10);
|
|
102
|
+
} else if (trimmed === 'end_of_record' && current) {
|
|
103
|
+
files.push(current);
|
|
104
|
+
current = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return files;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Calculate overall coverage from all files
|
|
112
|
+
function calculateOverallCoverage(files) {
|
|
113
|
+
const totals = files.reduce((acc, f) => ({
|
|
114
|
+
linesFound: acc.linesFound + f.linesFound,
|
|
115
|
+
linesHit: acc.linesHit + f.linesHit,
|
|
116
|
+
functionsFound: acc.functionsFound + f.functionsFound,
|
|
117
|
+
functionsHit: acc.functionsHit + f.functionsHit,
|
|
118
|
+
branchesFound: acc.branchesFound + f.branchesFound,
|
|
119
|
+
branchesHit: acc.branchesHit + f.branchesHit,
|
|
120
|
+
}), {
|
|
121
|
+
linesFound: 0,
|
|
122
|
+
linesHit: 0,
|
|
123
|
+
functionsFound: 0,
|
|
124
|
+
functionsHit: 0,
|
|
125
|
+
branchesFound: 0,
|
|
126
|
+
branchesHit: 0,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
lines: {
|
|
131
|
+
found: totals.linesFound,
|
|
132
|
+
hit: totals.linesHit,
|
|
133
|
+
coverage: totals.linesFound > 0
|
|
134
|
+
? Math.round((totals.linesHit / totals.linesFound) * 10000) / 100
|
|
135
|
+
: 100,
|
|
136
|
+
},
|
|
137
|
+
functions: {
|
|
138
|
+
found: totals.functionsFound,
|
|
139
|
+
hit: totals.functionsHit,
|
|
140
|
+
coverage: totals.functionsFound > 0
|
|
141
|
+
? Math.round((totals.functionsHit / totals.functionsFound) * 10000) / 100
|
|
142
|
+
: 100,
|
|
143
|
+
},
|
|
144
|
+
branches: {
|
|
145
|
+
found: totals.branchesFound,
|
|
146
|
+
hit: totals.branchesHit,
|
|
147
|
+
coverage: totals.branchesFound > 0
|
|
148
|
+
? Math.round((totals.branchesHit / totals.branchesFound) * 10000) / 100
|
|
149
|
+
: 100,
|
|
150
|
+
},
|
|
151
|
+
fileCount: files.length,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Calculate coverage percentages and priority score
|
|
156
|
+
function analyzeFile(file, weights) {
|
|
157
|
+
const lineCoverage = file.linesFound > 0
|
|
158
|
+
? (file.linesHit / file.linesFound) * 100
|
|
159
|
+
: 100;
|
|
160
|
+
|
|
161
|
+
const functionCoverage = file.functionsFound > 0
|
|
162
|
+
? (file.functionsHit / file.functionsFound) * 100
|
|
163
|
+
: 100;
|
|
164
|
+
|
|
165
|
+
const branchCoverage = file.branchesFound > 0
|
|
166
|
+
? (file.branchesHit / file.branchesFound) * 100
|
|
167
|
+
: 100;
|
|
168
|
+
|
|
169
|
+
// Priority score: lower coverage = higher priority (inverted)
|
|
170
|
+
// Weighted combination of coverage gaps
|
|
171
|
+
const lineGap = 100 - lineCoverage;
|
|
172
|
+
const functionGap = 100 - functionCoverage;
|
|
173
|
+
const branchGap = 100 - branchCoverage;
|
|
174
|
+
|
|
175
|
+
// Factor in file size - bigger files with low coverage = more important
|
|
176
|
+
const sizeFactor = Math.log10(Math.max(file.linesFound, 1) + 1);
|
|
177
|
+
|
|
178
|
+
const priorityScore = (
|
|
179
|
+
(branchGap * weights.branches) +
|
|
180
|
+
(functionGap * weights.functions) +
|
|
181
|
+
(lineGap * weights.lines)
|
|
182
|
+
) * sizeFactor;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
file: file.file,
|
|
186
|
+
lines: {
|
|
187
|
+
found: file.linesFound,
|
|
188
|
+
hit: file.linesHit,
|
|
189
|
+
coverage: Math.round(lineCoverage * 100) / 100,
|
|
190
|
+
},
|
|
191
|
+
functions: {
|
|
192
|
+
found: file.functionsFound,
|
|
193
|
+
hit: file.functionsHit,
|
|
194
|
+
coverage: Math.round(functionCoverage * 100) / 100,
|
|
195
|
+
},
|
|
196
|
+
branches: {
|
|
197
|
+
found: file.branchesFound,
|
|
198
|
+
hit: file.branchesHit,
|
|
199
|
+
coverage: Math.round(branchCoverage * 100) / 100,
|
|
200
|
+
},
|
|
201
|
+
priorityScore: Math.round(priorityScore * 100) / 100,
|
|
202
|
+
uncoveredLines: file.linesFound - file.linesHit,
|
|
203
|
+
uncoveredBranches: file.branchesFound - file.branchesHit,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Format for terminal output
|
|
208
|
+
function formatTable(analyzed, options, overall) {
|
|
209
|
+
const divider = 'ā'.repeat(120);
|
|
210
|
+
|
|
211
|
+
// Color helper
|
|
212
|
+
const colorPct = (pct) => {
|
|
213
|
+
if (pct >= 90) return `\x1b[32m${pct.toFixed(2)}%\x1b[0m`;
|
|
214
|
+
if (pct >= 80) return `\x1b[33m${pct.toFixed(2)}%\x1b[0m`;
|
|
215
|
+
return `\x1b[31m${pct.toFixed(2)}%\x1b[0m`;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
console.log('\nš Coverage Priority Report\n');
|
|
219
|
+
|
|
220
|
+
// Overall coverage summary box
|
|
221
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
222
|
+
console.log('ā OVERALL COVERAGE ā');
|
|
223
|
+
console.log('āāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤');
|
|
224
|
+
console.log(`ā Lines: ${colorPct(overall.lines.coverage).padEnd(24)}ā Functions: ${colorPct(overall.functions.coverage).padEnd(20)}ā Branches: ${colorPct(overall.branches.coverage).padEnd(22)}ā`);
|
|
225
|
+
console.log(`ā (${overall.lines.hit}/${overall.lines.found})`.padEnd(18) + `ā (${overall.functions.hit}/${overall.functions.found})`.padEnd(18) + `ā (${overall.branches.hit}/${overall.branches.found})`.padEnd(30) + 'ā');
|
|
226
|
+
console.log('āāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
227
|
+
console.log(`\nFiles: ${overall.fileCount} | Weights: B=${options.weights.branches}, F=${options.weights.functions}, L=${options.weights.lines} | Min lines: ${options.minLines}\n`);
|
|
228
|
+
|
|
229
|
+
console.log(divider);
|
|
230
|
+
console.log(
|
|
231
|
+
'Priority'.padEnd(10) +
|
|
232
|
+
'File'.padEnd(45) +
|
|
233
|
+
'Lines'.padEnd(12) +
|
|
234
|
+
'Funcs'.padEnd(12) +
|
|
235
|
+
'Branch'.padEnd(12) +
|
|
236
|
+
'Uncov Lines'.padEnd(12) +
|
|
237
|
+
'Score'
|
|
238
|
+
);
|
|
239
|
+
console.log(divider);
|
|
240
|
+
|
|
241
|
+
analyzed.forEach((item, index) => {
|
|
242
|
+
const priority = index + 1;
|
|
243
|
+
const fileName = item.file.length > 43
|
|
244
|
+
? '...' + item.file.slice(-40)
|
|
245
|
+
: item.file;
|
|
246
|
+
|
|
247
|
+
// Color coding based on coverage
|
|
248
|
+
const colorLine = item.lines.coverage < 50 ? '\x1b[31m' :
|
|
249
|
+
item.lines.coverage < 80 ? '\x1b[33m' : '\x1b[32m';
|
|
250
|
+
const colorFunc = item.functions.coverage < 50 ? '\x1b[31m' :
|
|
251
|
+
item.functions.coverage < 80 ? '\x1b[33m' : '\x1b[32m';
|
|
252
|
+
const colorBranch = item.branches.coverage < 50 ? '\x1b[31m' :
|
|
253
|
+
item.branches.coverage < 80 ? '\x1b[33m' : '\x1b[32m';
|
|
254
|
+
const reset = '\x1b[0m';
|
|
255
|
+
|
|
256
|
+
console.log(
|
|
257
|
+
`#${priority}`.padEnd(10) +
|
|
258
|
+
fileName.padEnd(45) +
|
|
259
|
+
`${colorLine}${item.lines.coverage.toFixed(1)}%${reset}`.padEnd(21) +
|
|
260
|
+
`${colorFunc}${item.functions.coverage.toFixed(1)}%${reset}`.padEnd(21) +
|
|
261
|
+
`${colorBranch}${item.branches.coverage.toFixed(1)}%${reset}`.padEnd(21) +
|
|
262
|
+
`${item.uncoveredLines}`.padEnd(12) +
|
|
263
|
+
item.priorityScore.toFixed(1)
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
console.log(divider);
|
|
268
|
+
console.log(`\nTotal files analyzed: ${analyzed.length}`);
|
|
269
|
+
|
|
270
|
+
// Summary stats
|
|
271
|
+
const totalUncoveredLines = analyzed.reduce((sum, f) => sum + f.uncoveredLines, 0);
|
|
272
|
+
const totalUncoveredBranches = analyzed.reduce((sum, f) => sum + f.uncoveredBranches, 0);
|
|
273
|
+
console.log(`Total uncovered lines: ${totalUncoveredLines}`);
|
|
274
|
+
console.log(`Total uncovered branches: ${totalUncoveredBranches}`);
|
|
275
|
+
|
|
276
|
+
// Top 3 recommendations
|
|
277
|
+
console.log('\nšÆ Recommended Focus (Top 3):\n');
|
|
278
|
+
analyzed.slice(0, 3).forEach((item, i) => {
|
|
279
|
+
const reasons = [];
|
|
280
|
+
if (item.branches.coverage < 70) reasons.push(`${item.branches.found - item.branches.hit} untested branches`);
|
|
281
|
+
if (item.functions.coverage < 80) reasons.push(`${item.functions.found - item.functions.hit} untested functions`);
|
|
282
|
+
if (item.lines.coverage < 70) reasons.push(`${item.uncoveredLines} uncovered lines`);
|
|
283
|
+
|
|
284
|
+
console.log(` ${i + 1}. ${item.file}`);
|
|
285
|
+
console.log(` ${reasons.join(', ') || 'General coverage improvement'}\n`);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Main
|
|
290
|
+
function main() {
|
|
291
|
+
const options = parseArgs();
|
|
292
|
+
|
|
293
|
+
let lcovContent;
|
|
294
|
+
try {
|
|
295
|
+
lcovContent = readFileSync(LCOV_PATH, 'utf-8');
|
|
296
|
+
} catch {
|
|
297
|
+
console.error(`Error: Could not read ${LCOV_PATH}`);
|
|
298
|
+
console.error('Run tests with coverage first: npm test -- --coverage');
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const files = parseLcov(lcovContent);
|
|
303
|
+
|
|
304
|
+
// Calculate overall coverage from ALL files (before filtering)
|
|
305
|
+
const overall = calculateOverallCoverage(files);
|
|
306
|
+
|
|
307
|
+
// Filter and analyze
|
|
308
|
+
const analyzed = files
|
|
309
|
+
.filter(f => f.linesFound >= options.minLines)
|
|
310
|
+
.map(f => analyzeFile(f, options.weights))
|
|
311
|
+
.sort((a, b) => b.priorityScore - a.priorityScore);
|
|
312
|
+
|
|
313
|
+
// Apply top limit if specified
|
|
314
|
+
const results = options.top ? analyzed.slice(0, options.top) : analyzed;
|
|
315
|
+
|
|
316
|
+
if (options.json) {
|
|
317
|
+
console.log(JSON.stringify({ overall, files: results }, null, 2));
|
|
318
|
+
} else {
|
|
319
|
+
formatTable(results, options, overall);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
main();
|