@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.
Files changed (88) hide show
  1. package/.cursor/rules/definition-of-done.md +1 -0
  2. package/.cursor/rules/no-emoticons.md +26 -12
  3. package/README.md +483 -69
  4. package/dist/agentic/executor.js +473 -41
  5. package/dist/agentic/executor.js.map +1 -1
  6. package/dist/agentic/index.js.map +1 -1
  7. package/dist/agentic/tools/lookup-person.js +123 -4
  8. package/dist/agentic/tools/lookup-person.js.map +1 -1
  9. package/dist/agentic/tools/lookup-project.js +139 -22
  10. package/dist/agentic/tools/lookup-project.js.map +1 -1
  11. package/dist/agentic/tools/route-note.js +5 -1
  12. package/dist/agentic/tools/route-note.js.map +1 -1
  13. package/dist/arguments.js +6 -3
  14. package/dist/arguments.js.map +1 -1
  15. package/dist/cli/action.js +704 -0
  16. package/dist/cli/action.js.map +1 -0
  17. package/dist/cli/config.js +482 -0
  18. package/dist/cli/config.js.map +1 -0
  19. package/dist/cli/context.js +466 -0
  20. package/dist/cli/context.js.map +1 -0
  21. package/dist/cli/feedback.js +858 -0
  22. package/dist/cli/feedback.js.map +1 -0
  23. package/dist/cli/index.js +103 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/cli/install.js +572 -0
  26. package/dist/cli/install.js.map +1 -0
  27. package/dist/cli/transcript.js +199 -0
  28. package/dist/cli/transcript.js.map +1 -0
  29. package/dist/constants.js +12 -5
  30. package/dist/constants.js.map +1 -1
  31. package/dist/context/discovery.js +1 -1
  32. package/dist/context/discovery.js.map +1 -1
  33. package/dist/context/index.js +25 -1
  34. package/dist/context/index.js.map +1 -1
  35. package/dist/context/storage.js +57 -4
  36. package/dist/context/storage.js.map +1 -1
  37. package/dist/interactive/handler.js +310 -9
  38. package/dist/interactive/handler.js.map +1 -1
  39. package/dist/main.js +11 -1
  40. package/dist/main.js.map +1 -1
  41. package/dist/output/index.js.map +1 -1
  42. package/dist/output/manager.js +47 -2
  43. package/dist/output/manager.js.map +1 -1
  44. package/dist/phases/complete.js +38 -3
  45. package/dist/phases/complete.js.map +1 -1
  46. package/dist/phases/locate.js +1 -1
  47. package/dist/phases/locate.js.map +1 -1
  48. package/dist/pipeline/orchestrator.js +104 -31
  49. package/dist/pipeline/orchestrator.js.map +1 -1
  50. package/dist/protokoll.js +68 -2
  51. package/dist/protokoll.js.map +1 -1
  52. package/dist/reasoning/client.js +83 -0
  53. package/dist/reasoning/client.js.map +1 -1
  54. package/dist/reasoning/index.js +1 -0
  55. package/dist/reasoning/index.js.map +1 -1
  56. package/dist/routing/router.js +2 -2
  57. package/dist/routing/router.js.map +1 -1
  58. package/dist/util/media.js +1 -1
  59. package/dist/util/media.js.map +1 -1
  60. package/dist/util/metadata.js.map +1 -1
  61. package/dist/util/sound.js +116 -0
  62. package/dist/util/sound.js.map +1 -0
  63. package/dist/util/storage.js +3 -3
  64. package/dist/util/storage.js.map +1 -1
  65. package/docs/duplicate-question-prevention.md +117 -0
  66. package/docs/examples.md +152 -0
  67. package/docs/interactive-context-example.md +92 -0
  68. package/docs/package-lock.json +6 -0
  69. package/docs/package.json +3 -1
  70. package/eslint.config.mjs +1 -1
  71. package/guide/action.md +375 -0
  72. package/guide/config.md +207 -0
  73. package/guide/configuration.md +82 -67
  74. package/guide/context-commands.md +574 -0
  75. package/guide/context-system.md +20 -7
  76. package/guide/development.md +106 -4
  77. package/guide/feedback.md +335 -0
  78. package/guide/index.md +100 -4
  79. package/guide/interactive.md +15 -14
  80. package/guide/quickstart.md +21 -7
  81. package/guide/reasoning.md +18 -4
  82. package/guide/routing.md +192 -97
  83. package/package.json +2 -3
  84. package/scripts/copy-assets.mjs +47 -0
  85. package/scripts/coverage-priority.mjs +323 -0
  86. package/tsconfig.tsbuildinfo +1 -1
  87. package/vite.config.ts +6 -13
  88. 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();