task-summary-extractor 8.1.0 → 9.0.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.
Files changed (52) hide show
  1. package/.env.example +38 -0
  2. package/ARCHITECTURE.md +116 -15
  3. package/EXPLORATION.md +164 -101
  4. package/QUICK_START.md +5 -2
  5. package/README.md +70 -18
  6. package/bin/taskex.js +11 -4
  7. package/package.json +39 -6
  8. package/process_and_upload.js +7 -100
  9. package/prompt.json +199 -131
  10. package/src/config.js +52 -3
  11. package/src/{utils → modes}/deep-dive.js +2 -2
  12. package/src/{utils → modes}/dynamic-mode.js +2 -2
  13. package/src/{utils → modes}/focused-reanalysis.js +4 -3
  14. package/src/{utils → modes}/progress-updater.js +2 -2
  15. package/src/phases/_shared.js +43 -0
  16. package/src/phases/compile.js +101 -0
  17. package/src/phases/deep-dive.js +118 -0
  18. package/src/phases/discover.js +178 -0
  19. package/src/phases/init.js +192 -0
  20. package/src/phases/output.js +238 -0
  21. package/src/phases/process-media.js +633 -0
  22. package/src/phases/services.js +104 -0
  23. package/src/phases/summary.js +86 -0
  24. package/src/pipeline.js +431 -1462
  25. package/src/renderers/docx.js +531 -0
  26. package/src/renderers/html.js +672 -0
  27. package/src/renderers/markdown.js +15 -183
  28. package/src/renderers/pdf.js +90 -0
  29. package/src/renderers/shared.js +211 -0
  30. package/src/schemas/analysis-compiled.schema.json +381 -0
  31. package/src/schemas/analysis-segment.schema.json +380 -0
  32. package/src/services/doc-parser.js +346 -0
  33. package/src/services/gemini.js +105 -48
  34. package/src/services/git.js +0 -29
  35. package/src/services/video.js +123 -8
  36. package/src/utils/adaptive-budget.js +6 -6
  37. package/src/utils/{progress.js → checkpoint.js} +2 -1
  38. package/src/utils/cli.js +161 -113
  39. package/src/utils/colors.js +83 -0
  40. package/src/utils/confidence-filter.js +138 -0
  41. package/src/utils/context-manager.js +0 -4
  42. package/src/utils/diff-engine.js +2 -4
  43. package/src/utils/global-config.js +6 -5
  44. package/src/utils/health-dashboard.js +11 -9
  45. package/src/utils/json-parser.js +5 -3
  46. package/src/utils/learning-loop.js +3 -3
  47. package/src/utils/progress-bar.js +286 -0
  48. package/src/utils/quality-gate.js +4 -8
  49. package/src/utils/retry.js +13 -5
  50. package/src/utils/schema-validator.js +314 -0
  51. package/src/utils/prompt.js +0 -32
  52. /package/src/{utils → modes}/change-detector.js +0 -0
@@ -254,34 +254,6 @@ function getDiffSummary(repoPath, sinceISO) {
254
254
  return output || 'No stat available';
255
255
  }
256
256
 
257
- /**
258
- * Get actual diff content, truncated to maxBytes.
259
- * Used for keyword matching, NOT sent to Gemini directly.
260
- *
261
- * @param {string} repoPath
262
- * @param {string} sinceISO
263
- * @param {number} [maxBytes=100000]
264
- * @returns {string} Diff content (may be truncated)
265
- */
266
- function getDiffContent(repoPath, sinceISO, maxBytes = 100000) {
267
- const commits = getCommitsSince(repoPath, sinceISO);
268
- if (commits.length === 0) return '';
269
-
270
- const oldestHash = commits[commits.length - 1].hash;
271
- let output = execGit(
272
- ['diff', '--no-color', '-U2', `${oldestHash}~1`, 'HEAD'],
273
- repoPath,
274
- );
275
- if (!output) {
276
- output = execGit(['diff', '--no-color', '-U2', oldestHash, 'HEAD'], repoPath);
277
- }
278
- if (!output) return '';
279
-
280
- return output.length > maxBytes
281
- ? output.slice(0, maxBytes) + '\n... (truncated at ' + Math.round(maxBytes / 1024) + ' KB)'
282
- : output;
283
- }
284
-
285
257
  // ======================== WORKING TREE ========================
