@cccarv82/freya 3.1.0 → 3.2.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/cli/web-ui.js +9 -0
- package/cli/web.js +62 -1
- package/package.json +1 -1
package/cli/web-ui.js
CHANGED
|
@@ -2498,6 +2498,15 @@
|
|
|
2498
2498
|
msg += 'Contexto registrado no log diário. Nenhuma tarefa ou blocker identificado.\n';
|
|
2499
2499
|
}
|
|
2500
2500
|
|
|
2501
|
+
// Show semantic duplicates detected
|
|
2502
|
+
if (summary && Array.isArray(summary.semanticDups) && summary.semanticDups.length > 0) {
|
|
2503
|
+
msg += '\n⚠️ **Duplicatas detectadas** (não criadas):\n';
|
|
2504
|
+
for (var di = 0; di < summary.semanticDups.length; di++) {
|
|
2505
|
+
var dup = summary.semanticDups[di];
|
|
2506
|
+
msg += '- "' + dup.newDesc + '" → já existe: "' + dup.existingDesc + '" (' + dup.similarity + ' similar)\n';
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2501
2510
|
if (summary && Array.isArray(summary.reportsSuggested) && summary.reportsSuggested.length) {
|
|
2502
2511
|
msg += '\n**Relatórios sugeridos:** ' + summary.reportsSuggested.join(', ');
|
|
2503
2512
|
msg += '\n\nUse: **Rodar relatórios sugeridos** (barra lateral)';
|
package/cli/web.js
CHANGED
|
@@ -3491,6 +3491,49 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3491
3491
|
const insertTask = dl.db.prepare(`INSERT INTO tasks (id, project_slug, description, category, status, metadata) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
3492
3492
|
const insertBlocker = dl.db.prepare(`INSERT INTO blockers (id, project_slug, title, severity, status, owner, next_action, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
3493
3493
|
|
|
3494
|
+
// ── Semantic deduplication (async, runs BEFORE the sync transaction) ──
|
|
3495
|
+
// Load pending tasks for cosine similarity comparison
|
|
3496
|
+
const pendingTasks = dl.db.prepare("SELECT id, description, project_slug FROM tasks WHERE status = 'PENDING'").all();
|
|
3497
|
+
let pendingEmbeddings = []; // [{ id, description, project_slug, vector }]
|
|
3498
|
+
try {
|
|
3499
|
+
const { defaultEmbedder } = require(path.join(workspaceDir, 'scripts', 'lib', 'Embedder.js'));
|
|
3500
|
+
for (const pt of pendingTasks) {
|
|
3501
|
+
try {
|
|
3502
|
+
const vec = await defaultEmbedder.embedText(pt.description);
|
|
3503
|
+
pendingEmbeddings.push({ id: pt.id, description: pt.description, project_slug: pt.project_slug, vector: vec });
|
|
3504
|
+
} catch { /* skip if embedding fails for individual task */ }
|
|
3505
|
+
}
|
|
3506
|
+
} catch (embErr) {
|
|
3507
|
+
// Embedder not available — fall back to exact-match only
|
|
3508
|
+
console.error('[dedup] Semantic dedup unavailable:', embErr.message);
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
// Pre-compute semantic duplicates for each action
|
|
3512
|
+
const semanticDupMap = new Map(); // action index → existing task id (if duplicate)
|
|
3513
|
+
const SIMILARITY_THRESHOLD = 0.78;
|
|
3514
|
+
if (pendingEmbeddings.length > 0) {
|
|
3515
|
+
try {
|
|
3516
|
+
const { defaultEmbedder } = require(path.join(workspaceDir, 'scripts', 'lib', 'Embedder.js'));
|
|
3517
|
+
for (let ai = 0; ai < actions.length; ai++) {
|
|
3518
|
+
const a = actions[ai];
|
|
3519
|
+
if (!a || a.type !== 'create_task') continue;
|
|
3520
|
+
const desc = normalizeWhitespace(a.description);
|
|
3521
|
+
if (!desc) continue;
|
|
3522
|
+
try {
|
|
3523
|
+
const newVec = await defaultEmbedder.embedText(desc);
|
|
3524
|
+
let bestScore = 0, bestMatch = null;
|
|
3525
|
+
for (const pe of pendingEmbeddings) {
|
|
3526
|
+
const score = defaultEmbedder.cosineSimilarity(newVec, pe.vector);
|
|
3527
|
+
if (score > bestScore) { bestScore = score; bestMatch = pe; }
|
|
3528
|
+
}
|
|
3529
|
+
if (bestScore >= SIMILARITY_THRESHOLD && bestMatch) {
|
|
3530
|
+
semanticDupMap.set(ai, { existingId: bestMatch.id, existingDesc: bestMatch.description, score: bestScore });
|
|
3531
|
+
}
|
|
3532
|
+
} catch { /* skip */ }
|
|
3533
|
+
}
|
|
3534
|
+
} catch { /* embedder not available */ }
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3494
3537
|
// BUG-31: Move deduplication queries INSIDE the transaction to eliminate TOCTOU race
|
|
3495
3538
|
const applyTx = dl.db.transaction((actionsToApply) => {
|
|
3496
3539
|
// Query for existing keys inside the transaction for atomicity
|
|
@@ -3499,7 +3542,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3499
3542
|
const recentBlockers = dl.db.prepare("SELECT title FROM blockers WHERE created_at >= datetime('now', '-1 day')").all();
|
|
3500
3543
|
const existingBlockerKeys24h = new Set(recentBlockers.map(b => sha1(normalizeTextForKey(b.title))));
|
|
3501
3544
|
|
|
3502
|
-
for (
|
|
3545
|
+
for (let ai = 0; ai < actionsToApply.length; ai++) {
|
|
3546
|
+
const a = actionsToApply[ai];
|
|
3503
3547
|
if (!a || typeof a !== 'object') continue;
|
|
3504
3548
|
const type = String(a.type || '').trim();
|
|
3505
3549
|
|
|
@@ -3509,8 +3553,25 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3509
3553
|
if (!description) continue;
|
|
3510
3554
|
const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
|
|
3511
3555
|
const streamSlug = String(a.streamSlug || '').trim();
|
|
3556
|
+
|
|
3557
|
+
// Exact-match dedup (24h window)
|
|
3512
3558
|
const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
|
|
3513
3559
|
if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
|
|
3560
|
+
|
|
3561
|
+
// Semantic dedup (all pending tasks)
|
|
3562
|
+
if (semanticDupMap.has(ai)) {
|
|
3563
|
+
const dup = semanticDupMap.get(ai);
|
|
3564
|
+
applied.tasksSkipped++;
|
|
3565
|
+
if (!applied.semanticDups) applied.semanticDups = [];
|
|
3566
|
+
applied.semanticDups.push({
|
|
3567
|
+
newDesc: description,
|
|
3568
|
+
existingId: dup.existingId,
|
|
3569
|
+
existingDesc: dup.existingDesc,
|
|
3570
|
+
similarity: Math.round(dup.score * 100) + '%'
|
|
3571
|
+
});
|
|
3572
|
+
continue;
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3514
3575
|
const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
|
|
3515
3576
|
const priority = normPriority(a.priority);
|
|
3516
3577
|
|
package/package.json
CHANGED