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.
- package/.env.example +38 -0
- package/ARCHITECTURE.md +116 -15
- package/EXPLORATION.md +164 -101
- package/QUICK_START.md +5 -2
- package/README.md +70 -18
- package/bin/taskex.js +11 -4
- package/package.json +39 -6
- package/process_and_upload.js +7 -100
- package/prompt.json +199 -131
- package/src/config.js +52 -3
- package/src/{utils → modes}/deep-dive.js +2 -2
- package/src/{utils → modes}/dynamic-mode.js +2 -2
- package/src/{utils → modes}/focused-reanalysis.js +4 -3
- package/src/{utils → modes}/progress-updater.js +2 -2
- package/src/phases/_shared.js +43 -0
- package/src/phases/compile.js +101 -0
- package/src/phases/deep-dive.js +118 -0
- package/src/phases/discover.js +178 -0
- package/src/phases/init.js +192 -0
- package/src/phases/output.js +238 -0
- package/src/phases/process-media.js +633 -0
- package/src/phases/services.js +104 -0
- package/src/phases/summary.js +86 -0
- package/src/pipeline.js +431 -1462
- package/src/renderers/docx.js +531 -0
- package/src/renderers/html.js +672 -0
- package/src/renderers/markdown.js +15 -183
- package/src/renderers/pdf.js +90 -0
- package/src/renderers/shared.js +211 -0
- package/src/schemas/analysis-compiled.schema.json +381 -0
- package/src/schemas/analysis-segment.schema.json +380 -0
- package/src/services/doc-parser.js +346 -0
- package/src/services/gemini.js +105 -48
- package/src/services/git.js +0 -29
- package/src/services/video.js +123 -8
- package/src/utils/adaptive-budget.js +6 -6
- package/src/utils/{progress.js → checkpoint.js} +2 -1
- package/src/utils/cli.js +161 -113
- package/src/utils/colors.js +83 -0
- package/src/utils/confidence-filter.js +138 -0
- package/src/utils/context-manager.js +0 -4
- package/src/utils/diff-engine.js +2 -4
- package/src/utils/global-config.js +6 -5
- package/src/utils/health-dashboard.js +11 -9
- package/src/utils/json-parser.js +5 -3
- package/src/utils/learning-loop.js +3 -3
- package/src/utils/progress-bar.js +286 -0
- package/src/utils/quality-gate.js +4 -8
- package/src/utils/retry.js +13 -5
- package/src/utils/schema-validator.js +314 -0
- package/src/utils/prompt.js +0 -32
- /package/src/{utils → modes}/change-detector.js +0 -0
package/src/services/git.js
CHANGED
|
@@ -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,
|
package/src/services/video.js
CHANGED
|
@@ -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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
268
|
+
console.log(` ${c.success(`Re-segmented from fallback: ${segments.length} segment(s)`)}`);
|
|
268
269
|
}
|
|
269
270
|
} else {
|
|
270
|
-
console.error(`
|
|
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(`
|
|
289
|
+
console.log(` ${c.success('Retry succeeded')}`);
|
|
289
290
|
} else {
|
|
290
|
-
console.error(`
|
|
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
|
|
25
|
-
MAX
|
|
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
|
|
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(`
|
|
72
|
+
console.warn(` ${c.warn(`Could not save progress: ${err.message}`)}`);
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|