286
258
 
287
259
  /**
@@ -322,7 +294,6 @@ module.exports = {
322
294
  getCommitsWithFiles,
323
295
  getChangedFilesSince,
324
296
  getDiffSummary,
325
- getDiffContent,
326
297
  getWorkingTreeChanges,
327
298
  getCurrentBranch,
328
299
  normPath,
@@ -14,6 +14,7 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
  const { SPEED, SEG_TIME, PRESET } = require('../config');
16
16
  const { fmtDuration } = require('../utils/format');
17
+ const { c } = require('../utils/colors');
17
18
 
18
19
  // ======================== BINARY DETECTION ========================
19
20
 
@@ -181,7 +182,7 @@ function compressAndSegment(inputFile, outputDir) {
181
182
 
182
183
  const result = spawnSync(getFFmpeg(), args, { stdio: 'inherit' });
183
184
  if (result.status !== 0) {
184
- console.warn(` ffmpeg exited with code ${result.status} (output may still be usable)`);
185
+ console.warn(` ${c.warn(`ffmpeg exited with code ${result.status} (output may still be usable)`)}`);
185
186
  }
186
187
  } else {
187
188
  console.log(` Compressing (single output, ${effectiveDuration ? fmtDuration(effectiveDuration) : '?'} effective)...`);
@@ -196,7 +197,7 @@ function compressAndSegment(inputFile, outputDir) {
196
197
 
197
198
  const result = spawnSync(getFFmpeg(), args, { stdio: 'inherit' });
198
199
  if (result.status !== 0) {
199
- console.warn(` ffmpeg exited with code ${result.status}`);
200
+ console.warn(` ${c.warn(`ffmpeg exited with code ${result.status}`)}`);
200
201
  }
201
202
  }
202
203
 
@@ -214,7 +215,7 @@ function compressAndSegment(inputFile, outputDir) {
214
215
  valid.push(seg);
215
216
  } else {
216
217
  corrupt.push(seg);
217
- console.warn(` Corrupt segment detected: ${path.basename(seg)} (missing moov atom)`);
218
+ console.warn(` ${c.warn(`Corrupt segment detected: ${path.basename(seg)} (missing moov atom)`)}`);
218
219
  }
219
220
  }
220
221
 
@@ -239,7 +240,7 @@ function compressAndSegment(inputFile, outputDir) {
239
240
  const dest = path.join(outputDir, 'segment_00.mp4');
240
241
  fs.renameSync(fallbackPath, dest);
241
242
  segments = [dest];
242
- console.log(` Re-encoded successfully as single segment`);
243
+ console.log(` ${c.success('Re-encoded successfully as single segment')}`);
243
244
  } else {
244
245
  // Re-segment the fallback
245
246
  const reSegDir = path.join(outputDir, '_reseg');
@@ -264,10 +265,10 @@ function compressAndSegment(inputFile, outputDir) {
264
265
  .filter(f => f.startsWith('segment_') && f.endsWith('.mp4'))
265
266
  .sort()
266
267
  .map(f => path.join(outputDir, f));
267
- console.log(` Re-segmented from fallback: ${segments.length} segment(s)`);
268
+ console.log(` ${c.success(`Re-segmented from fallback: ${segments.length} segment(s)`)}`);
268
269
  }
269
270
  } else {
270
- console.error(` Fallback re-encode also failed`);
271
+ console.error(` ${c.error('Fallback re-encode also failed')}`);
271
272
  try { fs.unlinkSync(fallbackPath); } catch {}
272
273
  }
273
274
  } else if (corrupt.length > 0 && !needsSegmentation) {
@@ -285,9 +286,122 @@ function compressAndSegment(inputFile, outputDir) {
285
286
  const retryResult = spawnSync(getFFmpeg(), retryArgs, { stdio: 'inherit' });
286
287
  if (retryResult.status === 0 && verifySegment(retryPath)) {
287
288
  segments = [retryPath];
288
- console.log(` Retry succeeded`);
289
+ console.log(` ${c.success('Retry succeeded')}`);
289
290
  } else {
290
- console.error(` Retry also produced invalid output`);
291
+ console.error(` ${c.error('Retry also produced invalid output')}`);
292
+ }
293
+ }
294
+
295
+ return segments;
296
+ }
297
+
298
+ /**
299
+ * Compress and segment an audio-only file using ffmpeg.
300
+ * No video stream — just audio compression + segmentation in MP4/M4A container
301
+ * (for Gemini File API compatibility).
302
+ *
303
+ * Returns sorted array of segment file paths.
304
+ */
305
+ function compressAndSegmentAudio(inputFile, outputDir) {
306
+ fs.mkdirSync(outputDir, { recursive: true });
307
+
308
+ const duration = probeFormat(inputFile, 'duration');
309
+ const durationSec = duration ? parseFloat(duration) : null;
310
+ const effectiveDuration = durationSec ? durationSec / SPEED : null;
311
+ const channels = parseInt(probe(inputFile, 'a:0', 'channels') || '1', 10);
312
+ const sampleRate = probe(inputFile, 'a:0', 'sample_rate') || '16000';
313
+ const audioBr = channels >= 2 ? '128k' : '64k';
314
+
315
+ console.log(` Duration : ${duration ? fmtDuration(parseFloat(duration)) : 'unknown'}${effectiveDuration ? ` (${fmtDuration(effectiveDuration)} at ${SPEED}x)` : ''}`);
316
+ console.log(` Audio-only mode | ${SPEED}x speed | ${audioBr} bitrate`);
317
+
318
+ const encodingArgs = [
319
+ '-af', `atempo=${SPEED}`,
320
+ '-c:a', 'aac', '-b:a', audioBr, '-ar', sampleRate, '-ac', String(channels),
321
+ '-vn', // no video
322
+ '-movflags', '+faststart',
323
+ ];
324
+
325
+ const needsSegmentation = effectiveDuration === null || effectiveDuration > SEG_TIME;
326
+
327
+ if (needsSegmentation) {
328
+ console.log(` Compressing (segmented, ${SEG_TIME}s chunks)...`);
329
+ const args = [
330
+ '-y', '-i', inputFile,
331
+ ...encodingArgs,
332
+ '-f', 'segment', '-segment_time', String(SEG_TIME), '-reset_timestamps', '1',
333
+ path.join(outputDir, 'segment_%02d.m4a'),
334
+ ];
335
+ const result = spawnSync(getFFmpeg(), args, { stdio: 'inherit' });
336
+ if (result.status !== 0) {
337
+ console.warn(` ${c.warn(`ffmpeg exited with code ${result.status} (output may still be usable)`)}`);
338
+ }
339
+ } else {
340
+ console.log(` Compressing (single output, ${effectiveDuration ? fmtDuration(effectiveDuration) : '?'} effective)...`);
341
+ const outPath = path.join(outputDir, 'segment_00.m4a');
342
+ const args = ['-y', '-i', inputFile, ...encodingArgs, outPath];
343
+ const result = spawnSync(getFFmpeg(), args, { stdio: 'inherit' });
344
+ if (result.status !== 0) {
345
+ console.warn(` ${c.warn(`ffmpeg exited with code ${result.status}`)}`);
346
+ }
347
+ }
348
+
349
+ // Collect segments (both .mp4 and .m4a)
350
+ let segments = fs.readdirSync(outputDir)
351
+ .filter(f => f.startsWith('segment_') && (f.endsWith('.m4a') || f.endsWith('.mp4')))
352
+ .sort()
353
+ .map(f => path.join(outputDir, f));
354
+
355
+ // Validate segments
356
+ const valid = [];
357
+ const corrupt = [];
358
+ for (const seg of segments) {
359
+ if (verifySegment(seg)) {
360
+ valid.push(seg);
361
+ } else {
362
+ corrupt.push(seg);
363
+ console.warn(` ${c.warn(`Corrupt audio segment: ${path.basename(seg)}`)}`);
364
+ }
365
+ }
366
+
367
+ if (corrupt.length > 0) {
368
+ console.log(` Retrying ${corrupt.length} corrupt segment(s)...`);
369
+ const fallbackPath = path.join(outputDir, '_fallback_full.m4a');
370
+ const fbArgs = ['-y', '-i', inputFile, ...encodingArgs, fallbackPath];
371
+ const fbResult = spawnSync(getFFmpeg(), fbArgs, { stdio: 'inherit' });
372
+ if (fbResult.status === 0 && verifySegment(fallbackPath)) {
373
+ for (const seg of corrupt) { try { fs.unlinkSync(seg); } catch {} }
374
+ if (segments.length === 1) {
375
+ const dest = path.join(outputDir, 'segment_00.m4a');
376
+ fs.renameSync(fallbackPath, dest);
377
+ segments = [dest];
378
+ console.log(` ${c.success('Re-encoded as single segment')}`);
379
+ } else {
380
+ // Re-segment
381
+ const reSegDir = path.join(outputDir, '_reseg');
382
+ fs.mkdirSync(reSegDir, { recursive: true });
383
+ const rsArgs = [
384
+ '-y', '-i', fallbackPath,
385
+ '-c', 'copy', '-vn',
386
+ '-f', 'segment', '-segment_time', String(SEG_TIME), '-reset_timestamps', '1',
387
+ path.join(reSegDir, 'segment_%02d.m4a'),
388
+ ];
389
+ spawnSync(getFFmpeg(), rsArgs, { stdio: 'inherit' });
390
+ const reSegs = fs.readdirSync(reSegDir).filter(f => f.endsWith('.m4a')).sort();
391
+ for (const f of reSegs) {
392
+ fs.renameSync(path.join(reSegDir, f), path.join(outputDir, f));
393
+ }
394
+ try { fs.rmSync(reSegDir, { recursive: true }); } catch {}
395
+ try { fs.unlinkSync(fallbackPath); } catch {}
396
+ segments = fs.readdirSync(outputDir)
397
+ .filter(f => f.startsWith('segment_') && (f.endsWith('.m4a') || f.endsWith('.mp4')))
398
+ .sort()
399
+ .map(f => path.join(outputDir, f));
400
+ console.log(` ${c.success(`Re-segmented from fallback: ${segments.length} segment(s)`)}`);
401
+ }
402
+ } else {
403
+ console.error(` ${c.error('Fallback audio re-encode failed')}`);
404
+ try { fs.unlinkSync(fallbackPath); } catch {}
291
405
  }
292
406
  }
293
407
 
@@ -299,6 +413,7 @@ module.exports = {
299
413
  probe,
300
414
  probeFormat,
301
415
  compressAndSegment,
416
+ compressAndSegmentAudio,
302
417
  verifySegment,
303
418
  getFFmpeg,
304
419
  getFFprobe,
@@ -14,6 +14,8 @@
14
14
 
15
15
  'use strict';
16
16
 
17
+ const config = require('../config');
18
+
17
19
  // ======================== BUDGET RANGES ========================
18
20
 
19
21
  const BUDGET = {
@@ -21,12 +23,12 @@ const BUDGET = {
21
23
  MIN: 8192,
22
24
  /** Base thinking budget for a simple segment */
23
25
  BASE: 16384,
24
- /** Maximum thinking budget per segment (avoid eating output token pool) */
25
- MAX: 32768,
26
+ /** Maximum thinking budget per segment dynamically read from model config */
27
+ get MAX() { return config.getMaxThinkingBudget(); },
26
28
  /** Base compilation thinking budget */
27
29
  COMPILATION_BASE: 10240,
28
- /** Max compilation thinking budget */
29
- COMPILATION_MAX: 24576,
30
+ /** Max compilation thinking budget — dynamically read from model config */
31
+ get COMPILATION_MAX() { return config.getMaxThinkingBudget(); },
30
32
  };
31
33
 
32
34
  // ======================== COMPLEXITY ANALYSIS ========================
@@ -259,8 +261,6 @@ function calculateCompilationBudget(allSegmentAnalyses, baseBudget = BUDGET.COMP
259
261
  }
260
262
 
261
263
  module.exports = {
262
- analyzeTranscriptComplexity,
263
264
  calculateThinkingBudget,
264
265
  calculateCompilationBudget,
265
- BUDGET,
266
266
  };
@@ -11,6 +11,7 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
+ const { c } = require('./colors');
14
15
 
15
16
  const STATE_FILE = '.pipeline-state.json';
16
17
 
@@ -68,7 +69,7 @@ class Progress {
68
69
  try {
69
70
  fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2), 'utf8');
70
71
  } catch (err) {
71
- console.warn(` Could not save progress: ${err.message}`);
72
+ console.warn(` ${c.warn(`Could not save progress: ${err.message}`)}`);
72
73
  }
73
74
  }
74
75