task-summary-extractor 8.1.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/ARCHITECTURE.md +605 -0
- package/EXPLORATION.md +451 -0
- package/QUICK_START.md +272 -0
- package/README.md +544 -0
- package/bin/taskex.js +64 -0
- package/package.json +63 -0
- package/process_and_upload.js +107 -0
- package/prompt.json +265 -0
- package/setup.js +505 -0
- package/src/config.js +327 -0
- package/src/logger.js +355 -0
- package/src/pipeline.js +2006 -0
- package/src/renderers/markdown.js +968 -0
- package/src/services/firebase.js +106 -0
- package/src/services/gemini.js +779 -0
- package/src/services/git.js +329 -0
- package/src/services/video.js +305 -0
- package/src/utils/adaptive-budget.js +266 -0
- package/src/utils/change-detector.js +466 -0
- package/src/utils/cli.js +415 -0
- package/src/utils/context-manager.js +499 -0
- package/src/utils/cost-tracker.js +156 -0
- package/src/utils/deep-dive.js +549 -0
- package/src/utils/diff-engine.js +315 -0
- package/src/utils/dynamic-mode.js +567 -0
- package/src/utils/focused-reanalysis.js +317 -0
- package/src/utils/format.js +32 -0
- package/src/utils/fs.js +39 -0
- package/src/utils/global-config.js +315 -0
- package/src/utils/health-dashboard.js +216 -0
- package/src/utils/inject-cli-flags.js +58 -0
- package/src/utils/json-parser.js +245 -0
- package/src/utils/learning-loop.js +301 -0
- package/src/utils/progress-updater.js +451 -0
- package/src/utils/progress.js +166 -0
- package/src/utils/prompt.js +32 -0
- package/src/utils/quality-gate.js +429 -0
- package/src/utils/retry.js +129 -0
package/setup.js
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Task Summary Extractor — Complete Setup
|
|
4
|
+
*
|
|
5
|
+
* Sets up everything needed from zero to working:
|
|
6
|
+
* 1. Checks prerequisites (Node.js, ffmpeg, git)
|
|
7
|
+
* 2. Installs npm dependencies
|
|
8
|
+
* 3. Creates .env with API key
|
|
9
|
+
* 4. Sets up .gitignore for local data
|
|
10
|
+
* 5. Creates sample call folder structure
|
|
11
|
+
* 6. Optionally creates a local working branch
|
|
12
|
+
* 7. Validates the pipeline loads
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node setup.js Full interactive setup
|
|
16
|
+
* node setup.js --check Validation only (no changes)
|
|
17
|
+
* node setup.js --silent Non-interactive (skip prompts, use defaults)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const { execSync } = require('child_process');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const readline = require('readline');
|
|
26
|
+
|
|
27
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const ROOT = __dirname;
|
|
30
|
+
const ENV_FILE = path.join(ROOT, '.env');
|
|
31
|
+
const GITIGNORE_FILE = path.join(ROOT, '.gitignore');
|
|
32
|
+
const NODE_MODULES = path.join(ROOT, 'node_modules');
|
|
33
|
+
const SAMPLE_CALL = path.join(ROOT, 'sample-call');
|
|
34
|
+
|
|
35
|
+
const CHECK_ONLY = process.argv.includes('--check');
|
|
36
|
+
const SILENT = process.argv.includes('--silent');
|
|
37
|
+
const REQUIRED_NODE_VERSION = 18;
|
|
38
|
+
|
|
39
|
+
const ENV_TEMPLATE = `# ─── Required ──────────────────────────────────────────
|
|
40
|
+
GEMINI_API_KEY=YOUR_GEMINI_API_KEY_HERE
|
|
41
|
+
|
|
42
|
+
# ─── Optional: Firebase (skip if using --skip-upload) ──
|
|
43
|
+
# Get these from Firebase Console → Project Settings → General
|
|
44
|
+
# FIREBASE_API_KEY=
|
|
45
|
+
# FIREBASE_AUTH_DOMAIN=
|
|
46
|
+
# FIREBASE_PROJECT_ID=
|
|
47
|
+
# FIREBASE_STORAGE_BUCKET=
|
|
48
|
+
# FIREBASE_MESSAGING_SENDER_ID=
|
|
49
|
+
# FIREBASE_APP_ID=
|
|
50
|
+
# FIREBASE_MEASUREMENT_ID=
|
|
51
|
+
|
|
52
|
+
# ─── Optional: Tuning ─────────────────────────────────
|
|
53
|
+
# GEMINI_MODEL=gemini-2.5-flash
|
|
54
|
+
# VIDEO_SPEED=1.5
|
|
55
|
+
# VIDEO_SEGMENT_TIME=280
|
|
56
|
+
# VIDEO_PRESET=slow
|
|
57
|
+
# THINKING_BUDGET=24576
|
|
58
|
+
# COMPILATION_THINKING_BUDGET=10240
|
|
59
|
+
# LOG_LEVEL=info
|
|
60
|
+
# MAX_PARALLEL_UPLOADS=3
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const GITIGNORE_CONTENT = `# ─── Local Data (call folders, results, logs) ─────────
|
|
64
|
+
call */
|
|
65
|
+
logs/
|
|
66
|
+
gemini_runs/
|
|
67
|
+
|
|
68
|
+
# ─── Video Files ──────────────────────────────────────
|
|
69
|
+
**/*.mp4
|
|
70
|
+
**/*.mkv
|
|
71
|
+
**/*.avi
|
|
72
|
+
**/*.mov
|
|
73
|
+
**/*.webm
|
|
74
|
+
|
|
75
|
+
# ─── Environment & Dependencies ───────────────────────
|
|
76
|
+
.env
|
|
77
|
+
.env.local
|
|
78
|
+
node_modules/
|
|
79
|
+
|
|
80
|
+
# ─── OS / Editor ──────────────────────────────────────
|
|
81
|
+
.DS_Store
|
|
82
|
+
Thumbs.db
|
|
83
|
+
*.swp
|
|
84
|
+
*.swo
|
|
85
|
+
.vscode/settings.json
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const SAMPLE_README = `# Sample Meeting Folder
|
|
89
|
+
|
|
90
|
+
This is a sample folder. Replace with your actual recording.
|
|
91
|
+
|
|
92
|
+
## How to use
|
|
93
|
+
|
|
94
|
+
1. Drop your video file here (e.g., \`Meeting Recording.mp4\`)
|
|
95
|
+
2. Add subtitles if available (e.g., \`Meeting Recording.vtt\`)
|
|
96
|
+
3. Add any relevant docs in subfolders — the pipeline scans ALL subfolders recursively
|
|
97
|
+
4. Run: \`taskex --name "Your Name" "sample-call"\`
|
|
98
|
+
|
|
99
|
+
## Folder structure
|
|
100
|
+
|
|
101
|
+
\`\`\`
|
|
102
|
+
sample-call/
|
|
103
|
+
├── your-video.mp4 ← Required: your recording
|
|
104
|
+
├── your-video.vtt ← Recommended: subtitles
|
|
105
|
+
├── agenda.md ← Optional: loose docs at root work too
|
|
106
|
+
│
|
|
107
|
+
├── .tasks/ ← Optional: gets highest priority in AI prompt
|
|
108
|
+
│ ├── code-map.md
|
|
109
|
+
│ └── team.csv
|
|
110
|
+
├── specs/ ← Any folder name — all are scanned
|
|
111
|
+
│ └── requirements.md
|
|
112
|
+
├── notes/ ← Add as many context folders as you need
|
|
113
|
+
│ └── previous-decisions.md
|
|
114
|
+
│
|
|
115
|
+
├── compressed/ ← Auto-generated: compressed segments
|
|
116
|
+
└── runs/ ← Auto-generated: analysis results
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
## Use case examples
|
|
120
|
+
|
|
121
|
+
- **Dev call**: .tasks/code-map.md, .tasks/current-sprint.md, docs/tech-debt.md
|
|
122
|
+
- **Client meeting**: requirements/scope.md, contracts/sow.md, .tasks/stakeholders.csv
|
|
123
|
+
- **Interview**: role/job-description.md, role/evaluation-rubric.md
|
|
124
|
+
- **Incident review**: systems/architecture.md, runbooks/service-x.md
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
const ICONS = { ok: '\u2705', fail: '\u274c', warn: '\u26a0\ufe0f', info: '\u2139\ufe0f', gear: '\u2699\ufe0f', rocket: '\ud83d\ude80', folder: '\ud83d\udcc1' };
|
|
130
|
+
|
|
131
|
+
function print(icon, msg) { console.log(` ${icon} ${msg}`); }
|
|
132
|
+
function printSub(msg) { console.log(` ${msg}`); }
|
|
133
|
+
|
|
134
|
+
function header(num, title) {
|
|
135
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
136
|
+
console.log(` Step ${num}: ${title}`);
|
|
137
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function commandExists(cmd) {
|
|
141
|
+
try {
|
|
142
|
+
const where = process.platform === 'win32' ? 'where' : 'which';
|
|
143
|
+
execSync(`${where} ${cmd}`, { stdio: 'pipe' });
|
|
144
|
+
return true;
|
|
145
|
+
} catch { return false; }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getVersion(cmd, flag = '--version') {
|
|
149
|
+
try {
|
|
150
|
+
return execSync(`${cmd} ${flag}`, { stdio: 'pipe' }).toString().trim().split('\n')[0];
|
|
151
|
+
} catch { return null; }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function ask(question) {
|
|
155
|
+
if (SILENT) return Promise.resolve('');
|
|
156
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
157
|
+
return new Promise(resolve => rl.question(question, a => { rl.close(); resolve(a.trim()); }));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function confirm(question, defaultYes = true) {
|
|
161
|
+
if (SILENT) return defaultYes;
|
|
162
|
+
const suffix = defaultYes ? '[Y/n]' : '[y/N]';
|
|
163
|
+
const answer = await ask(` ${question} ${suffix} `);
|
|
164
|
+
if (answer === '') return defaultYes;
|
|
165
|
+
return answer.toLowerCase().startsWith('y');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Tracking ─────────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
const counts = { pass: 0, fail: 0, warn: 0, action: 0 };
|
|
171
|
+
function pass(msg) { counts.pass++; print(ICONS.ok, msg); }
|
|
172
|
+
function fail(msg, hint) { counts.fail++; print(ICONS.fail, msg); if (hint) printSub(`→ ${hint}`); }
|
|
173
|
+
function warn(msg, hint) { counts.warn++; print(ICONS.warn, msg); if (hint) printSub(`→ ${hint}`); }
|
|
174
|
+
function action(msg) { counts.action++; print(ICONS.gear, msg); }
|
|
175
|
+
|
|
176
|
+
// ─── Step 1: Prerequisites ───────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
function checkPrerequisites() {
|
|
179
|
+
header(1, 'Prerequisites');
|
|
180
|
+
|
|
181
|
+
// Node.js
|
|
182
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
183
|
+
if (nodeMajor >= REQUIRED_NODE_VERSION) {
|
|
184
|
+
pass(`Node.js v${process.versions.node} (requires ≥${REQUIRED_NODE_VERSION})`);
|
|
185
|
+
} else {
|
|
186
|
+
fail(`Node.js v${process.versions.node} — requires ≥${REQUIRED_NODE_VERSION}`, 'Download from https://nodejs.org/');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ffmpeg
|
|
190
|
+
if (commandExists('ffmpeg')) {
|
|
191
|
+
const ver = getVersion('ffmpeg', '-version');
|
|
192
|
+
const short = ver ? ver.replace(/^ffmpeg version\s+/i, '').split(' ')[0] : 'found';
|
|
193
|
+
pass(`ffmpeg ${short}`);
|
|
194
|
+
} else if (process.platform === 'win32' && fs.existsSync('C:\\ffmpeg\\bin\\ffmpeg.exe')) {
|
|
195
|
+
pass('ffmpeg found at C:\\ffmpeg\\bin\\');
|
|
196
|
+
} else {
|
|
197
|
+
fail('ffmpeg not found in PATH',
|
|
198
|
+
process.platform === 'win32'
|
|
199
|
+
? 'Download: https://www.gyan.dev/ffmpeg/builds/ → add to PATH or C:\\ffmpeg\\bin\\'
|
|
200
|
+
: 'Install: brew install ffmpeg (macOS) / apt install ffmpeg (Linux)');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Git
|
|
204
|
+
if (commandExists('git')) {
|
|
205
|
+
const ver = getVersion('git') || 'found';
|
|
206
|
+
pass(`Git ${ver.replace('git version ', '')}`);
|
|
207
|
+
} else {
|
|
208
|
+
warn('Git not found — --update-progress feature will be unavailable',
|
|
209
|
+
'Install from https://git-scm.com/downloads');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Step 2: Dependencies ────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
async function installDependencies() {
|
|
216
|
+
header(2, 'Dependencies');
|
|
217
|
+
|
|
218
|
+
if (fs.existsSync(NODE_MODULES)) {
|
|
219
|
+
// Check if key packages exist
|
|
220
|
+
const hasGenai = fs.existsSync(path.join(NODE_MODULES, '@google', 'genai'));
|
|
221
|
+
const hasFirebase = fs.existsSync(path.join(NODE_MODULES, 'firebase'));
|
|
222
|
+
const hasDotenv = fs.existsSync(path.join(NODE_MODULES, 'dotenv'));
|
|
223
|
+
|
|
224
|
+
if (hasGenai && hasFirebase && hasDotenv) {
|
|
225
|
+
pass('All dependencies installed');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
warn('node_modules exists but some packages are missing');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (CHECK_ONLY) {
|
|
232
|
+
fail('Dependencies not fully installed', 'Run: node setup.js');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
action('Running npm install...');
|
|
237
|
+
try {
|
|
238
|
+
execSync('npm install', { cwd: ROOT, stdio: 'inherit' });
|
|
239
|
+
pass('npm install completed');
|
|
240
|
+
} catch {
|
|
241
|
+
fail('npm install failed', 'Try: rm -rf node_modules && npm install');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── Step 3: Environment ─────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
async function setupEnvironment() {
|
|
248
|
+
header(3, 'Environment Configuration');
|
|
249
|
+
|
|
250
|
+
if (fs.existsSync(ENV_FILE)) {
|
|
251
|
+
const content = fs.readFileSync(ENV_FILE, 'utf8');
|
|
252
|
+
const hasKey = /GEMINI_API_KEY\s*=\s*\S+/.test(content) && !content.includes('YOUR_GEMINI_API_KEY_HERE');
|
|
253
|
+
|
|
254
|
+
if (hasKey) {
|
|
255
|
+
pass('.env file with GEMINI_API_KEY configured');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
warn('.env exists but GEMINI_API_KEY not set', 'Edit .env and add your key from https://aistudio.google.com/apikey');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (CHECK_ONLY) {
|
|
263
|
+
fail('.env file not found', 'Run: node setup.js');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
action('Creating .env from template...');
|
|
268
|
+
let content = ENV_TEMPLATE;
|
|
269
|
+
|
|
270
|
+
const key = await ask(' Enter your Gemini API key (or Enter to skip): ');
|
|
271
|
+
if (key) {
|
|
272
|
+
content = content.replace('YOUR_GEMINI_API_KEY_HERE', key);
|
|
273
|
+
pass('Gemini API key saved to .env');
|
|
274
|
+
} else {
|
|
275
|
+
warn('Gemini API key not set — edit .env later', 'Get key: https://aistudio.google.com/apikey');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fs.writeFileSync(ENV_FILE, content, 'utf8');
|
|
279
|
+
pass('.env file created');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── Step 4: Git Ignore ──────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
function setupGitignore() {
|
|
285
|
+
header(4, 'Git Configuration');
|
|
286
|
+
|
|
287
|
+
if (fs.existsSync(GITIGNORE_FILE)) {
|
|
288
|
+
const content = fs.readFileSync(GITIGNORE_FILE, 'utf8');
|
|
289
|
+
|
|
290
|
+
// Check for essential patterns
|
|
291
|
+
const hasCallFolders = content.includes('call */') || content.includes('call*/');
|
|
292
|
+
const hasEnv = content.includes('.env');
|
|
293
|
+
const hasLogs = content.includes('logs/');
|
|
294
|
+
const hasGeminiRuns = content.includes('gemini_runs/');
|
|
295
|
+
const hasNodeModules = content.includes('node_modules');
|
|
296
|
+
|
|
297
|
+
if (hasCallFolders && hasEnv && hasLogs && hasGeminiRuns && hasNodeModules) {
|
|
298
|
+
pass('.gitignore properly configured');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (CHECK_ONLY) {
|
|
304
|
+
warn('.gitignore needs updating', 'Run: node setup.js');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
action('Writing .gitignore (local data excluded from repo)...');
|
|
309
|
+
fs.writeFileSync(GITIGNORE_FILE, GITIGNORE_CONTENT, 'utf8');
|
|
310
|
+
pass('.gitignore configured — call folders, .env, logs, videos excluded');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ─── Step 5: Sample Meeting Folder ───────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
async function setupSampleFolder() {
|
|
316
|
+
header(5, 'Sample Meeting Folder');
|
|
317
|
+
|
|
318
|
+
// Check if any call/meeting folder exists
|
|
319
|
+
const entries = fs.readdirSync(ROOT);
|
|
320
|
+
const hasCallFolder = entries.some(e => {
|
|
321
|
+
const fullPath = path.join(ROOT, e);
|
|
322
|
+
return fs.statSync(fullPath).isDirectory() &&
|
|
323
|
+
(e.startsWith('call') || e === 'sample-call') &&
|
|
324
|
+
!['node_modules', '.git', 'src', 'logs', 'gemini_runs'].includes(e);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (hasCallFolder) {
|
|
328
|
+
pass('Meeting folder already exists');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (CHECK_ONLY) {
|
|
333
|
+
warn('No meeting folder found', 'Create one or run: node setup.js');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const create = await confirm('Create a sample meeting folder with README?');
|
|
338
|
+
if (!create) {
|
|
339
|
+
warn('Skipped — create a meeting folder manually when ready');
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Create sample-call structure
|
|
344
|
+
fs.mkdirSync(SAMPLE_CALL, { recursive: true });
|
|
345
|
+
fs.mkdirSync(path.join(SAMPLE_CALL, '.tasks'), { recursive: true });
|
|
346
|
+
fs.writeFileSync(path.join(SAMPLE_CALL, 'README.md'), SAMPLE_README, 'utf8');
|
|
347
|
+
fs.writeFileSync(path.join(SAMPLE_CALL, '.tasks', 'context.md'),
|
|
348
|
+
'# Project Context\n\nDescribe your project, team, or meeting context here.\nThe AI uses this to better understand what\'s discussed in the recording.\n\n## Examples by Use Case\n\n### Dev Project\n- `src/api/` — REST API endpoints\n- `src/models/` — Database models\n- Sprint: TICKET-123 (auth), TICKET-456 (payments)\n\n### Client Project\n- Deliverable: Phase 1 UI redesign\n- Stakeholders: Jane (PM), Ahmed (Design Lead)\n- Contract scope: 6 screens, responsive, Q1 deadline\n\n### General\n- Meeting purpose: [describe]\n- Key participants: [list]\n- Relevant docs: [reference]\n', 'utf8');
|
|
349
|
+
|
|
350
|
+
pass('sample-call/ created with README and .tasks/ templates');
|
|
351
|
+
printSub('Drop your video file in sample-call/ to get started');
|
|
352
|
+
printSub('See README.md for .tasks/ examples by use case (dev, client, interview, etc.)');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ─── Step 6: Local Branch ────────────────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
async function setupBranch() {
|
|
358
|
+
header(6, 'Working Branch');
|
|
359
|
+
|
|
360
|
+
if (!commandExists('git')) {
|
|
361
|
+
warn('Git not available — skipping branch setup');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check if we're in a git repo
|
|
366
|
+
try {
|
|
367
|
+
execSync('git rev-parse --git-dir', { cwd: ROOT, stdio: 'pipe' });
|
|
368
|
+
} catch {
|
|
369
|
+
warn('Not a git repository — skipping branch setup');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check current branch
|
|
374
|
+
let currentBranch;
|
|
375
|
+
try {
|
|
376
|
+
currentBranch = execSync('git branch --show-current', { cwd: ROOT, stdio: 'pipe' }).toString().trim();
|
|
377
|
+
} catch {
|
|
378
|
+
currentBranch = 'unknown';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (currentBranch.startsWith('local/')) {
|
|
382
|
+
pass(`Already on local branch: ${currentBranch}`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (CHECK_ONLY) {
|
|
387
|
+
warn(`On branch ${currentBranch} — consider creating a local branch`,
|
|
388
|
+
'Run: git checkout -b local/my-workspace');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
print(ICONS.info, `Current branch: ${currentBranch}`);
|
|
393
|
+
printSub('Recommended: create a local branch to separate your data from tool code.');
|
|
394
|
+
printSub('This lets you pull updates from main without conflicts.');
|
|
395
|
+
console.log('');
|
|
396
|
+
|
|
397
|
+
const create = await confirm('Create local/my-workspace branch?');
|
|
398
|
+
if (!create) {
|
|
399
|
+
warn('Skipped — you can create it later: git checkout -b local/my-workspace');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
execSync('git checkout -b local/my-workspace', { cwd: ROOT, stdio: 'pipe' });
|
|
405
|
+
pass('Created and switched to branch: local/my-workspace');
|
|
406
|
+
} catch (err) {
|
|
407
|
+
const msg = err.stderr ? err.stderr.toString().trim() : '';
|
|
408
|
+
if (msg.includes('already exists')) {
|
|
409
|
+
try {
|
|
410
|
+
execSync('git checkout local/my-workspace', { cwd: ROOT, stdio: 'pipe' });
|
|
411
|
+
pass('Switched to existing branch: local/my-workspace');
|
|
412
|
+
} catch {
|
|
413
|
+
warn('Branch local/my-workspace exists but could not switch', 'Run: git checkout local/my-workspace');
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
warn('Could not create branch', msg || 'Run: git checkout -b local/my-workspace');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ─── Step 7: Validation ──────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
function validate() {
|
|
424
|
+
header(7, 'Validation');
|
|
425
|
+
|
|
426
|
+
// Check pipeline loads
|
|
427
|
+
if (!fs.existsSync(NODE_MODULES)) {
|
|
428
|
+
warn('Skipping pipeline validation — no node_modules');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
execSync('node -e "require(\'./src/pipeline\')"', { cwd: ROOT, stdio: 'pipe', timeout: 15000 });
|
|
434
|
+
pass('Pipeline module loads successfully');
|
|
435
|
+
} catch (err) {
|
|
436
|
+
const msg = err.stderr ? err.stderr.toString().split('\n')[0] : '';
|
|
437
|
+
fail('Pipeline failed to load', msg || 'Run: node -e "require(\'./src/pipeline\')" for details');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Check version
|
|
441
|
+
try {
|
|
442
|
+
const version = execSync('node process_and_upload.js --version', { cwd: ROOT, stdio: 'pipe', timeout: 10000 })
|
|
443
|
+
.toString().trim().split('\n').pop();
|
|
444
|
+
pass(`Version: ${version}`);
|
|
445
|
+
} catch {
|
|
446
|
+
warn('Could not read version');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
async function main() {
|
|
453
|
+
console.log('');
|
|
454
|
+
console.log(' ╔══════════════════════════════════════════╗');
|
|
455
|
+
console.log(' ║ Task Summary Extractor — Setup v8.0.0 ║');
|
|
456
|
+
console.log(' ╚══════════════════════════════════════════╝');
|
|
457
|
+
|
|
458
|
+
if (CHECK_ONLY) {
|
|
459
|
+
console.log('\n Mode: Validation only (--check)\n');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Run all steps
|
|
463
|
+
checkPrerequisites();
|
|
464
|
+
await installDependencies();
|
|
465
|
+
await setupEnvironment();
|
|
466
|
+
setupGitignore();
|
|
467
|
+
await setupSampleFolder();
|
|
468
|
+
await setupBranch();
|
|
469
|
+
validate();
|
|
470
|
+
|
|
471
|
+
// ── Summary ──────────────────────────────────────────────────────────
|
|
472
|
+
console.log(`\n${'═'.repeat(60)}`);
|
|
473
|
+
console.log(' Summary');
|
|
474
|
+
console.log(`${'═'.repeat(60)}\n`);
|
|
475
|
+
|
|
476
|
+
console.log(` ${ICONS.ok} Passed: ${counts.pass}`);
|
|
477
|
+
if (counts.warn > 0) console.log(` ${ICONS.warn} Warnings: ${counts.warn}`);
|
|
478
|
+
if (counts.fail > 0) console.log(` ${ICONS.fail} Failed: ${counts.fail}`);
|
|
479
|
+
if (counts.action > 0) console.log(` ${ICONS.gear} Actions taken: ${counts.action}`);
|
|
480
|
+
console.log('');
|
|
481
|
+
|
|
482
|
+
if (counts.fail === 0) {
|
|
483
|
+
console.log(` ${ICONS.rocket} Setup complete! You're ready to go.\n`);
|
|
484
|
+
console.log(' Next steps:');
|
|
485
|
+
console.log(' ──────────');
|
|
486
|
+
console.log(' 1. Drop a video in your call folder');
|
|
487
|
+
console.log(' 2. Run: taskex --name "Your Name" "call 1"');
|
|
488
|
+
console.log(' or: node process_and_upload.js --name "Your Name" "call 1"');
|
|
489
|
+
console.log(' 3. Open: call 1/runs/{timestamp}/results.md');
|
|
490
|
+
console.log('');
|
|
491
|
+
console.log(' Docs: README.md · QUICK_START.md · ARCHITECTURE.md');
|
|
492
|
+
console.log('');
|
|
493
|
+
} else {
|
|
494
|
+
console.log(` ${ICONS.fail} ${counts.fail} issue(s) need fixing.\n`);
|
|
495
|
+
console.log(' Fix the issues above, then re-run:');
|
|
496
|
+
console.log(' node setup.js');
|
|
497
|
+
console.log('');
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
main().catch(err => {
|
|
503
|
+
console.error(`\nSetup failed: ${err.message}`);
|
|
504
|
+
process.exit(1);
|
|
505
|
+
});
|