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.
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Global configuration manager — persistent config stored in ~/.taskexrc
3
+ *
4
+ * Resolution priority (highest wins):
5
+ * 1. CLI flags (--gemini-key, --firebase-key, etc.)
6
+ * 2. process.env (set by user shell or CI)
7
+ * 3. CWD .env file (project-specific)
8
+ * 4. ~/.taskexrc (global persistent config)
9
+ * 5. Package root .env (development fallback)
10
+ *
11
+ * The global config is a JSON file at ~/.taskexrc containing:
12
+ * { "GEMINI_API_KEY": "...", "FIREBASE_API_KEY": "...", ... }
13
+ *
14
+ * Use `taskex config` to interactively set/update keys.
15
+ * Use `taskex config --show` to display current saved config.
16
+ * Use `taskex config --clear` to remove the global config file.
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const os = require('os');
24
+
25
+ // ── Config file path ──────────────────────────────────────────────────────
26
+ const CONFIG_FILE = path.join(os.homedir(), '.taskexrc');
27
+
28
+ // ── Known config keys ─────────────────────────────────────────────────────
29
+ // Maps config key name → description (for interactive prompts)
30
+ const CONFIG_KEYS = {
31
+ GEMINI_API_KEY: { label: 'Gemini API Key', required: true, hint: 'Get one at https://aistudio.google.com/apikey' },
32
+ GEMINI_MODEL: { label: 'Default Gemini Model', required: false, hint: 'e.g. gemini-2.5-flash, gemini-2.5-pro' },
33
+ FIREBASE_API_KEY: { label: 'Firebase API Key', required: false, hint: 'From Firebase Console → Project Settings' },
34
+ FIREBASE_PROJECT_ID: { label: 'Firebase Project ID', required: false, hint: 'e.g. my-project-12345' },
35
+ FIREBASE_STORAGE_BUCKET: { label: 'Firebase Storage Bucket', required: false, hint: 'e.g. my-project-12345.appspot.com' },
36
+ FIREBASE_AUTH_DOMAIN: { label: 'Firebase Auth Domain', required: false, hint: 'e.g. my-project-12345.firebaseapp.com' },
37
+ };
38
+
39
+ // ── Read / Write ──────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Load the global config from ~/.taskexrc.
43
+ * Returns an object of key-value pairs, or {} if file doesn't exist.
44
+ * @returns {Record<string, string>}
45
+ */
46
+ function loadGlobalConfig() {
47
+ try {
48
+ if (!fs.existsSync(CONFIG_FILE)) return {};
49
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
50
+ const parsed = JSON.parse(raw);
51
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return {};
52
+ return parsed;
53
+ } catch (err) {
54
+ // Warn if file exists but is corrupt (not just missing)
55
+ if (fs.existsSync(CONFIG_FILE)) {
56
+ process.stderr.write(` ⚠ Could not parse ${CONFIG_FILE}: ${err.message}\n`);
57
+ process.stderr.write(` Run \`taskex config\` to reconfigure, or delete the file.\n`);
58
+ }
59
+ return {};
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Save the global config to ~/.taskexrc.
65
+ * Merges new values into existing config (doesn't overwrite unrelated keys).
66
+ * @param {Record<string, string>} newValues
67
+ */
68
+ function saveGlobalConfig(newValues) {
69
+ const existing = loadGlobalConfig();
70
+ const merged = { ...existing, ...newValues };
71
+
72
+ // Remove empty/null values
73
+ for (const key of Object.keys(merged)) {
74
+ if (!merged[key]) delete merged[key];
75
+ }
76
+
77
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + '\n', 'utf8');
78
+ // Restrict permissions on config file (contains secrets)
79
+ try {
80
+ if (process.platform !== 'win32') {
81
+ fs.chmodSync(CONFIG_FILE, 0o600);
82
+ }
83
+ } catch {
84
+ // Best-effort — Windows doesn't support chmod
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Delete the global config file.
90
+ * @returns {boolean} true if file was deleted
91
+ */
92
+ function clearGlobalConfig() {
93
+ try {
94
+ if (fs.existsSync(CONFIG_FILE)) {
95
+ fs.unlinkSync(CONFIG_FILE);
96
+ return true;
97
+ }
98
+ return false;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ // ── Inject into process.env ───────────────────────────────────────────────
105
+
106
+ /**
107
+ * Load global config and inject any values into process.env that aren't
108
+ * already set. This respects the priority chain: env vars / CLI flags
109
+ * that are already set take precedence over global config.
110
+ *
111
+ * Call this BEFORE dotenv loads (so it's lower priority than .env too —
112
+ * actually call it AFTER dotenv, but only inject if still missing).
113
+ *
114
+ * @returns {string[]} List of keys that were injected from global config
115
+ */
116
+ function injectGlobalConfig() {
117
+ const config = loadGlobalConfig();
118
+ const injected = [];
119
+
120
+ for (const [key, value] of Object.entries(config)) {
121
+ if (value && (!process.env[key] || process.env[key] === '')) {
122
+ process.env[key] = value;
123
+ injected.push(key);
124
+ }
125
+ }
126
+
127
+ return injected;
128
+ }
129
+
130
+ // ── Interactive setup ─────────────────────────────────────────────────────
131
+
132
+ /**
133
+ * Mask a secret for display — shows first 4 + last 4 chars.
134
+ * @param {string} value
135
+ * @returns {string}
136
+ */
137
+ function maskSecret(value) {
138
+ if (!value || value.length < 12) return '****';
139
+ return value.slice(0, 4) + '...' + value.slice(-4);
140
+ }
141
+
142
+ /**
143
+ * Interactive config setup — prompts for each key and saves to ~/.taskexrc.
144
+ * Shows current values (masked) and lets user update or keep existing.
145
+ *
146
+ * @param {object} [options]
147
+ * @param {boolean} [options.showOnly=false] Just display, don't prompt
148
+ * @param {boolean} [options.clear=false] Delete config file
149
+ * @param {boolean} [options.onlyMissing=false] Only prompt for keys not yet set anywhere
150
+ * @returns {Promise<void>}
151
+ */
152
+ async function interactiveSetup({ showOnly = false, clear = false, onlyMissing = false } = {}) {
153
+ const readline = require('readline');
154
+
155
+ if (clear) {
156
+ const removed = clearGlobalConfig();
157
+ if (removed) {
158
+ console.log('\n ✓ Global config cleared (~/.taskexrc deleted)\n');
159
+ } else {
160
+ console.log('\n No global config to clear.\n');
161
+ }
162
+ return;
163
+ }
164
+
165
+ const existing = loadGlobalConfig();
166
+
167
+ if (showOnly) {
168
+ console.log('');
169
+ console.log(' Global Config (~/.taskexrc)');
170
+ console.log(' ─────────────────────────────');
171
+
172
+ const keys = Object.keys(CONFIG_KEYS);
173
+ let hasAny = false;
174
+ for (const key of keys) {
175
+ const val = existing[key] || process.env[key];
176
+ const source = existing[key] ? 'saved' : process.env[key] ? 'env' : '';
177
+ if (val) {
178
+ const display = key.includes('KEY') || key.includes('SECRET') ? maskSecret(val) : val;
179
+ console.log(` ${CONFIG_KEYS[key].label}: ${display} (${source})`);
180
+ hasAny = true;
181
+ }
182
+ }
183
+ if (!hasAny) {
184
+ console.log(' (empty — run `taskex config` to set up)');
185
+ }
186
+ console.log(`\n Config file: ${CONFIG_FILE}\n`);
187
+ return;
188
+ }
189
+
190
+ // Interactive prompts
191
+ console.log('');
192
+ console.log(' ┌──────────────────────────────────────────────────────────┐');
193
+ console.log(' │ 🔧 taskex — Global Configuration │');
194
+ console.log(' └──────────────────────────────────────────────────────────┘');
195
+ console.log('');
196
+ console.log(` Config file: ${CONFIG_FILE}`);
197
+ console.log(' Values saved here are used whenever .env or CLI flags don\'t set them.');
198
+ console.log(' Press Enter to keep the current value, or type a new one.');
199
+ console.log('');
200
+
201
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
202
+ const ask = (question) => new Promise(resolve => {
203
+ rl.question(question, answer => resolve((answer || '').trim()));
204
+ });
205
+
206
+ const updates = {};
207
+
208
+ try {
209
+ for (const [key, meta] of Object.entries(CONFIG_KEYS)) {
210
+ const current = existing[key] || process.env[key] || '';
211
+
212
+ // If onlyMissing mode, skip keys that are already set
213
+ if (onlyMissing && current) continue;
214
+
215
+ const displayCurrent = current
216
+ ? (key.includes('KEY') || key.includes('SECRET') ? maskSecret(current) : current)
217
+ : '(not set)';
218
+
219
+ const reqTag = meta.required ? ' *required*' : '';
220
+ console.log(` ${meta.label}${reqTag}`);
221
+ if (meta.hint) console.log(` ${meta.hint}`);
222
+ console.log(` Current: ${displayCurrent}`);
223
+
224
+ const answer = await ask(' New value (Enter to keep): ');
225
+ if (answer) {
226
+ updates[key] = answer;
227
+ }
228
+ console.log('');
229
+ }
230
+ } finally {
231
+ rl.close();
232
+ }
233
+
234
+ if (Object.keys(updates).length > 0) {
235
+ saveGlobalConfig(updates);
236
+ console.log(` ✓ Saved ${Object.keys(updates).length} value(s) to ${CONFIG_FILE}`);
237
+ console.log('');
238
+
239
+ // Also inject into current process so the pipeline can proceed
240
+ for (const [k, v] of Object.entries(updates)) {
241
+ process.env[k] = v;
242
+ }
243
+ } else {
244
+ console.log(' No changes made.');
245
+ console.log('');
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Quick prompt for a single missing required key.
251
+ * Used during first-run when GEMINI_API_KEY is missing after all resolution.
252
+ *
253
+ * @param {string} key - The config key (e.g. 'GEMINI_API_KEY')
254
+ * @returns {Promise<string|null>} The value entered, or null if skipped
255
+ */
256
+ async function promptForKey(key) {
257
+ const meta = CONFIG_KEYS[key];
258
+ if (!meta) return null;
259
+
260
+ const readline = require('readline');
261
+
262
+ console.log('');
263
+ console.log(` ⚠ ${meta.label} is not configured.`);
264
+ if (meta.hint) console.log(` ${meta.hint}`);
265
+ console.log('');
266
+
267
+ let value;
268
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
269
+ try {
270
+ value = await new Promise(resolve => {
271
+ rl.question(` Enter ${meta.label}: `, answer => resolve((answer || '').trim()));
272
+ });
273
+ } finally {
274
+ rl.close();
275
+ }
276
+
277
+ if (!value) return null;
278
+
279
+ // Ask if they want to save globally
280
+ let save;
281
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
282
+ try {
283
+ save = await new Promise(resolve => {
284
+ rl2.question(' Save to global config for future use? (Y/n): ', answer => {
285
+ const a = (answer || '').trim().toLowerCase();
286
+ resolve(a === '' || a === 'y' || a === 'yes');
287
+ });
288
+ });
289
+ } finally {
290
+ rl2.close();
291
+ }
292
+
293
+ if (save) {
294
+ saveGlobalConfig({ [key]: value });
295
+ console.log(` ✓ Saved to ${CONFIG_FILE}`);
296
+ }
297
+
298
+ // Inject into current process
299
+ process.env[key] = value;
300
+ return value;
301
+ }
302
+
303
+ // ── Exports ───────────────────────────────────────────────────────────────
304
+
305
+ module.exports = {
306
+ CONFIG_FILE,
307
+ CONFIG_KEYS,
308
+ loadGlobalConfig,
309
+ saveGlobalConfig,
310
+ clearGlobalConfig,
311
+ injectGlobalConfig,
312
+ interactiveSetup,
313
+ promptForKey,
314
+ maskSecret,
315
+ };
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Pipeline Health Dashboard — generates a comprehensive quality report
3
+ * after all processing is complete.
4
+ *
5
+ * Reports on:
6
+ * - Parse success rates
7
+ * - Quality scores per segment
8
+ * - Extraction density (items per segment)
9
+ * - Data coverage analysis
10
+ * - Retry statistics
11
+ * - Token efficiency metrics
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ // ======================== HEALTH REPORT ========================
17
+
18
+ /**
19
+ * Build a comprehensive health report from pipeline execution data.
20
+ *
21
+ * @param {object} params
22
+ * @param {Array} params.segmentReports - Array of { segmentName, qualityReport, retried, retryImproved }
23
+ * @param {Array} params.allSegmentAnalyses - All final segment analyses
24
+ * @param {object} params.costSummary - From CostTracker.getSummary()
25
+ * @param {object} [params.compilationQuality] - Quality report for the compilation step
26
+ * @param {number} params.totalDurationMs - Wall-clock duration of pipeline
27
+ * @returns {object} Health report
28
+ */
29
+ function buildHealthReport(params) {
30
+ const {
31
+ segmentReports = [],
32
+ allSegmentAnalyses = [],
33
+ costSummary = {},
34
+ compilationQuality = null,
35
+ totalDurationMs = 0,
36
+ } = params;
37
+
38
+ // Parse success rate
39
+ const totalSegments = segmentReports.length;
40
+ const parsed = segmentReports.filter(r => r.qualityReport?.grade !== 'FAIL').length;
41
+ const parseRate = totalSegments > 0 ? (parsed / totalSegments * 100).toFixed(1) : 0;
42
+
43
+ // Quality score distribution
44
+ const scores = segmentReports.map(r => r.qualityReport?.score || 0);
45
+ const avgScore = scores.length > 0 ? (scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(1) : 0;
46
+ const minScore = scores.length > 0 ? Math.min(...scores) : 0;
47
+ const maxScore = scores.length > 0 ? Math.max(...scores) : 0;
48
+
49
+ // Grade distribution
50
+ const grades = { PASS: 0, WARN: 0, FAIL: 0 };
51
+ for (const r of segmentReports) {
52
+ const g = r.qualityReport?.grade || 'FAIL';
53
+ grades[g] = (grades[g] || 0) + 1;
54
+ }
55
+
56
+ // Extraction density
57
+ let totalTickets = 0, totalCrs = 0, totalActions = 0, totalBlockers = 0, totalScopes = 0;
58
+ const perSegment = [];
59
+
60
+ for (const analysis of allSegmentAnalyses) {
61
+ const tickets = analysis.tickets?.length || 0;
62
+ const crs = analysis.change_requests?.length || 0;
63
+ const actions = analysis.action_items?.length || 0;
64
+ const blockers = analysis.blockers?.length || 0;
65
+ const scopes = analysis.scope_changes?.length || 0;
66
+
67
+ totalTickets += tickets;
68
+ totalCrs += crs;
69
+ totalActions += actions;
70
+ totalBlockers += blockers;
71
+ totalScopes += scopes;
72
+
73
+ perSegment.push({ tickets, crs, actions, blockers, scopes });
74
+ }
75
+
76
+ // Retry stats
77
+ const retriedCount = segmentReports.filter(r => r.retried).length;
78
+ const retryImprovedCount = segmentReports.filter(r => r.retryImproved).length;
79
+
80
+ // Token efficiency
81
+ const tokensPerItem = costSummary.totalTokens && (totalTickets + totalCrs + totalActions) > 0
82
+ ? Math.round(costSummary.totalTokens / (totalTickets + totalCrs + totalActions))
83
+ : 0;
84
+
85
+ // All issues across segments
86
+ const allIssues = [];
87
+ for (const r of segmentReports) {
88
+ for (const issue of (r.qualityReport?.issues || [])) {
89
+ allIssues.push({ segment: r.segmentName, issue });
90
+ }
91
+ }
92
+
93
+ return {
94
+ summary: {
95
+ totalSegments,
96
+ parseSuccessRate: parseFloat(parseRate),
97
+ avgQualityScore: parseFloat(avgScore),
98
+ minQualityScore: minScore,
99
+ maxQualityScore: maxScore,
100
+ grades,
101
+ },
102
+ extraction: {
103
+ totalTickets,
104
+ totalChangeRequests: totalCrs,
105
+ totalActionItems: totalActions,
106
+ totalBlockers,
107
+ totalScopeChanges: totalScopes,
108
+ totalItems: totalTickets + totalCrs + totalActions + totalBlockers + totalScopes,
109
+ perSegment,
110
+ },
111
+ retry: {
112
+ segmentsRetried: retriedCount,
113
+ retriesImproved: retryImprovedCount,
114
+ },
115
+ efficiency: {
116
+ tokensPerExtractedItem: tokensPerItem,
117
+ totalTokens: costSummary.totalTokens || 0,
118
+ totalCost: costSummary.totalCost || 0,
119
+ aiTimeMs: costSummary.totalDurationMs || 0,
120
+ wallClockMs: totalDurationMs,
121
+ },
122
+ compilation: compilationQuality ? {
123
+ score: compilationQuality.score,
124
+ grade: compilationQuality.grade,
125
+ issues: compilationQuality.issues,
126
+ } : null,
127
+ issues: allIssues,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Print the health dashboard to console.
133
+ * @param {object} report - From buildHealthReport
134
+ */
135
+ function printHealthDashboard(report) {
136
+ const { summary: s, extraction: e, retry: r, efficiency: eff, compilation: c } = report;
137
+
138
+ console.log('');
139
+ console.log('╔══════════════════════════════════════════════╗');
140
+ console.log('║ PIPELINE HEALTH DASHBOARD ║');
141
+ console.log('╚══════════════════════════════════════════════╝');
142
+ console.log('');
143
+
144
+ // Quality overview
145
+ console.log(' Quality Scores:');
146
+ console.log(` Average : ${s.avgQualityScore}/100`);
147
+ console.log(` Range : ${s.minQualityScore}–${s.maxQualityScore}`);
148
+ console.log(` Grades : ✓ ${s.grades.PASS} PASS | ⚠ ${s.grades.WARN} WARN | ✗ ${s.grades.FAIL} FAIL`);
149
+ console.log(` Parse : ${s.parseSuccessRate}% success rate`);
150
+ console.log('');
151
+
152
+ // Extraction density
153
+ console.log(' Extraction Summary:');
154
+ console.log(` Tickets : ${e.totalTickets}`);
155
+ console.log(` Change Requests : ${e.totalChangeRequests}`);
156
+ console.log(` Action Items : ${e.totalActionItems}`);
157
+ console.log(` Blockers : ${e.totalBlockers}`);
158
+ console.log(` Scope Changes : ${e.totalScopeChanges}`);
159
+ console.log(` Total items : ${e.totalItems} across ${s.totalSegments} segment(s)`);
160
+ console.log('');
161
+
162
+ // Per-segment density bars
163
+ if (e.perSegment.length > 0) {
164
+ console.log(' Per-Segment Density:');
165
+ e.perSegment.forEach((seg, i) => {
166
+ const total = seg.tickets + seg.crs + seg.actions + seg.blockers + seg.scopes;
167
+ const bar = '█'.repeat(Math.min(total, 30)) + (total > 30 ? '…' : '');
168
+ console.log(` Seg ${i + 1}: ${bar} (${total} items)`);
169
+ });
170
+ console.log('');
171
+ }
172
+
173
+ // Retry stats
174
+ if (r.segmentsRetried > 0) {
175
+ console.log(' Retry Statistics:');
176
+ console.log(` Segments retried : ${r.segmentsRetried}`);
177
+ console.log(` Retries improved : ${r.retriesImproved}`);
178
+ console.log('');
179
+ }
180
+
181
+ // Efficiency
182
+ console.log(' Efficiency:');
183
+ console.log(` Tokens/item : ${eff.tokensPerExtractedItem.toLocaleString()}`);
184
+ console.log(` AI time : ${(eff.aiTimeMs / 1000).toFixed(1)}s`);
185
+ console.log(` Wall clock : ${(eff.wallClockMs / 1000).toFixed(1)}s`);
186
+ console.log(` Cost : $${eff.totalCost.toFixed(4)}`);
187
+
188
+ // Compilation quality
189
+ if (c) {
190
+ console.log('');
191
+ console.log(' Compilation:');
192
+ console.log(` Score : ${c.score}/100 (${c.grade})`);
193
+ if (c.issues.length > 0) {
194
+ console.log(` Issues: ${c.issues.length}`);
195
+ c.issues.slice(0, 3).forEach(issue => console.log(` • ${issue}`));
196
+ if (c.issues.length > 3) console.log(` ... +${c.issues.length - 3} more`);
197
+ }
198
+ }
199
+
200
+ // Top issues
201
+ const criticalIssues = report.issues.filter(i =>
202
+ i.issue.includes('FAIL') || i.issue.includes('Missing required') || i.issue.includes('parse failed')
203
+ );
204
+ if (criticalIssues.length > 0) {
205
+ console.log('');
206
+ console.log(' ⚠ Critical Issues:');
207
+ criticalIssues.slice(0, 5).forEach(i => console.log(` ${i.segment}: ${i.issue}`));
208
+ }
209
+
210
+ console.log('');
211
+ }
212
+
213
+ module.exports = {
214
+ buildHealthReport,
215
+ printHealthDashboard,
216
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * CLI config flag injection — maps --flag values to process.env before
3
+ * any module touches config.js / dotenv.
4
+ *
5
+ * Shared between bin/taskex.js and process_and_upload.js to avoid
6
+ * duplicated flag-parsing logic.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /** Map of CLI flag names → environment variable names */
12
+ const CONFIG_FLAG_MAP = {
13
+ 'gemini-key': 'GEMINI_API_KEY',
14
+ 'firebase-key': 'FIREBASE_API_KEY',
15
+ 'firebase-project': 'FIREBASE_PROJECT_ID',
16
+ 'firebase-bucket': 'FIREBASE_STORAGE_BUCKET',
17
+ 'firebase-domain': 'FIREBASE_AUTH_DOMAIN',
18
+ };
19
+
20
+ /**
21
+ * Scan process.argv for config flags and inject their values into process.env.
22
+ * Must be called BEFORE any require() that touches config.js / dotenv.
23
+ *
24
+ * Supports both `--key value` and `--key=value` forms.
25
+ *
26
+ * @param {string[]} [argv] - Arguments (defaults to process.argv.slice(2))
27
+ * @returns {string[]} List of env var names that were injected
28
+ */
29
+ function injectCliFlags(argv) {
30
+ if (!argv) argv = process.argv.slice(2);
31
+ const injected = [];
32
+
33
+ for (let i = 0; i < argv.length; i++) {
34
+ if (!argv[i].startsWith('--')) continue;
35
+
36
+ const eqIdx = argv[i].indexOf('=');
37
+ let key, val;
38
+
39
+ if (eqIdx !== -1) {
40
+ key = argv[i].slice(2, eqIdx);
41
+ val = argv[i].slice(eqIdx + 1);
42
+ } else {
43
+ key = argv[i].slice(2);
44
+ if (CONFIG_FLAG_MAP[key] && i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
45
+ val = argv[i + 1];
46
+ }
47
+ }
48
+
49
+ if (key && val && CONFIG_FLAG_MAP[key]) {
50
+ process.env[CONFIG_FLAG_MAP[key]] = val;
51
+ injected.push(CONFIG_FLAG_MAP[key]);
52
+ }
53
+ }
54
+
55
+ return injected;
56
+ }
57
+
58
+ module.exports = { injectCliFlags, CONFIG_FLAG_MAP };