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
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Mode — AI-powered document generation from video + documents + user request.
|
|
3
|
+
*
|
|
4
|
+
* This mode automatically detects and processes ALL content in a folder:
|
|
5
|
+
* - Video files: compressed, segmented, analyzed for content summaries
|
|
6
|
+
* - Documents: loaded as text context
|
|
7
|
+
*
|
|
8
|
+
* The pipeline then:
|
|
9
|
+
* 1. Discovers all videos and documents
|
|
10
|
+
* 2. Compresses and segments videos, analyzes each segment
|
|
11
|
+
* 3. Sends video summaries + docs + request to Gemini for topic planning
|
|
12
|
+
* 4. Generates a set of Markdown documents — one per topic
|
|
13
|
+
*
|
|
14
|
+
* Works with any type of video (mp4, mkv, avi, mov, webm) and any documents.
|
|
15
|
+
*
|
|
16
|
+
* Fully backward-compatible — works with docs-only folders too.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const config = require('../config');
|
|
24
|
+
// Access config.GEMINI_MODEL at call time (not destructured) for runtime model changes.
|
|
25
|
+
const { extractJson } = require('./json-parser');
|
|
26
|
+
const { withRetry } = require('./retry');
|
|
27
|
+
|
|
28
|
+
// ======================== TOPIC PLANNING ========================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ask Gemini to plan a set of documents based on video + docs + user request.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} ai - GoogleGenAI instance
|
|
34
|
+
* @param {string} userRequest - What the user wants generated
|
|
35
|
+
* @param {string[]} docSnippets - Content from context documents
|
|
36
|
+
* @param {object} options
|
|
37
|
+
* @param {string} [options.folderName] - Name of the source folder
|
|
38
|
+
* @param {string} [options.userName] - User's name
|
|
39
|
+
* @param {number} [options.thinkingBudget] - Thinking tokens
|
|
40
|
+
* @param {Array} [options.videoSummaries] - Video segment summaries from analyzeVideoForContext
|
|
41
|
+
* @returns {Promise<{topics: Array, raw: string, durationMs: number, tokenUsage: object}>}
|
|
42
|
+
*/
|
|
43
|
+
async function planTopics(ai, userRequest, docSnippets, options = {}) {
|
|
44
|
+
const { folderName = 'project', userName = '', thinkingBudget = 16384, videoSummaries = [] } = options;
|
|
45
|
+
|
|
46
|
+
// Build video context section
|
|
47
|
+
const videoSection = videoSummaries.length > 0
|
|
48
|
+
? `\n\nVIDEO CONTENT ANALYZED (${videoSummaries.length} segment${videoSummaries.length > 1 ? 's' : ''}):\n` +
|
|
49
|
+
videoSummaries.map((vs, i) => {
|
|
50
|
+
const label = vs.totalSegments > 1
|
|
51
|
+
? `--- Video: ${vs.videoFile} — Segment ${vs.segmentIndex + 1}/${vs.totalSegments} ---`
|
|
52
|
+
: `--- Video: ${vs.videoFile} ---`;
|
|
53
|
+
return `${label}\n${vs.summary}`;
|
|
54
|
+
}).join('\n\n')
|
|
55
|
+
: '';
|
|
56
|
+
|
|
57
|
+
const docsSection = docSnippets.length > 0
|
|
58
|
+
? `\n\nCONTEXT DOCUMENTS PROVIDED:\n${docSnippets.join('\n\n---\n\n')}`
|
|
59
|
+
: '';
|
|
60
|
+
|
|
61
|
+
const hasVideo = videoSummaries.length > 0;
|
|
62
|
+
const hasDocs = docSnippets.length > 0;
|
|
63
|
+
const sourceDescription = hasVideo && hasDocs
|
|
64
|
+
? '(Video recordings + context documents provided — use BOTH as source material)'
|
|
65
|
+
: hasVideo
|
|
66
|
+
? '(Video recordings provided — use the video content as your primary source material)'
|
|
67
|
+
: hasDocs
|
|
68
|
+
? '(Context documents provided — use them as source material)'
|
|
69
|
+
: '(No context provided — generate based on the request alone)';
|
|
70
|
+
|
|
71
|
+
const prompt = `You are an expert knowledge architect and technical writer. A user has a request and optionally provided context — which may include video recordings, documents, or both. Your job is to plan a set of Markdown documents that fully address their request.
|
|
72
|
+
|
|
73
|
+
USER REQUEST:
|
|
74
|
+
"${userRequest}"
|
|
75
|
+
|
|
76
|
+
SOURCE FOLDER: "${folderName}"
|
|
77
|
+
${userName ? `USER: "${userName}"` : ''}
|
|
78
|
+
${sourceDescription}
|
|
79
|
+
${videoSection}
|
|
80
|
+
${docsSection}
|
|
81
|
+
|
|
82
|
+
YOUR TASK:
|
|
83
|
+
Plan 3-15 standalone Markdown documents that together comprehensively address the user's request. Each document should focus on ONE aspect/topic.
|
|
84
|
+
|
|
85
|
+
DOCUMENT CATEGORIES (use these exact names):
|
|
86
|
+
- "overview" — High-level summaries, executive briefs, introductions
|
|
87
|
+
- "guide" — How-to guides, step-by-step instructions, tutorials
|
|
88
|
+
- "analysis" — Analysis documents, comparisons, evaluations, assessments
|
|
89
|
+
- "plan" — Plans, roadmaps, timelines, strategies, proposals
|
|
90
|
+
- "reference" — Reference material, specifications, API docs, schemas
|
|
91
|
+
- "concept" — Concept explanations, definitions, theory, background
|
|
92
|
+
- "decision" — Decision records, options analysis, trade-off evaluations
|
|
93
|
+
- "checklist" — Checklists, verification lists, audit documents
|
|
94
|
+
- "template" — Templates, scaffolds, reusable patterns
|
|
95
|
+
- "report" — Status reports, summaries, findings
|
|
96
|
+
|
|
97
|
+
RULES:
|
|
98
|
+
1. Plan 3-15 documents. More for complex requests, fewer for simple ones.
|
|
99
|
+
2. Each document should be substantial (200-1000+ words depending on complexity).
|
|
100
|
+
3. Documents should be self-contained but reference each other where relevant.
|
|
101
|
+
4. First document should always be an overview/index of the entire set.
|
|
102
|
+
5. Order by logical reading sequence — overview first, then foundational, then detailed.
|
|
103
|
+
6. Each topic should have clear value — don't pad with trivial docs.
|
|
104
|
+
7. Use ALL provided context (video content + documents) to ground your planning in reality.
|
|
105
|
+
8. If video recordings are provided, extract insights, discussions, decisions, and details from them.
|
|
106
|
+
9. If the request is about learning/teaching, include progressive complexity.
|
|
107
|
+
10. If the request is about planning/migration, include risk analysis and timelines.
|
|
108
|
+
11. Be creative but practical — generate what would actually help someone.
|
|
109
|
+
|
|
110
|
+
RESPOND WITH ONLY VALID JSON — no markdown fences, no extra text:
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
"topics": [
|
|
114
|
+
{
|
|
115
|
+
"id": "DM-01",
|
|
116
|
+
"title": "Clear document title",
|
|
117
|
+
"category": "overview|guide|analysis|plan|reference|concept|decision|checklist|template|report",
|
|
118
|
+
"description": "2-3 sentence description of what this document covers",
|
|
119
|
+
"target_audience": "Who this document is for",
|
|
120
|
+
"estimated_length": "short|medium|long",
|
|
121
|
+
"depends_on": []
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"project_summary": "One-line summary of the document set's purpose"
|
|
125
|
+
}`;
|
|
126
|
+
|
|
127
|
+
const requestPayload = {
|
|
128
|
+
model: config.GEMINI_MODEL,
|
|
129
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
130
|
+
config: {
|
|
131
|
+
systemInstruction: 'You are a knowledge architect. Plan a comprehensive set of documents based on the user\'s request and provided context. Respond with valid JSON only.',
|
|
132
|
+
maxOutputTokens: 16384,
|
|
133
|
+
temperature: 0.3,
|
|
134
|
+
thinkingConfig: { thinkingBudget },
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const t0 = Date.now();
|
|
139
|
+
const response = await withRetry(
|
|
140
|
+
() => ai.models.generateContent(requestPayload),
|
|
141
|
+
{ label: 'Dynamic mode topic planning', maxRetries: 2, baseDelay: 3000 }
|
|
142
|
+
);
|
|
143
|
+
const durationMs = Date.now() - t0;
|
|
144
|
+
const rawText = response.text;
|
|
145
|
+
|
|
146
|
+
const parsed = extractJson(rawText);
|
|
147
|
+
const topics = parsed?.topics || [];
|
|
148
|
+
const projectSummary = parsed?.project_summary || '';
|
|
149
|
+
|
|
150
|
+
const usage = response.usageMetadata || {};
|
|
151
|
+
const tokenUsage = {
|
|
152
|
+
inputTokens: usage.promptTokenCount || 0,
|
|
153
|
+
outputTokens: usage.candidatesTokenCount || 0,
|
|
154
|
+
totalTokens: usage.totalTokenCount || 0,
|
|
155
|
+
thoughtTokens: usage.thoughtsTokenCount || 0,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return { topics, projectSummary, raw: rawText, durationMs, tokenUsage };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ======================== DOCUMENT GENERATION ========================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate a single document for a planned topic.
|
|
165
|
+
*
|
|
166
|
+
* @param {object} ai - GoogleGenAI instance
|
|
167
|
+
* @param {object} topic - Topic from planTopics
|
|
168
|
+
* @param {string} userRequest - Original user request
|
|
169
|
+
* @param {string[]} docSnippets - Context document content
|
|
170
|
+
* @param {object} options
|
|
171
|
+
* @param {Array} [options.videoSummaries] - Video segment summaries
|
|
172
|
+
* @returns {Promise<{markdown: string, raw: string, durationMs: number, tokenUsage: object}>}
|
|
173
|
+
*/
|
|
174
|
+
async function generateDynamicDocument(ai, topic, userRequest, docSnippets, options = {}) {
|
|
175
|
+
const { folderName = 'project', userName = '', thinkingBudget = 16384, allTopics = [], videoSummaries = [] } = options;
|
|
176
|
+
|
|
177
|
+
// Build the list of related documents for cross-references
|
|
178
|
+
const otherDocs = allTopics
|
|
179
|
+
.filter(t => t.id !== topic.id)
|
|
180
|
+
.map(t => `- ${t.id}: ${t.title} (${t.category})`)
|
|
181
|
+
.join('\n');
|
|
182
|
+
|
|
183
|
+
const contextSection = docSnippets.length > 0
|
|
184
|
+
? `\nCONTEXT DOCUMENTS:\n${docSnippets.slice(0, 5).join('\n---\n')}`
|
|
185
|
+
: '';
|
|
186
|
+
|
|
187
|
+
// Build video context for this document
|
|
188
|
+
const videoContextSection = videoSummaries.length > 0
|
|
189
|
+
? `\nVIDEO CONTENT (from ${videoSummaries.length} analyzed segment${videoSummaries.length > 1 ? 's' : ''}):\n` +
|
|
190
|
+
videoSummaries.map((vs, i) => {
|
|
191
|
+
const label = vs.totalSegments > 1
|
|
192
|
+
? `--- ${vs.videoFile} — Segment ${vs.segmentIndex + 1}/${vs.totalSegments} ---`
|
|
193
|
+
: `--- ${vs.videoFile} ---`;
|
|
194
|
+
// Truncate very long summaries per segment to manage token budget
|
|
195
|
+
const summary = vs.summary.length > 6000
|
|
196
|
+
? vs.summary.slice(0, 6000) + '\n... (truncated for token budget)'
|
|
197
|
+
: vs.summary;
|
|
198
|
+
return `${label}\n${summary}`;
|
|
199
|
+
}).join('\n\n')
|
|
200
|
+
: '';
|
|
201
|
+
|
|
202
|
+
const categoryGuidance = getDynamicCategoryGuidance(topic.category);
|
|
203
|
+
|
|
204
|
+
// Adaptive max tokens based on estimated length
|
|
205
|
+
const maxOutputTokens = topic.estimated_length === 'long' ? 16384
|
|
206
|
+
: topic.estimated_length === 'medium' ? 8192
|
|
207
|
+
: 4096;
|
|
208
|
+
|
|
209
|
+
const prompt = `You are an expert technical writer creating a document as part of a comprehensive document set.
|
|
210
|
+
|
|
211
|
+
USER'S ORIGINAL REQUEST:
|
|
212
|
+
"${userRequest}"
|
|
213
|
+
|
|
214
|
+
DOCUMENT TO WRITE:
|
|
215
|
+
- ID: ${topic.id}
|
|
216
|
+
- Title: "${topic.title}"
|
|
217
|
+
- Category: ${topic.category}
|
|
218
|
+
- Description: ${topic.description}
|
|
219
|
+
- Target Audience: ${topic.target_audience || 'General'}
|
|
220
|
+
|
|
221
|
+
OTHER DOCUMENTS IN THE SET (for cross-references):
|
|
222
|
+
${otherDocs || '(This is the only document)'}
|
|
223
|
+
${videoContextSection}
|
|
224
|
+
${contextSection}
|
|
225
|
+
|
|
226
|
+
${categoryGuidance}
|
|
227
|
+
|
|
228
|
+
WRITING RULES:
|
|
229
|
+
1. Write in clear, professional Markdown.
|
|
230
|
+
2. Use headers (##, ###), bullet points, tables, code blocks, and diagrams where helpful.
|
|
231
|
+
3. Target ${topic.estimated_length === 'long' ? '800-1500' : topic.estimated_length === 'medium' ? '400-800' : '200-400'} words.
|
|
232
|
+
4. Write for the specified target audience — adjust technical depth accordingly.
|
|
233
|
+
5. Reference other documents in the set using their titles where relevant (e.g., "See [Document Title]").
|
|
234
|
+
6. Ground content in ALL provided context — video recordings AND documents when available.
|
|
235
|
+
7. If video content is provided, use specific details, quotes, and decisions from the video.
|
|
236
|
+
8. Be practical and actionable — include concrete examples, steps, or recommendations.
|
|
237
|
+
9. DO NOT include YAML frontmatter or metadata blocks.
|
|
238
|
+
10. Start with a level-1 heading (# Title) followed by a brief introduction.
|
|
239
|
+
11. Include a "Summary" or "Key Takeaways" section at the end for longer docs.
|
|
240
|
+
|
|
241
|
+
START YOUR RESPONSE DIRECTLY WITH THE MARKDOWN CONTENT (no fences, no preamble):`;
|
|
242
|
+
|
|
243
|
+
const requestPayload = {
|
|
244
|
+
model: config.GEMINI_MODEL,
|
|
245
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
246
|
+
config: {
|
|
247
|
+
systemInstruction: 'You are a technical writer creating comprehensive documentation. Write clear, well-structured Markdown that directly addresses the request. Start directly with the content.',
|
|
248
|
+
maxOutputTokens,
|
|
249
|
+
temperature: 0.4,
|
|
250
|
+
thinkingConfig: { thinkingBudget },
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const t0 = Date.now();
|
|
255
|
+
const response = await withRetry(
|
|
256
|
+
() => ai.models.generateContent(requestPayload),
|
|
257
|
+
{ label: `Dynamic doc: ${topic.title}`, maxRetries: 2, baseDelay: 3000 }
|
|
258
|
+
);
|
|
259
|
+
const durationMs = Date.now() - t0;
|
|
260
|
+
const rawText = response.text;
|
|
261
|
+
|
|
262
|
+
// Clean up markdown fences if model wrapped output
|
|
263
|
+
let markdown = rawText.trim();
|
|
264
|
+
if (markdown.startsWith('```markdown')) {
|
|
265
|
+
markdown = markdown.replace(/^```markdown\s*\n?/, '').replace(/\n?```\s*$/, '');
|
|
266
|
+
} else if (markdown.startsWith('```md')) {
|
|
267
|
+
markdown = markdown.replace(/^```md\s*\n?/, '').replace(/\n?```\s*$/, '');
|
|
268
|
+
} else if (markdown.startsWith('```')) {
|
|
269
|
+
markdown = markdown.replace(/^```\s*\n?/, '').replace(/\n?```\s*$/, '');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const usage = response.usageMetadata || {};
|
|
273
|
+
const tokenUsage = {
|
|
274
|
+
inputTokens: usage.promptTokenCount || 0,
|
|
275
|
+
outputTokens: usage.candidatesTokenCount || 0,
|
|
276
|
+
totalTokens: usage.totalTokenCount || 0,
|
|
277
|
+
thoughtTokens: usage.thoughtsTokenCount || 0,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return { markdown, raw: rawText, durationMs, tokenUsage };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ======================== BATCH GENERATION ========================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Generate all planned documents in parallel batches.
|
|
287
|
+
*/
|
|
288
|
+
async function generateAllDynamicDocuments(ai, topics, userRequest, docSnippets, options = {}) {
|
|
289
|
+
const { concurrency = 2, onProgress, ...docOptions } = options;
|
|
290
|
+
|
|
291
|
+
const results = [];
|
|
292
|
+
const queue = [...topics];
|
|
293
|
+
let completed = 0;
|
|
294
|
+
|
|
295
|
+
while (queue.length > 0) {
|
|
296
|
+
const batch = queue.splice(0, concurrency);
|
|
297
|
+
const batchResults = await Promise.allSettled(
|
|
298
|
+
batch.map(topic =>
|
|
299
|
+
generateDynamicDocument(ai, topic, userRequest, docSnippets, { ...docOptions, allTopics: topics })
|
|
300
|
+
.then(result => {
|
|
301
|
+
completed++;
|
|
302
|
+
if (onProgress) onProgress(completed, topics.length, topic);
|
|
303
|
+
return { topic, ...result };
|
|
304
|
+
})
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < batchResults.length; i++) {
|
|
309
|
+
const r = batchResults[i];
|
|
310
|
+
if (r.status === 'fulfilled') {
|
|
311
|
+
results.push(r.value);
|
|
312
|
+
} else {
|
|
313
|
+
completed++;
|
|
314
|
+
if (onProgress) onProgress(completed, topics.length, batch[i]);
|
|
315
|
+
results.push({
|
|
316
|
+
topic: batch[i],
|
|
317
|
+
markdown: null,
|
|
318
|
+
raw: null,
|
|
319
|
+
durationMs: 0,
|
|
320
|
+
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, thoughtTokens: 0 },
|
|
321
|
+
error: r.reason?.message || 'Unknown error',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return results;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ======================== OUTPUT ========================
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Write all dynamic documents to disk with an index.
|
|
334
|
+
*
|
|
335
|
+
* @param {string} outputDir - Directory to write to
|
|
336
|
+
* @param {Array} documents - Results from generateAllDynamicDocuments
|
|
337
|
+
* @param {object} meta - Metadata
|
|
338
|
+
* @returns {{ indexPath: string, docPaths: string[], stats: object }}
|
|
339
|
+
*/
|
|
340
|
+
function writeDynamicOutput(outputDir, documents, meta = {}) {
|
|
341
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
342
|
+
|
|
343
|
+
const docPaths = [];
|
|
344
|
+
const successful = documents.filter(d => d.markdown);
|
|
345
|
+
const failed = documents.filter(d => !d.markdown);
|
|
346
|
+
|
|
347
|
+
// Write individual documents
|
|
348
|
+
for (const doc of successful) {
|
|
349
|
+
const slug = slugify(doc.topic.title);
|
|
350
|
+
const fileName = `${doc.topic.id.toLowerCase()}-${slug}.md`;
|
|
351
|
+
const filePath = path.join(outputDir, fileName);
|
|
352
|
+
fs.writeFileSync(filePath, doc.markdown, 'utf8');
|
|
353
|
+
docPaths.push(filePath);
|
|
354
|
+
doc._fileName = fileName;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Build index
|
|
358
|
+
const indexLines = [
|
|
359
|
+
`# ${meta.projectSummary || meta.userRequest || 'Generated Documents'}`,
|
|
360
|
+
'',
|
|
361
|
+
`> Generated from: **${meta.folderName || 'project'}**`,
|
|
362
|
+
`> Request: *"${meta.userRequest || ''}"*`,
|
|
363
|
+
`> Date: ${meta.timestamp || new Date().toISOString()}`,
|
|
364
|
+
`> Documents: ${successful.length}`,
|
|
365
|
+
'',
|
|
366
|
+
'---',
|
|
367
|
+
'',
|
|
368
|
+
'## Document Index',
|
|
369
|
+
'',
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
// Group by category
|
|
373
|
+
const categories = {};
|
|
374
|
+
for (const doc of successful) {
|
|
375
|
+
const cat = doc.topic.category || 'other';
|
|
376
|
+
if (!categories[cat]) categories[cat] = [];
|
|
377
|
+
categories[cat].push(doc);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const categoryLabels = {
|
|
381
|
+
'overview': 'Overview',
|
|
382
|
+
'guide': 'Guides & How-To',
|
|
383
|
+
'analysis': 'Analysis & Evaluation',
|
|
384
|
+
'plan': 'Plans & Strategy',
|
|
385
|
+
'reference': 'Reference Material',
|
|
386
|
+
'concept': 'Concepts & Theory',
|
|
387
|
+
'decision': 'Decisions & Trade-offs',
|
|
388
|
+
'checklist': 'Checklists & Verification',
|
|
389
|
+
'template': 'Templates & Patterns',
|
|
390
|
+
'report': 'Reports & Findings',
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
for (const [cat, docs] of Object.entries(categories)) {
|
|
394
|
+
indexLines.push(`### ${categoryLabels[cat] || cat}`);
|
|
395
|
+
indexLines.push('');
|
|
396
|
+
for (const doc of docs) {
|
|
397
|
+
const audience = doc.topic.target_audience ? ` *(${doc.topic.target_audience})*` : '';
|
|
398
|
+
indexLines.push(`- **[${doc.topic.title}](${doc._fileName})**${audience}`);
|
|
399
|
+
indexLines.push(` ${doc.topic.description}`);
|
|
400
|
+
}
|
|
401
|
+
indexLines.push('');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Stats
|
|
405
|
+
const totalTokens = documents.reduce((s, d) => s + (d.tokenUsage?.totalTokens || 0), 0);
|
|
406
|
+
const totalDuration = documents.reduce((s, d) => s + (d.durationMs || 0), 0);
|
|
407
|
+
|
|
408
|
+
indexLines.push('---');
|
|
409
|
+
indexLines.push('');
|
|
410
|
+
indexLines.push(`*${successful.length} documents generated | ${totalTokens.toLocaleString()} tokens | ${(totalDuration / 1000).toFixed(1)}s*`);
|
|
411
|
+
|
|
412
|
+
if (failed.length > 0) {
|
|
413
|
+
indexLines.push('');
|
|
414
|
+
indexLines.push(`> ⚠ ${failed.length} document(s) failed to generate:`);
|
|
415
|
+
for (const doc of failed) {
|
|
416
|
+
indexLines.push(`> - ${doc.topic.title}: ${doc.error}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const indexPath = path.join(outputDir, 'INDEX.md');
|
|
421
|
+
fs.writeFileSync(indexPath, indexLines.join('\n'), 'utf8');
|
|
422
|
+
docPaths.unshift(indexPath);
|
|
423
|
+
|
|
424
|
+
// Write metadata JSON
|
|
425
|
+
const metaPath = path.join(outputDir, 'dynamic-run.json');
|
|
426
|
+
fs.writeFileSync(metaPath, JSON.stringify({
|
|
427
|
+
timestamp: meta.timestamp,
|
|
428
|
+
folderName: meta.folderName,
|
|
429
|
+
userRequest: meta.userRequest,
|
|
430
|
+
projectSummary: meta.projectSummary,
|
|
431
|
+
topicCount: successful.length,
|
|
432
|
+
failedCount: failed.length,
|
|
433
|
+
totalTokens,
|
|
434
|
+
totalDurationMs: totalDuration,
|
|
435
|
+
topics: documents.map(d => ({
|
|
436
|
+
id: d.topic.id,
|
|
437
|
+
title: d.topic.title,
|
|
438
|
+
category: d.topic.category,
|
|
439
|
+
fileName: d._fileName || null,
|
|
440
|
+
success: !!d.markdown,
|
|
441
|
+
error: d.error || null,
|
|
442
|
+
tokens: d.tokenUsage?.totalTokens || 0,
|
|
443
|
+
durationMs: d.durationMs,
|
|
444
|
+
})),
|
|
445
|
+
}, null, 2), 'utf8');
|
|
446
|
+
docPaths.push(metaPath);
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
indexPath,
|
|
450
|
+
docPaths,
|
|
451
|
+
stats: {
|
|
452
|
+
total: documents.length,
|
|
453
|
+
successful: successful.length,
|
|
454
|
+
failed: failed.length,
|
|
455
|
+
totalTokens,
|
|
456
|
+
totalDurationMs: totalDuration,
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ======================== HELPERS ========================
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get category-specific writing guidance for dynamic mode.
|
|
465
|
+
*/
|
|
466
|
+
function getDynamicCategoryGuidance(category) {
|
|
467
|
+
const guides = {
|
|
468
|
+
'overview': `CATEGORY GUIDANCE — OVERVIEW:
|
|
469
|
+
Write a high-level overview that serves as an introduction and navigation aid.
|
|
470
|
+
- Summarize the entire scope of the document set
|
|
471
|
+
- Explain the "why" — why this document set exists
|
|
472
|
+
- Provide a reading order recommendation
|
|
473
|
+
- Keep it concise but comprehensive`,
|
|
474
|
+
|
|
475
|
+
'guide': `CATEGORY GUIDANCE — GUIDE:
|
|
476
|
+
Write a practical, hands-on guide with clear steps.
|
|
477
|
+
- Use numbered steps for sequential processes
|
|
478
|
+
- Include prerequisites at the top
|
|
479
|
+
- Add code examples, commands, or configuration snippets
|
|
480
|
+
- Include "common pitfalls" or "troubleshooting" sections
|
|
481
|
+
- Make steps testable/verifiable`,
|
|
482
|
+
|
|
483
|
+
'analysis': `CATEGORY GUIDANCE — ANALYSIS:
|
|
484
|
+
Write an analytical document with evidence-based reasoning.
|
|
485
|
+
- Use comparison tables for alternatives
|
|
486
|
+
- Include pros/cons or SWOT where relevant
|
|
487
|
+
- Support claims with data from context docs
|
|
488
|
+
- Include risk assessments where appropriate
|
|
489
|
+
- End with clear conclusions or recommendations`,
|
|
490
|
+
|
|
491
|
+
'plan': `CATEGORY GUIDANCE — PLAN:
|
|
492
|
+
Write an actionable plan with clear milestones.
|
|
493
|
+
- Include timeline or phases
|
|
494
|
+
- Define owners/responsibilities where possible
|
|
495
|
+
- List dependencies between steps
|
|
496
|
+
- Include risk mitigation strategies
|
|
497
|
+
- Add success criteria or KPIs`,
|
|
498
|
+
|
|
499
|
+
'reference': `CATEGORY GUIDANCE — REFERENCE:
|
|
500
|
+
Write clear, well-structured reference material.
|
|
501
|
+
- Use tables extensively for structured data
|
|
502
|
+
- Include examples for each concept
|
|
503
|
+
- Organize alphabetically or by logical grouping
|
|
504
|
+
- Make it scannable with clear headings
|
|
505
|
+
- Include cross-references to related docs`,
|
|
506
|
+
|
|
507
|
+
'concept': `CATEGORY GUIDANCE — CONCEPT EXPLANATION:
|
|
508
|
+
Write a clear educational explanation.
|
|
509
|
+
- Start with "what it is" for newcomers
|
|
510
|
+
- Explain "why it matters" in context
|
|
511
|
+
- Use analogies to make complex ideas accessible
|
|
512
|
+
- Include diagrams (as described text) if helpful
|
|
513
|
+
- Progress from simple to advanced`,
|
|
514
|
+
|
|
515
|
+
'decision': `CATEGORY GUIDANCE — DECISION RECORD:
|
|
516
|
+
Write an Architecture/Engineering Decision Record.
|
|
517
|
+
- "Context" — what situation requires a decision
|
|
518
|
+
- "Options" — what alternatives exist (with pros/cons)
|
|
519
|
+
- "Decision" — what was chosen and why
|
|
520
|
+
- "Consequences" — what this means going forward
|
|
521
|
+
- "Review Date" — when to reassess (if applicable)`,
|
|
522
|
+
|
|
523
|
+
'checklist': `CATEGORY GUIDANCE — CHECKLIST:
|
|
524
|
+
Write an actionable checklist with clear verification criteria.
|
|
525
|
+
- Use checkbox syntax (- [ ]) for items
|
|
526
|
+
- Group items by phase or category
|
|
527
|
+
- Include "done when" criteria for each item
|
|
528
|
+
- Add notes for non-obvious items
|
|
529
|
+
- Keep items concise and actionable`,
|
|
530
|
+
|
|
531
|
+
'template': `CATEGORY GUIDANCE — TEMPLATE:
|
|
532
|
+
Create a reusable template with clear structure.
|
|
533
|
+
- Include placeholder text showing expected content
|
|
534
|
+
- Add instructions/comments explaining each section
|
|
535
|
+
- Make it copy-paste ready
|
|
536
|
+
- Include examples of filled-out sections
|
|
537
|
+
- Keep it flexible but structured`,
|
|
538
|
+
|
|
539
|
+
'report': `CATEGORY GUIDANCE — REPORT:
|
|
540
|
+
Write a clear findings/status report.
|
|
541
|
+
- Start with executive summary
|
|
542
|
+
- Use data and metrics where available
|
|
543
|
+
- Include visualizations as text tables
|
|
544
|
+
- Separate observations from recommendations
|
|
545
|
+
- End with clear next steps`,
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
return guides[category] || 'Write a clear, well-structured, professional document.';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Convert title to URL-safe slug.
|
|
553
|
+
*/
|
|
554
|
+
function slugify(text) {
|
|
555
|
+
return text
|
|
556
|
+
.toLowerCase()
|
|
557
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
558
|
+
.replace(/^-+|-+$/g, '')
|
|
559
|
+
.slice(0, 60);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
module.exports = {
|
|
563
|
+
planTopics,
|
|
564
|
+
generateDynamicDocument,
|
|
565
|
+
generateAllDynamicDocuments,
|
|
566
|
+
writeDynamicOutput,
|
|
567
|
+
};
|