@harness-engineering/cli 1.9.0 → 1.11.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/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +7 -2
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +10 -1
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +2 -2
- package/dist/agents/skills/claude-code/harness-parallel-agents/SKILL.md +105 -20
- package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +37 -0
- package/dist/agents/skills/gemini-cli/enforce-architecture/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +7 -2
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +10 -1
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +2 -2
- package/dist/agents/skills/gemini-cli/harness-parallel-agents/SKILL.md +105 -20
- package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +37 -0
- package/dist/agents-md-ZFV6RR5J.js +8 -0
- package/dist/architecture-EXNUMH5R.js +13 -0
- package/dist/bin/harness-mcp.d.ts +1 -0
- package/dist/bin/harness-mcp.js +28 -0
- package/dist/bin/harness.js +42 -8
- package/dist/check-phase-gate-VZFOY2PO.js +12 -0
- package/dist/chunk-2NCIKJES.js +470 -0
- package/dist/chunk-2YPZKGAG.js +62 -0
- package/dist/{chunk-CGSHUJES.js → chunk-2YSQOUHO.js} +4484 -2688
- package/dist/chunk-3WGJMBKH.js +45 -0
- package/dist/{chunk-ULSRSP53.js → chunk-6N4R6FVX.js} +11 -112
- package/dist/{chunk-6JIT7CEM.js → chunk-72GHBOL2.js} +1 -1
- package/dist/chunk-BM3PWGXQ.js +14 -0
- package/dist/chunk-C2ERUR3L.js +255 -0
- package/dist/chunk-EBJQ6N4M.js +39 -0
- package/dist/chunk-GNGELAXY.js +293 -0
- package/dist/chunk-GSIVNYVJ.js +187 -0
- package/dist/chunk-HD4IBGLA.js +80 -0
- package/dist/chunk-I6JZYEGT.js +4361 -0
- package/dist/chunk-IDZNPTYD.js +16 -0
- package/dist/chunk-JSTQ3AWB.js +31 -0
- package/dist/chunk-K6XAPGML.js +27 -0
- package/dist/chunk-KET4QQZB.js +8 -0
- package/dist/chunk-L2KLU56K.js +125 -0
- package/dist/chunk-MHBMTPW7.js +29 -0
- package/dist/chunk-NC6PXVWT.js +116 -0
- package/dist/chunk-NKDM3FMH.js +52 -0
- package/dist/chunk-PA2XHK75.js +248 -0
- package/dist/chunk-Q6AB7W5Z.js +135 -0
- package/dist/chunk-QPEH2QPG.js +347 -0
- package/dist/chunk-TEFCFC4H.js +15 -0
- package/dist/chunk-TI4TGEX6.js +85 -0
- package/dist/chunk-TRAPF4IX.js +185 -0
- package/dist/chunk-VRFZWGMS.js +68 -0
- package/dist/chunk-VUCPTQ6G.js +67 -0
- package/dist/chunk-W6Y7ZW3Y.js +13 -0
- package/dist/chunk-WJZDO6OY.js +103 -0
- package/dist/chunk-WUJTCNOU.js +122 -0
- package/dist/chunk-X3MN5UQJ.js +89 -0
- package/dist/chunk-Z75JC6I2.js +189 -0
- package/dist/chunk-ZOAWBDWU.js +72 -0
- package/dist/{chunk-RTPHUDZS.js → chunk-ZWC3MN5E.js} +1944 -2779
- package/dist/ci-workflow-K5RCRNYR.js +8 -0
- package/dist/constants-5JGUXPEK.js +6 -0
- package/dist/create-skill-WPXHSLX2.js +11 -0
- package/dist/dist-D4RYGUZE.js +14 -0
- package/dist/{dist-C5PYIQPF.js → dist-JVZ2MKBC.js} +108 -6
- package/dist/dist-L7LAAQAS.js +18 -0
- package/dist/{dist-I7DB5VKB.js → dist-M6BQODWC.js} +1145 -0
- package/dist/docs-PWCUVYWU.js +12 -0
- package/dist/engine-6XUP6GAK.js +8 -0
- package/dist/entropy-4I6JEYAC.js +12 -0
- package/dist/feedback-TNIW534S.js +18 -0
- package/dist/generate-agent-definitions-MWKEA5NU.js +15 -0
- package/dist/glob-helper-5OHBUQAI.js +52 -0
- package/dist/graph-loader-KO4GJ5N2.js +8 -0
- package/dist/index.d.ts +328 -12
- package/dist/index.js +93 -34
- package/dist/loader-4FIPIFII.js +10 -0
- package/dist/mcp-MOKLYNZL.js +34 -0
- package/dist/performance-BTOJCPXU.js +24 -0
- package/dist/review-pipeline-3YTW3463.js +9 -0
- package/dist/runner-VMYLHWOC.js +6 -0
- package/dist/runtime-GO7K2PJE.js +9 -0
- package/dist/security-4P2GGFF6.js +9 -0
- package/dist/skill-executor-RG45LUO5.js +8 -0
- package/dist/templates/orchestrator/WORKFLOW.md +48 -0
- package/dist/templates/orchestrator/template.json +6 -0
- package/dist/validate-JN44D2Q7.js +12 -0
- package/dist/validate-cross-check-DB7RIFFF.js +8 -0
- package/dist/version-KFFPOQAX.js +6 -0
- package/package.json +13 -7
- package/dist/create-skill-UZOHMXRU.js +0 -8
- package/dist/validate-cross-check-VG573VZO.js +0 -7
|
@@ -467,6 +467,36 @@ function project(nodes, spec) {
|
|
|
467
467
|
return projected;
|
|
468
468
|
});
|
|
469
469
|
}
|
|
470
|
+
var TEST_TYPES = /* @__PURE__ */ new Set(["test_result"]);
|
|
471
|
+
var DOC_TYPES = /* @__PURE__ */ new Set(["adr", "decision", "document", "learning"]);
|
|
472
|
+
var CODE_TYPES = /* @__PURE__ */ new Set([
|
|
473
|
+
"file",
|
|
474
|
+
"module",
|
|
475
|
+
"class",
|
|
476
|
+
"interface",
|
|
477
|
+
"function",
|
|
478
|
+
"method",
|
|
479
|
+
"variable"
|
|
480
|
+
]);
|
|
481
|
+
function groupNodesByImpact(nodes, excludeId) {
|
|
482
|
+
const tests = [];
|
|
483
|
+
const docs = [];
|
|
484
|
+
const code = [];
|
|
485
|
+
const other = [];
|
|
486
|
+
for (const node of nodes) {
|
|
487
|
+
if (excludeId && node.id === excludeId) continue;
|
|
488
|
+
if (TEST_TYPES.has(node.type)) {
|
|
489
|
+
tests.push(node);
|
|
490
|
+
} else if (DOC_TYPES.has(node.type)) {
|
|
491
|
+
docs.push(node);
|
|
492
|
+
} else if (CODE_TYPES.has(node.type)) {
|
|
493
|
+
code.push(node);
|
|
494
|
+
} else {
|
|
495
|
+
other.push(node);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return { tests, docs, code, other };
|
|
499
|
+
}
|
|
470
500
|
var CodeIngestor = class {
|
|
471
501
|
constructor(store) {
|
|
472
502
|
this.store = store;
|
|
@@ -2400,6 +2430,675 @@ var GraphAnomalyAdapter = class {
|
|
|
2400
2430
|
return { components, dependentCount };
|
|
2401
2431
|
}
|
|
2402
2432
|
};
|
|
2433
|
+
var INTENTS = ["impact", "find", "relationships", "explain", "anomaly"];
|
|
2434
|
+
var SIGNAL_WEIGHTS = {
|
|
2435
|
+
keyword: 0.35,
|
|
2436
|
+
questionWord: 0.2,
|
|
2437
|
+
verbPattern: 0.45
|
|
2438
|
+
};
|
|
2439
|
+
var INTENT_SIGNALS = {
|
|
2440
|
+
impact: {
|
|
2441
|
+
keywords: [
|
|
2442
|
+
"break",
|
|
2443
|
+
"affect",
|
|
2444
|
+
"impact",
|
|
2445
|
+
"change",
|
|
2446
|
+
"depend",
|
|
2447
|
+
"blast",
|
|
2448
|
+
"radius",
|
|
2449
|
+
"risk",
|
|
2450
|
+
"delete",
|
|
2451
|
+
"remove"
|
|
2452
|
+
],
|
|
2453
|
+
questionWords: ["what", "if"],
|
|
2454
|
+
verbPatterns: [
|
|
2455
|
+
/what\s+(breaks|happens|is affected)/,
|
|
2456
|
+
/if\s+i\s+(change|modify|remove|delete)/,
|
|
2457
|
+
/blast\s+radius/,
|
|
2458
|
+
/what\s+(depend|relies)/
|
|
2459
|
+
]
|
|
2460
|
+
},
|
|
2461
|
+
find: {
|
|
2462
|
+
keywords: ["find", "where", "locate", "search", "list", "all", "every"],
|
|
2463
|
+
questionWords: ["where"],
|
|
2464
|
+
verbPatterns: [
|
|
2465
|
+
/where\s+is/,
|
|
2466
|
+
/find\s+(the|all|every)/,
|
|
2467
|
+
/show\s+me/,
|
|
2468
|
+
/show\s+(all|every|the)/,
|
|
2469
|
+
/locate\s+/,
|
|
2470
|
+
/list\s+(all|every|the)/
|
|
2471
|
+
]
|
|
2472
|
+
},
|
|
2473
|
+
relationships: {
|
|
2474
|
+
keywords: [
|
|
2475
|
+
"connect",
|
|
2476
|
+
"call",
|
|
2477
|
+
"import",
|
|
2478
|
+
"use",
|
|
2479
|
+
"depend",
|
|
2480
|
+
"link",
|
|
2481
|
+
"neighbor",
|
|
2482
|
+
"caller",
|
|
2483
|
+
"callee"
|
|
2484
|
+
],
|
|
2485
|
+
questionWords: ["what", "who"],
|
|
2486
|
+
verbPatterns: [/connects?\s+to/, /depends?\s+on/, /\bcalls?\b/, /\bimports?\b/]
|
|
2487
|
+
},
|
|
2488
|
+
explain: {
|
|
2489
|
+
keywords: ["describe", "explain", "tell", "about", "overview", "summary", "work"],
|
|
2490
|
+
questionWords: ["what", "how"],
|
|
2491
|
+
verbPatterns: [
|
|
2492
|
+
/what\s+is\s+\w/,
|
|
2493
|
+
/describe\s+/,
|
|
2494
|
+
/tell\s+me\s+about/,
|
|
2495
|
+
/how\s+does/,
|
|
2496
|
+
/overview\s+of/,
|
|
2497
|
+
/give\s+me\s+/
|
|
2498
|
+
]
|
|
2499
|
+
},
|
|
2500
|
+
anomaly: {
|
|
2501
|
+
keywords: [
|
|
2502
|
+
"wrong",
|
|
2503
|
+
"problem",
|
|
2504
|
+
"anomaly",
|
|
2505
|
+
"smell",
|
|
2506
|
+
"issue",
|
|
2507
|
+
"outlier",
|
|
2508
|
+
"hotspot",
|
|
2509
|
+
"suspicious",
|
|
2510
|
+
"risk"
|
|
2511
|
+
],
|
|
2512
|
+
questionWords: ["what"],
|
|
2513
|
+
verbPatterns: [
|
|
2514
|
+
/what.*(wrong|problem|smell)/,
|
|
2515
|
+
/find.*(issue|anomal|problem)/,
|
|
2516
|
+
/code\s+smell/,
|
|
2517
|
+
/suspicious/,
|
|
2518
|
+
/hotspot/
|
|
2519
|
+
]
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
var IntentClassifier = class {
|
|
2523
|
+
/**
|
|
2524
|
+
* Classify a natural language question into an intent.
|
|
2525
|
+
*
|
|
2526
|
+
* @param question - The natural language question to classify
|
|
2527
|
+
* @returns ClassificationResult with intent, confidence, and per-signal scores
|
|
2528
|
+
*/
|
|
2529
|
+
classify(question) {
|
|
2530
|
+
const normalized = question.toLowerCase().trim();
|
|
2531
|
+
const scores = [];
|
|
2532
|
+
for (const intent of INTENTS) {
|
|
2533
|
+
const signals = this.scoreIntent(normalized, INTENT_SIGNALS[intent]);
|
|
2534
|
+
const confidence = this.combineSignals(signals);
|
|
2535
|
+
scores.push({ intent, confidence, signals });
|
|
2536
|
+
}
|
|
2537
|
+
scores.sort((a, b) => b.confidence - a.confidence);
|
|
2538
|
+
const best = scores[0];
|
|
2539
|
+
return {
|
|
2540
|
+
intent: best.intent,
|
|
2541
|
+
confidence: best.confidence,
|
|
2542
|
+
signals: best.signals
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Score individual signals for an intent against the normalized query.
|
|
2547
|
+
*/
|
|
2548
|
+
scoreIntent(normalized, signalSet) {
|
|
2549
|
+
return {
|
|
2550
|
+
keyword: this.scoreKeywords(normalized, signalSet.keywords),
|
|
2551
|
+
questionWord: this.scoreQuestionWord(normalized, signalSet.questionWords),
|
|
2552
|
+
verbPattern: this.scoreVerbPatterns(normalized, signalSet.verbPatterns)
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Score keyword signal: uses word-stem matching (checks if any word in the
|
|
2557
|
+
* query starts with the keyword). Saturates at 2 matches to avoid penalizing
|
|
2558
|
+
* intents with many keywords when only a few appear in the query.
|
|
2559
|
+
*/
|
|
2560
|
+
scoreKeywords(normalized, keywords) {
|
|
2561
|
+
if (keywords.length === 0) return 0;
|
|
2562
|
+
const words = normalized.split(/\s+/);
|
|
2563
|
+
let matched = 0;
|
|
2564
|
+
for (const keyword of keywords) {
|
|
2565
|
+
if (words.some((w) => w.startsWith(keyword))) {
|
|
2566
|
+
matched++;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
return Math.min(matched / 2, 1);
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Score question-word signal: 1.0 if the query starts with a matching
|
|
2573
|
+
* question word, 0 otherwise.
|
|
2574
|
+
*/
|
|
2575
|
+
scoreQuestionWord(normalized, questionWords) {
|
|
2576
|
+
const firstWord = normalized.split(/\s+/)[0] ?? "";
|
|
2577
|
+
return questionWords.includes(firstWord) ? 1 : 0;
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Score verb-pattern signal: any matching pattern yields a strong score.
|
|
2581
|
+
* Multiple matches increase score but saturate quickly.
|
|
2582
|
+
*/
|
|
2583
|
+
scoreVerbPatterns(normalized, patterns) {
|
|
2584
|
+
if (patterns.length === 0) return 0;
|
|
2585
|
+
let matched = 0;
|
|
2586
|
+
for (const pattern of patterns) {
|
|
2587
|
+
if (pattern.test(normalized)) {
|
|
2588
|
+
matched++;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
return matched === 0 ? 0 : Math.min(0.6 + matched * 0.2, 1);
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Combine individual signal scores into a single confidence score
|
|
2595
|
+
* using additive weighted scoring. Each signal contributes weight * score,
|
|
2596
|
+
* and the total weights sum to 1.0 so the result is naturally bounded [0, 1].
|
|
2597
|
+
*/
|
|
2598
|
+
combineSignals(signals) {
|
|
2599
|
+
let total = 0;
|
|
2600
|
+
for (const key of Object.keys(signals)) {
|
|
2601
|
+
const weight = SIGNAL_WEIGHTS[key];
|
|
2602
|
+
total += signals[key] * weight;
|
|
2603
|
+
}
|
|
2604
|
+
return total;
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
var INTENT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2608
|
+
// impact
|
|
2609
|
+
"break",
|
|
2610
|
+
"breaks",
|
|
2611
|
+
"affect",
|
|
2612
|
+
"affects",
|
|
2613
|
+
"affected",
|
|
2614
|
+
"impact",
|
|
2615
|
+
"change",
|
|
2616
|
+
"depend",
|
|
2617
|
+
"depends",
|
|
2618
|
+
"blast",
|
|
2619
|
+
"radius",
|
|
2620
|
+
"risk",
|
|
2621
|
+
"delete",
|
|
2622
|
+
"remove",
|
|
2623
|
+
"modify",
|
|
2624
|
+
"happens",
|
|
2625
|
+
// find
|
|
2626
|
+
"find",
|
|
2627
|
+
"where",
|
|
2628
|
+
"locate",
|
|
2629
|
+
"search",
|
|
2630
|
+
"list",
|
|
2631
|
+
"all",
|
|
2632
|
+
"every",
|
|
2633
|
+
"show",
|
|
2634
|
+
// relationships
|
|
2635
|
+
"connect",
|
|
2636
|
+
"connects",
|
|
2637
|
+
"call",
|
|
2638
|
+
"calls",
|
|
2639
|
+
"import",
|
|
2640
|
+
"imports",
|
|
2641
|
+
"use",
|
|
2642
|
+
"uses",
|
|
2643
|
+
"link",
|
|
2644
|
+
"neighbor",
|
|
2645
|
+
"caller",
|
|
2646
|
+
"callers",
|
|
2647
|
+
"callee",
|
|
2648
|
+
"callees",
|
|
2649
|
+
// explain
|
|
2650
|
+
"describe",
|
|
2651
|
+
"explain",
|
|
2652
|
+
"tell",
|
|
2653
|
+
"about",
|
|
2654
|
+
"overview",
|
|
2655
|
+
"summary",
|
|
2656
|
+
"work",
|
|
2657
|
+
"works",
|
|
2658
|
+
// anomaly
|
|
2659
|
+
"wrong",
|
|
2660
|
+
"problem",
|
|
2661
|
+
"problems",
|
|
2662
|
+
"anomaly",
|
|
2663
|
+
"anomalies",
|
|
2664
|
+
"smell",
|
|
2665
|
+
"smells",
|
|
2666
|
+
"issue",
|
|
2667
|
+
"issues",
|
|
2668
|
+
"outlier",
|
|
2669
|
+
"hotspot",
|
|
2670
|
+
"hotspots",
|
|
2671
|
+
"suspicious"
|
|
2672
|
+
]);
|
|
2673
|
+
var STOP_WORDS2 = /* @__PURE__ */ new Set([
|
|
2674
|
+
"a",
|
|
2675
|
+
"an",
|
|
2676
|
+
"the",
|
|
2677
|
+
"is",
|
|
2678
|
+
"are",
|
|
2679
|
+
"was",
|
|
2680
|
+
"were",
|
|
2681
|
+
"be",
|
|
2682
|
+
"been",
|
|
2683
|
+
"being",
|
|
2684
|
+
"have",
|
|
2685
|
+
"has",
|
|
2686
|
+
"had",
|
|
2687
|
+
"do",
|
|
2688
|
+
"does",
|
|
2689
|
+
"did",
|
|
2690
|
+
"will",
|
|
2691
|
+
"would",
|
|
2692
|
+
"could",
|
|
2693
|
+
"should",
|
|
2694
|
+
"may",
|
|
2695
|
+
"might",
|
|
2696
|
+
"shall",
|
|
2697
|
+
"can",
|
|
2698
|
+
"need",
|
|
2699
|
+
"must",
|
|
2700
|
+
"i",
|
|
2701
|
+
"me",
|
|
2702
|
+
"my",
|
|
2703
|
+
"we",
|
|
2704
|
+
"our",
|
|
2705
|
+
"you",
|
|
2706
|
+
"your",
|
|
2707
|
+
"he",
|
|
2708
|
+
"she",
|
|
2709
|
+
"it",
|
|
2710
|
+
"its",
|
|
2711
|
+
"they",
|
|
2712
|
+
"them",
|
|
2713
|
+
"their",
|
|
2714
|
+
"this",
|
|
2715
|
+
"that",
|
|
2716
|
+
"these",
|
|
2717
|
+
"those",
|
|
2718
|
+
"and",
|
|
2719
|
+
"or",
|
|
2720
|
+
"but",
|
|
2721
|
+
"if",
|
|
2722
|
+
"then",
|
|
2723
|
+
"else",
|
|
2724
|
+
"when",
|
|
2725
|
+
"while",
|
|
2726
|
+
"for",
|
|
2727
|
+
"of",
|
|
2728
|
+
"at",
|
|
2729
|
+
"by",
|
|
2730
|
+
"to",
|
|
2731
|
+
"in",
|
|
2732
|
+
"on",
|
|
2733
|
+
"with",
|
|
2734
|
+
"from",
|
|
2735
|
+
"up",
|
|
2736
|
+
"out",
|
|
2737
|
+
"not",
|
|
2738
|
+
"no",
|
|
2739
|
+
"nor",
|
|
2740
|
+
"so",
|
|
2741
|
+
"too",
|
|
2742
|
+
"very",
|
|
2743
|
+
"just",
|
|
2744
|
+
"also",
|
|
2745
|
+
"what",
|
|
2746
|
+
"who",
|
|
2747
|
+
"how",
|
|
2748
|
+
"which",
|
|
2749
|
+
"where",
|
|
2750
|
+
"why",
|
|
2751
|
+
"there",
|
|
2752
|
+
"here",
|
|
2753
|
+
"any",
|
|
2754
|
+
"some",
|
|
2755
|
+
"each",
|
|
2756
|
+
"than",
|
|
2757
|
+
"like",
|
|
2758
|
+
"get",
|
|
2759
|
+
"give",
|
|
2760
|
+
"go",
|
|
2761
|
+
"make",
|
|
2762
|
+
"see",
|
|
2763
|
+
"know",
|
|
2764
|
+
"take"
|
|
2765
|
+
]);
|
|
2766
|
+
var PASCAL_OR_CAMEL_RE = /\b([A-Z][a-z]+[A-Za-z]*[a-z][A-Za-z]*|[a-z]+[A-Z][A-Za-z]*)\b/g;
|
|
2767
|
+
var FILE_PATH_RE = /(?:\.\/|[a-zA-Z0-9_-]+\/)[a-zA-Z0-9_\-./]+\.[a-zA-Z]{1,10}/g;
|
|
2768
|
+
var QUOTED_RE = /["']([^"']+)["']/g;
|
|
2769
|
+
var EntityExtractor = class {
|
|
2770
|
+
/**
|
|
2771
|
+
* Extract candidate entity mentions from a natural language query.
|
|
2772
|
+
*
|
|
2773
|
+
* @param query - The natural language query to extract entities from
|
|
2774
|
+
* @returns Array of raw entity strings in priority order, deduplicated
|
|
2775
|
+
*/
|
|
2776
|
+
extract(query) {
|
|
2777
|
+
const trimmed = query.trim();
|
|
2778
|
+
if (trimmed.length === 0) return [];
|
|
2779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2780
|
+
const result = [];
|
|
2781
|
+
const add = (entity) => {
|
|
2782
|
+
if (!seen.has(entity)) {
|
|
2783
|
+
seen.add(entity);
|
|
2784
|
+
result.push(entity);
|
|
2785
|
+
}
|
|
2786
|
+
};
|
|
2787
|
+
const quotedConsumed = /* @__PURE__ */ new Set();
|
|
2788
|
+
for (const match of trimmed.matchAll(QUOTED_RE)) {
|
|
2789
|
+
const inner = match[1].trim();
|
|
2790
|
+
if (inner.length > 0) {
|
|
2791
|
+
add(inner);
|
|
2792
|
+
quotedConsumed.add(inner);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
const casingConsumed = /* @__PURE__ */ new Set();
|
|
2796
|
+
for (const match of trimmed.matchAll(PASCAL_OR_CAMEL_RE)) {
|
|
2797
|
+
const token = match[0];
|
|
2798
|
+
if (!quotedConsumed.has(token)) {
|
|
2799
|
+
add(token);
|
|
2800
|
+
casingConsumed.add(token);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
const pathConsumed = /* @__PURE__ */ new Set();
|
|
2804
|
+
for (const match of trimmed.matchAll(FILE_PATH_RE)) {
|
|
2805
|
+
const path6 = match[0];
|
|
2806
|
+
add(path6);
|
|
2807
|
+
pathConsumed.add(path6);
|
|
2808
|
+
}
|
|
2809
|
+
const quotedWords = /* @__PURE__ */ new Set();
|
|
2810
|
+
for (const q of quotedConsumed) {
|
|
2811
|
+
for (const w of q.split(/\s+/)) {
|
|
2812
|
+
if (w.length > 0) quotedWords.add(w);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
const allConsumed = /* @__PURE__ */ new Set([
|
|
2816
|
+
...quotedConsumed,
|
|
2817
|
+
...quotedWords,
|
|
2818
|
+
...casingConsumed,
|
|
2819
|
+
...pathConsumed
|
|
2820
|
+
]);
|
|
2821
|
+
const words = trimmed.split(/\s+/);
|
|
2822
|
+
for (const raw of words) {
|
|
2823
|
+
const cleaned = raw.replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, "");
|
|
2824
|
+
if (cleaned.length === 0) continue;
|
|
2825
|
+
const lower = cleaned.toLowerCase();
|
|
2826
|
+
if (allConsumed.has(cleaned)) continue;
|
|
2827
|
+
if (STOP_WORDS2.has(lower)) continue;
|
|
2828
|
+
if (INTENT_KEYWORDS.has(lower)) continue;
|
|
2829
|
+
if (cleaned === cleaned.toUpperCase() && /^[A-Z]+$/.test(cleaned)) continue;
|
|
2830
|
+
add(cleaned);
|
|
2831
|
+
}
|
|
2832
|
+
return result;
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2835
|
+
var EntityResolver = class {
|
|
2836
|
+
store;
|
|
2837
|
+
fusion;
|
|
2838
|
+
constructor(store, fusion) {
|
|
2839
|
+
this.store = store;
|
|
2840
|
+
this.fusion = fusion;
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Resolve an array of raw entity strings to graph nodes.
|
|
2844
|
+
*
|
|
2845
|
+
* @param raws - Raw entity strings from EntityExtractor
|
|
2846
|
+
* @returns Array of ResolvedEntity for each successfully resolved raw string
|
|
2847
|
+
*/
|
|
2848
|
+
resolve(raws) {
|
|
2849
|
+
const results = [];
|
|
2850
|
+
for (const raw of raws) {
|
|
2851
|
+
const resolved = this.resolveOne(raw);
|
|
2852
|
+
if (resolved !== void 0) {
|
|
2853
|
+
results.push(resolved);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
return results;
|
|
2857
|
+
}
|
|
2858
|
+
resolveOne(raw) {
|
|
2859
|
+
const exactMatches = this.store.findNodes({ name: raw });
|
|
2860
|
+
if (exactMatches.length > 0) {
|
|
2861
|
+
const node = exactMatches[0];
|
|
2862
|
+
return {
|
|
2863
|
+
raw,
|
|
2864
|
+
nodeId: node.id,
|
|
2865
|
+
node,
|
|
2866
|
+
confidence: 1,
|
|
2867
|
+
method: "exact"
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
if (this.fusion) {
|
|
2871
|
+
const fusionResults = this.fusion.search(raw, 5);
|
|
2872
|
+
if (fusionResults.length > 0 && fusionResults[0].score > 0.5) {
|
|
2873
|
+
const top = fusionResults[0];
|
|
2874
|
+
return {
|
|
2875
|
+
raw,
|
|
2876
|
+
nodeId: top.nodeId,
|
|
2877
|
+
node: top.node,
|
|
2878
|
+
confidence: top.score,
|
|
2879
|
+
method: "fusion"
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
if (raw.length < 3) return void 0;
|
|
2884
|
+
const isPathLike = raw.includes("/");
|
|
2885
|
+
const fileNodes = this.store.findNodes({ type: "file" });
|
|
2886
|
+
for (const node of fileNodes) {
|
|
2887
|
+
if (!node.path) continue;
|
|
2888
|
+
if (isPathLike && node.path.includes(raw)) {
|
|
2889
|
+
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
2890
|
+
}
|
|
2891
|
+
const basename4 = node.path.split("/").pop() ?? "";
|
|
2892
|
+
if (basename4.includes(raw)) {
|
|
2893
|
+
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
2894
|
+
}
|
|
2895
|
+
if (raw.length >= 4 && node.path.includes(raw)) {
|
|
2896
|
+
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return void 0;
|
|
2900
|
+
}
|
|
2901
|
+
};
|
|
2902
|
+
var ResponseFormatter = class {
|
|
2903
|
+
/**
|
|
2904
|
+
* Format graph operation results into a human-readable summary.
|
|
2905
|
+
*
|
|
2906
|
+
* @param intent - The classified intent
|
|
2907
|
+
* @param entities - Resolved entities from the query
|
|
2908
|
+
* @param data - Raw result data (shape varies per intent)
|
|
2909
|
+
* @param query - Original natural language query (optional)
|
|
2910
|
+
* @returns Human-readable summary string
|
|
2911
|
+
*/
|
|
2912
|
+
format(intent, entities, data, query) {
|
|
2913
|
+
if (data === null || data === void 0) {
|
|
2914
|
+
return "No results found.";
|
|
2915
|
+
}
|
|
2916
|
+
const firstEntity = entities[0];
|
|
2917
|
+
const entityName = firstEntity?.raw ?? "the target";
|
|
2918
|
+
switch (intent) {
|
|
2919
|
+
case "impact":
|
|
2920
|
+
return this.formatImpact(entityName, data);
|
|
2921
|
+
case "find":
|
|
2922
|
+
return this.formatFind(data, query);
|
|
2923
|
+
case "relationships":
|
|
2924
|
+
return this.formatRelationships(entityName, entities, data);
|
|
2925
|
+
case "explain":
|
|
2926
|
+
return this.formatExplain(entityName, entities, data);
|
|
2927
|
+
case "anomaly":
|
|
2928
|
+
return this.formatAnomaly(data);
|
|
2929
|
+
default:
|
|
2930
|
+
return `Processed results for "${entityName}".`;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
formatImpact(entityName, data) {
|
|
2934
|
+
const d = data;
|
|
2935
|
+
const code = this.safeArrayLength(d?.code);
|
|
2936
|
+
const tests = this.safeArrayLength(d?.tests);
|
|
2937
|
+
const docs = this.safeArrayLength(d?.docs);
|
|
2938
|
+
return `Changing **${entityName}** affects ${this.p(code, "code file")}, ${this.p(tests, "test")}, and ${this.p(docs, "doc")}.`;
|
|
2939
|
+
}
|
|
2940
|
+
formatFind(data, query) {
|
|
2941
|
+
const count = Array.isArray(data) ? data.length : 0;
|
|
2942
|
+
if (query) {
|
|
2943
|
+
return `Found ${this.p(count, "match", "matches")} for "${query}".`;
|
|
2944
|
+
}
|
|
2945
|
+
return `Found ${this.p(count, "match", "matches")}.`;
|
|
2946
|
+
}
|
|
2947
|
+
formatRelationships(entityName, entities, data) {
|
|
2948
|
+
const d = data;
|
|
2949
|
+
const edges = Array.isArray(d?.edges) ? d.edges : [];
|
|
2950
|
+
const firstEntity = entities[0];
|
|
2951
|
+
const rootId = firstEntity?.nodeId ?? "";
|
|
2952
|
+
let outbound = 0;
|
|
2953
|
+
let inbound = 0;
|
|
2954
|
+
for (const edge of edges) {
|
|
2955
|
+
if (edge.from === rootId) outbound++;
|
|
2956
|
+
if (edge.to === rootId) inbound++;
|
|
2957
|
+
}
|
|
2958
|
+
return `**${entityName}** has ${outbound} outbound and ${inbound} inbound relationships.`;
|
|
2959
|
+
}
|
|
2960
|
+
formatExplain(entityName, entities, data) {
|
|
2961
|
+
const d = data;
|
|
2962
|
+
const context = Array.isArray(d?.context) ? d.context : [];
|
|
2963
|
+
const firstEntity = entities[0];
|
|
2964
|
+
const nodeType = firstEntity?.node.type ?? "node";
|
|
2965
|
+
const path6 = firstEntity?.node.path ?? "unknown";
|
|
2966
|
+
let neighborCount = 0;
|
|
2967
|
+
const firstContext = context[0];
|
|
2968
|
+
if (firstContext && Array.isArray(firstContext.nodes)) {
|
|
2969
|
+
neighborCount = firstContext.nodes.length;
|
|
2970
|
+
}
|
|
2971
|
+
return `**${entityName}** is a ${nodeType} at \`${path6}\`. Connected to ${neighborCount} nodes.`;
|
|
2972
|
+
}
|
|
2973
|
+
formatAnomaly(data) {
|
|
2974
|
+
const d = data;
|
|
2975
|
+
const outliers = Array.isArray(d?.statisticalOutliers) ? d.statisticalOutliers : [];
|
|
2976
|
+
const artPoints = Array.isArray(d?.articulationPoints) ? d.articulationPoints : [];
|
|
2977
|
+
const count = outliers.length + artPoints.length;
|
|
2978
|
+
if (count === 0) {
|
|
2979
|
+
return "Found 0 anomalies.";
|
|
2980
|
+
}
|
|
2981
|
+
const topItems = [
|
|
2982
|
+
...outliers.slice(0, 2).map((o) => o.nodeId ?? "unknown outlier"),
|
|
2983
|
+
...artPoints.slice(0, 1).map((a) => a.nodeId ?? "unknown bottleneck")
|
|
2984
|
+
].join(", ");
|
|
2985
|
+
return `Found ${this.p(count, "anomaly", "anomalies")}: ${topItems}.`;
|
|
2986
|
+
}
|
|
2987
|
+
safeArrayLength(value) {
|
|
2988
|
+
return Array.isArray(value) ? value.length : 0;
|
|
2989
|
+
}
|
|
2990
|
+
p(count, singular, plural) {
|
|
2991
|
+
const word = count === 1 ? singular : plural ?? singular + "s";
|
|
2992
|
+
return `${count} ${word}`;
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
var ENTITY_REQUIRED_INTENTS = /* @__PURE__ */ new Set(["impact", "relationships", "explain"]);
|
|
2996
|
+
var classifier = new IntentClassifier();
|
|
2997
|
+
var extractor = new EntityExtractor();
|
|
2998
|
+
var formatter = new ResponseFormatter();
|
|
2999
|
+
async function askGraph(store, question) {
|
|
3000
|
+
const fusion = new FusionLayer(store);
|
|
3001
|
+
const resolver = new EntityResolver(store, fusion);
|
|
3002
|
+
const classification = classifier.classify(question);
|
|
3003
|
+
if (classification.confidence < 0.3) {
|
|
3004
|
+
return {
|
|
3005
|
+
intent: classification.intent,
|
|
3006
|
+
intentConfidence: classification.confidence,
|
|
3007
|
+
entities: [],
|
|
3008
|
+
summary: "I'm not sure what you're asking. Try rephrasing your question.",
|
|
3009
|
+
data: null,
|
|
3010
|
+
suggestions: [
|
|
3011
|
+
'Try "what breaks if I change <name>?" for impact analysis',
|
|
3012
|
+
'Try "where is <name>?" to find entities',
|
|
3013
|
+
'Try "what calls <name>?" for relationships',
|
|
3014
|
+
'Try "what is <name>?" for explanations',
|
|
3015
|
+
'Try "what looks wrong?" for anomaly detection'
|
|
3016
|
+
]
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
const rawEntities = extractor.extract(question);
|
|
3020
|
+
const entities = resolver.resolve(rawEntities);
|
|
3021
|
+
if (ENTITY_REQUIRED_INTENTS.has(classification.intent) && entities.length === 0) {
|
|
3022
|
+
return {
|
|
3023
|
+
intent: classification.intent,
|
|
3024
|
+
intentConfidence: classification.confidence,
|
|
3025
|
+
entities: [],
|
|
3026
|
+
summary: "Could not find any matching nodes in the graph for your query. Try using exact class names, function names, or file paths.",
|
|
3027
|
+
data: null
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
let data;
|
|
3031
|
+
try {
|
|
3032
|
+
data = executeOperation(store, classification.intent, entities, question, fusion);
|
|
3033
|
+
} catch (err) {
|
|
3034
|
+
return {
|
|
3035
|
+
intent: classification.intent,
|
|
3036
|
+
intentConfidence: classification.confidence,
|
|
3037
|
+
entities,
|
|
3038
|
+
summary: `An error occurred while querying the graph: ${err instanceof Error ? err.message : String(err)}`,
|
|
3039
|
+
data: null
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
const summary = formatter.format(classification.intent, entities, data, question);
|
|
3043
|
+
return {
|
|
3044
|
+
intent: classification.intent,
|
|
3045
|
+
intentConfidence: classification.confidence,
|
|
3046
|
+
entities,
|
|
3047
|
+
summary,
|
|
3048
|
+
data
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
function executeOperation(store, intent, entities, question, fusion) {
|
|
3052
|
+
const cql = new ContextQL(store);
|
|
3053
|
+
switch (intent) {
|
|
3054
|
+
case "impact": {
|
|
3055
|
+
const rootId = entities[0].nodeId;
|
|
3056
|
+
const result = cql.execute({
|
|
3057
|
+
rootNodeIds: [rootId],
|
|
3058
|
+
bidirectional: true,
|
|
3059
|
+
maxDepth: 3
|
|
3060
|
+
});
|
|
3061
|
+
return groupNodesByImpact(result.nodes, rootId);
|
|
3062
|
+
}
|
|
3063
|
+
case "find": {
|
|
3064
|
+
return fusion.search(question, 10);
|
|
3065
|
+
}
|
|
3066
|
+
case "relationships": {
|
|
3067
|
+
const rootId = entities[0].nodeId;
|
|
3068
|
+
const result = cql.execute({
|
|
3069
|
+
rootNodeIds: [rootId],
|
|
3070
|
+
bidirectional: true,
|
|
3071
|
+
maxDepth: 1
|
|
3072
|
+
});
|
|
3073
|
+
return { nodes: result.nodes, edges: result.edges };
|
|
3074
|
+
}
|
|
3075
|
+
case "explain": {
|
|
3076
|
+
const searchResults = fusion.search(question, 10);
|
|
3077
|
+
const contextBlocks = [];
|
|
3078
|
+
const rootIds = entities.length > 0 ? [entities[0].nodeId] : searchResults.slice(0, 3).map((r) => r.nodeId);
|
|
3079
|
+
for (const rootId of rootIds) {
|
|
3080
|
+
const expanded = cql.execute({
|
|
3081
|
+
rootNodeIds: [rootId],
|
|
3082
|
+
maxDepth: 2
|
|
3083
|
+
});
|
|
3084
|
+
const matchingResult = searchResults.find((r) => r.nodeId === rootId);
|
|
3085
|
+
contextBlocks.push({
|
|
3086
|
+
rootNode: rootId,
|
|
3087
|
+
score: matchingResult?.score ?? 1,
|
|
3088
|
+
nodes: expanded.nodes,
|
|
3089
|
+
edges: expanded.edges
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
return { searchResults, context: contextBlocks };
|
|
3093
|
+
}
|
|
3094
|
+
case "anomaly": {
|
|
3095
|
+
const adapter = new GraphAnomalyAdapter(store);
|
|
3096
|
+
return adapter.detect();
|
|
3097
|
+
}
|
|
3098
|
+
default:
|
|
3099
|
+
return null;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
2403
3102
|
var PHASE_NODE_TYPES = {
|
|
2404
3103
|
implement: ["file", "function", "class", "method", "interface", "variable"],
|
|
2405
3104
|
review: ["adr", "document", "learning", "commit"],
|
|
@@ -3008,17 +3707,457 @@ var GraphFeedbackAdapter = class {
|
|
|
3008
3707
|
};
|
|
3009
3708
|
}
|
|
3010
3709
|
};
|
|
3710
|
+
var DEFAULT_EDGE_TYPES = ["imports", "calls", "references"];
|
|
3711
|
+
var TaskIndependenceAnalyzer = class {
|
|
3712
|
+
store;
|
|
3713
|
+
constructor(store) {
|
|
3714
|
+
this.store = store;
|
|
3715
|
+
}
|
|
3716
|
+
analyze(params) {
|
|
3717
|
+
const { tasks } = params;
|
|
3718
|
+
const depth = params.depth ?? 1;
|
|
3719
|
+
const edgeTypes = params.edgeTypes ?? DEFAULT_EDGE_TYPES;
|
|
3720
|
+
this.validate(tasks);
|
|
3721
|
+
const useGraph = this.store != null && depth > 0;
|
|
3722
|
+
const analysisLevel = useGraph ? "graph-expanded" : "file-only";
|
|
3723
|
+
const originalFiles = /* @__PURE__ */ new Map();
|
|
3724
|
+
const expandedFiles = /* @__PURE__ */ new Map();
|
|
3725
|
+
for (const task of tasks) {
|
|
3726
|
+
const origSet = new Set(task.files);
|
|
3727
|
+
originalFiles.set(task.id, origSet);
|
|
3728
|
+
if (useGraph) {
|
|
3729
|
+
const expanded = this.expandViaGraph(task.files, depth, edgeTypes);
|
|
3730
|
+
expandedFiles.set(task.id, expanded);
|
|
3731
|
+
} else {
|
|
3732
|
+
expandedFiles.set(task.id, /* @__PURE__ */ new Map());
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
const taskIds = tasks.map((t) => t.id);
|
|
3736
|
+
const pairs = [];
|
|
3737
|
+
for (let i = 0; i < taskIds.length; i++) {
|
|
3738
|
+
for (let j = i + 1; j < taskIds.length; j++) {
|
|
3739
|
+
const idA = taskIds[i];
|
|
3740
|
+
const idB = taskIds[j];
|
|
3741
|
+
const pair = this.computePairOverlap(
|
|
3742
|
+
idA,
|
|
3743
|
+
idB,
|
|
3744
|
+
originalFiles.get(idA),
|
|
3745
|
+
originalFiles.get(idB),
|
|
3746
|
+
expandedFiles.get(idA),
|
|
3747
|
+
expandedFiles.get(idB)
|
|
3748
|
+
);
|
|
3749
|
+
pairs.push(pair);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
const groups = this.buildGroups(taskIds, pairs);
|
|
3753
|
+
const verdict = this.generateVerdict(taskIds, groups, analysisLevel);
|
|
3754
|
+
return {
|
|
3755
|
+
tasks: taskIds,
|
|
3756
|
+
analysisLevel,
|
|
3757
|
+
depth,
|
|
3758
|
+
pairs,
|
|
3759
|
+
groups,
|
|
3760
|
+
verdict
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
// --- Private methods ---
|
|
3764
|
+
validate(tasks) {
|
|
3765
|
+
if (tasks.length < 2) {
|
|
3766
|
+
throw new Error("At least 2 tasks are required for independence analysis");
|
|
3767
|
+
}
|
|
3768
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
3769
|
+
for (const task of tasks) {
|
|
3770
|
+
if (seenIds.has(task.id)) {
|
|
3771
|
+
throw new Error(`Duplicate task ID: "${task.id}"`);
|
|
3772
|
+
}
|
|
3773
|
+
seenIds.add(task.id);
|
|
3774
|
+
if (task.files.length === 0) {
|
|
3775
|
+
throw new Error(`Task "${task.id}" has an empty files array`);
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
expandViaGraph(files, depth, edgeTypes) {
|
|
3780
|
+
const result = /* @__PURE__ */ new Map();
|
|
3781
|
+
const store = this.store;
|
|
3782
|
+
const cql = new ContextQL(store);
|
|
3783
|
+
const fileSet = new Set(files);
|
|
3784
|
+
for (const file of files) {
|
|
3785
|
+
const nodeId = `file:${file}`;
|
|
3786
|
+
const node = store.getNode(nodeId);
|
|
3787
|
+
if (!node) continue;
|
|
3788
|
+
const queryResult = cql.execute({
|
|
3789
|
+
rootNodeIds: [nodeId],
|
|
3790
|
+
maxDepth: depth,
|
|
3791
|
+
includeEdges: edgeTypes,
|
|
3792
|
+
includeTypes: ["file"]
|
|
3793
|
+
});
|
|
3794
|
+
for (const n of queryResult.nodes) {
|
|
3795
|
+
const path6 = n.path ?? n.id.replace(/^file:/, "");
|
|
3796
|
+
if (!fileSet.has(path6)) {
|
|
3797
|
+
if (!result.has(path6)) {
|
|
3798
|
+
result.set(path6, file);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return result;
|
|
3804
|
+
}
|
|
3805
|
+
computePairOverlap(idA, idB, origA, origB, expandedA, expandedB) {
|
|
3806
|
+
const overlaps = [];
|
|
3807
|
+
for (const file of origA) {
|
|
3808
|
+
if (origB.has(file)) {
|
|
3809
|
+
overlaps.push({ file, type: "direct" });
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
const directFiles = new Set(overlaps.map((o) => o.file));
|
|
3813
|
+
const transitiveFiles = /* @__PURE__ */ new Set();
|
|
3814
|
+
for (const [file, via] of expandedA) {
|
|
3815
|
+
if (origB.has(file) && !directFiles.has(file) && !transitiveFiles.has(file)) {
|
|
3816
|
+
transitiveFiles.add(file);
|
|
3817
|
+
overlaps.push({ file, type: "transitive", via });
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
for (const [file, via] of expandedB) {
|
|
3821
|
+
if (origA.has(file) && !directFiles.has(file) && !transitiveFiles.has(file)) {
|
|
3822
|
+
transitiveFiles.add(file);
|
|
3823
|
+
overlaps.push({ file, type: "transitive", via });
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
for (const [file, viaA] of expandedA) {
|
|
3827
|
+
if (expandedB.has(file) && !directFiles.has(file) && !transitiveFiles.has(file)) {
|
|
3828
|
+
transitiveFiles.add(file);
|
|
3829
|
+
overlaps.push({ file, type: "transitive", via: viaA });
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
return {
|
|
3833
|
+
taskA: idA,
|
|
3834
|
+
taskB: idB,
|
|
3835
|
+
independent: overlaps.length === 0,
|
|
3836
|
+
overlaps
|
|
3837
|
+
};
|
|
3838
|
+
}
|
|
3839
|
+
buildGroups(taskIds, pairs) {
|
|
3840
|
+
const parent = /* @__PURE__ */ new Map();
|
|
3841
|
+
const rank = /* @__PURE__ */ new Map();
|
|
3842
|
+
for (const id of taskIds) {
|
|
3843
|
+
parent.set(id, id);
|
|
3844
|
+
rank.set(id, 0);
|
|
3845
|
+
}
|
|
3846
|
+
const find = (x) => {
|
|
3847
|
+
let root = x;
|
|
3848
|
+
while (parent.get(root) !== root) {
|
|
3849
|
+
root = parent.get(root);
|
|
3850
|
+
}
|
|
3851
|
+
let current = x;
|
|
3852
|
+
while (current !== root) {
|
|
3853
|
+
const next = parent.get(current);
|
|
3854
|
+
parent.set(current, root);
|
|
3855
|
+
current = next;
|
|
3856
|
+
}
|
|
3857
|
+
return root;
|
|
3858
|
+
};
|
|
3859
|
+
const union = (a, b) => {
|
|
3860
|
+
const rootA = find(a);
|
|
3861
|
+
const rootB = find(b);
|
|
3862
|
+
if (rootA === rootB) return;
|
|
3863
|
+
const rankA = rank.get(rootA);
|
|
3864
|
+
const rankB = rank.get(rootB);
|
|
3865
|
+
if (rankA < rankB) {
|
|
3866
|
+
parent.set(rootA, rootB);
|
|
3867
|
+
} else if (rankA > rankB) {
|
|
3868
|
+
parent.set(rootB, rootA);
|
|
3869
|
+
} else {
|
|
3870
|
+
parent.set(rootB, rootA);
|
|
3871
|
+
rank.set(rootA, rankA + 1);
|
|
3872
|
+
}
|
|
3873
|
+
};
|
|
3874
|
+
for (const pair of pairs) {
|
|
3875
|
+
if (!pair.independent) {
|
|
3876
|
+
union(pair.taskA, pair.taskB);
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
3880
|
+
for (const id of taskIds) {
|
|
3881
|
+
const root = find(id);
|
|
3882
|
+
if (!groupMap.has(root)) {
|
|
3883
|
+
groupMap.set(root, []);
|
|
3884
|
+
}
|
|
3885
|
+
groupMap.get(root).push(id);
|
|
3886
|
+
}
|
|
3887
|
+
return Array.from(groupMap.values());
|
|
3888
|
+
}
|
|
3889
|
+
generateVerdict(taskIds, groups, analysisLevel) {
|
|
3890
|
+
const total = taskIds.length;
|
|
3891
|
+
const groupCount = groups.length;
|
|
3892
|
+
let verdict;
|
|
3893
|
+
if (groupCount === 1) {
|
|
3894
|
+
verdict = `All ${total} tasks conflict \u2014 must run serially.`;
|
|
3895
|
+
} else if (groupCount === total) {
|
|
3896
|
+
verdict = `All ${total} tasks are independent \u2014 can all run in parallel.`;
|
|
3897
|
+
} else {
|
|
3898
|
+
verdict = `${total} tasks form ${groupCount} independent groups \u2014 ${groupCount} parallel waves possible.`;
|
|
3899
|
+
}
|
|
3900
|
+
if (analysisLevel === "file-only") {
|
|
3901
|
+
verdict += " Graph unavailable \u2014 transitive dependencies not checked.";
|
|
3902
|
+
}
|
|
3903
|
+
return verdict;
|
|
3904
|
+
}
|
|
3905
|
+
};
|
|
3906
|
+
var ConflictPredictor = class {
|
|
3907
|
+
store;
|
|
3908
|
+
constructor(store) {
|
|
3909
|
+
this.store = store;
|
|
3910
|
+
}
|
|
3911
|
+
predict(params) {
|
|
3912
|
+
const analyzer = new TaskIndependenceAnalyzer(this.store);
|
|
3913
|
+
const result = analyzer.analyze(params);
|
|
3914
|
+
const churnMap = /* @__PURE__ */ new Map();
|
|
3915
|
+
const couplingMap = /* @__PURE__ */ new Map();
|
|
3916
|
+
let churnThreshold = Infinity;
|
|
3917
|
+
let couplingThreshold = Infinity;
|
|
3918
|
+
if (this.store != null) {
|
|
3919
|
+
const complexityResult = new GraphComplexityAdapter(this.store).computeComplexityHotspots();
|
|
3920
|
+
for (const hotspot of complexityResult.hotspots) {
|
|
3921
|
+
const existing = churnMap.get(hotspot.file);
|
|
3922
|
+
if (existing === void 0 || hotspot.changeFrequency > existing) {
|
|
3923
|
+
churnMap.set(hotspot.file, hotspot.changeFrequency);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
const couplingResult = new GraphCouplingAdapter(this.store).computeCouplingData();
|
|
3927
|
+
for (const fileData of couplingResult.files) {
|
|
3928
|
+
couplingMap.set(fileData.file, fileData.fanIn + fileData.fanOut);
|
|
3929
|
+
}
|
|
3930
|
+
churnThreshold = this.computePercentile(Array.from(churnMap.values()), 80);
|
|
3931
|
+
couplingThreshold = this.computePercentile(Array.from(couplingMap.values()), 80);
|
|
3932
|
+
}
|
|
3933
|
+
const conflicts = [];
|
|
3934
|
+
for (const pair of result.pairs) {
|
|
3935
|
+
if (pair.independent) continue;
|
|
3936
|
+
const { severity, reason, mitigation } = this.classifyPair(
|
|
3937
|
+
pair.taskA,
|
|
3938
|
+
pair.taskB,
|
|
3939
|
+
pair.overlaps,
|
|
3940
|
+
churnMap,
|
|
3941
|
+
couplingMap,
|
|
3942
|
+
churnThreshold,
|
|
3943
|
+
couplingThreshold
|
|
3944
|
+
);
|
|
3945
|
+
conflicts.push({
|
|
3946
|
+
taskA: pair.taskA,
|
|
3947
|
+
taskB: pair.taskB,
|
|
3948
|
+
severity,
|
|
3949
|
+
reason,
|
|
3950
|
+
mitigation,
|
|
3951
|
+
overlaps: pair.overlaps
|
|
3952
|
+
});
|
|
3953
|
+
}
|
|
3954
|
+
const taskIds = result.tasks;
|
|
3955
|
+
const groups = this.buildHighSeverityGroups(taskIds, conflicts);
|
|
3956
|
+
const regrouped = !this.groupsEqual(result.groups, groups);
|
|
3957
|
+
let highCount = 0;
|
|
3958
|
+
let mediumCount = 0;
|
|
3959
|
+
let lowCount = 0;
|
|
3960
|
+
for (const c of conflicts) {
|
|
3961
|
+
if (c.severity === "high") highCount++;
|
|
3962
|
+
else if (c.severity === "medium") mediumCount++;
|
|
3963
|
+
else lowCount++;
|
|
3964
|
+
}
|
|
3965
|
+
const verdict = this.generateVerdict(
|
|
3966
|
+
taskIds,
|
|
3967
|
+
groups,
|
|
3968
|
+
result.analysisLevel,
|
|
3969
|
+
highCount,
|
|
3970
|
+
mediumCount,
|
|
3971
|
+
lowCount,
|
|
3972
|
+
regrouped
|
|
3973
|
+
);
|
|
3974
|
+
return {
|
|
3975
|
+
tasks: taskIds,
|
|
3976
|
+
analysisLevel: result.analysisLevel,
|
|
3977
|
+
depth: result.depth,
|
|
3978
|
+
conflicts,
|
|
3979
|
+
groups,
|
|
3980
|
+
summary: {
|
|
3981
|
+
high: highCount,
|
|
3982
|
+
medium: mediumCount,
|
|
3983
|
+
low: lowCount,
|
|
3984
|
+
regrouped
|
|
3985
|
+
},
|
|
3986
|
+
verdict
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
// --- Private helpers ---
|
|
3990
|
+
classifyPair(taskA, taskB, overlaps, churnMap, couplingMap, churnThreshold, couplingThreshold) {
|
|
3991
|
+
let maxSeverity = "low";
|
|
3992
|
+
let primaryReason = "";
|
|
3993
|
+
let primaryMitigation = "";
|
|
3994
|
+
for (const overlap of overlaps) {
|
|
3995
|
+
let overlapSeverity;
|
|
3996
|
+
let reason;
|
|
3997
|
+
let mitigation;
|
|
3998
|
+
if (overlap.type === "direct") {
|
|
3999
|
+
overlapSeverity = "high";
|
|
4000
|
+
reason = `Both tasks write to ${overlap.file}`;
|
|
4001
|
+
mitigation = `Serialize: run ${taskA} before ${taskB}`;
|
|
4002
|
+
} else {
|
|
4003
|
+
const churn = churnMap.get(overlap.file);
|
|
4004
|
+
const coupling = couplingMap.get(overlap.file);
|
|
4005
|
+
const via = overlap.via ?? "unknown";
|
|
4006
|
+
if (churn !== void 0 && churn >= churnThreshold && churnThreshold !== Infinity) {
|
|
4007
|
+
overlapSeverity = "medium";
|
|
4008
|
+
reason = `Transitive overlap on high-churn file ${overlap.file} (via ${via})`;
|
|
4009
|
+
mitigation = `Review: ${overlap.file} changes frequently \u2014 coordinate edits between ${taskA} and ${taskB}`;
|
|
4010
|
+
} else if (coupling !== void 0 && coupling >= couplingThreshold && couplingThreshold !== Infinity) {
|
|
4011
|
+
overlapSeverity = "medium";
|
|
4012
|
+
reason = `Transitive overlap on highly-coupled file ${overlap.file} (via ${via})`;
|
|
4013
|
+
mitigation = `Review: ${overlap.file} has high coupling \u2014 coordinate edits between ${taskA} and ${taskB}`;
|
|
4014
|
+
} else {
|
|
4015
|
+
overlapSeverity = "low";
|
|
4016
|
+
reason = `Transitive overlap on ${overlap.file} (via ${via}) \u2014 low risk`;
|
|
4017
|
+
mitigation = `Info: transitive overlap unlikely to cause conflicts`;
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
if (this.severityRank(overlapSeverity) > this.severityRank(maxSeverity)) {
|
|
4021
|
+
maxSeverity = overlapSeverity;
|
|
4022
|
+
primaryReason = reason;
|
|
4023
|
+
primaryMitigation = mitigation;
|
|
4024
|
+
} else if (primaryReason === "") {
|
|
4025
|
+
primaryReason = reason;
|
|
4026
|
+
primaryMitigation = mitigation;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
return { severity: maxSeverity, reason: primaryReason, mitigation: primaryMitigation };
|
|
4030
|
+
}
|
|
4031
|
+
severityRank(severity) {
|
|
4032
|
+
switch (severity) {
|
|
4033
|
+
case "high":
|
|
4034
|
+
return 3;
|
|
4035
|
+
case "medium":
|
|
4036
|
+
return 2;
|
|
4037
|
+
case "low":
|
|
4038
|
+
return 1;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
computePercentile(values, percentile) {
|
|
4042
|
+
if (values.length === 0) return Infinity;
|
|
4043
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
4044
|
+
const index = Math.ceil(percentile / 100 * sorted.length) - 1;
|
|
4045
|
+
return sorted[Math.min(index, sorted.length - 1)];
|
|
4046
|
+
}
|
|
4047
|
+
buildHighSeverityGroups(taskIds, conflicts) {
|
|
4048
|
+
const parent = /* @__PURE__ */ new Map();
|
|
4049
|
+
const rank = /* @__PURE__ */ new Map();
|
|
4050
|
+
for (const id of taskIds) {
|
|
4051
|
+
parent.set(id, id);
|
|
4052
|
+
rank.set(id, 0);
|
|
4053
|
+
}
|
|
4054
|
+
const find = (x) => {
|
|
4055
|
+
let root = x;
|
|
4056
|
+
while (parent.get(root) !== root) {
|
|
4057
|
+
root = parent.get(root);
|
|
4058
|
+
}
|
|
4059
|
+
let current = x;
|
|
4060
|
+
while (current !== root) {
|
|
4061
|
+
const next = parent.get(current);
|
|
4062
|
+
parent.set(current, root);
|
|
4063
|
+
current = next;
|
|
4064
|
+
}
|
|
4065
|
+
return root;
|
|
4066
|
+
};
|
|
4067
|
+
const union = (a, b) => {
|
|
4068
|
+
const rootA = find(a);
|
|
4069
|
+
const rootB = find(b);
|
|
4070
|
+
if (rootA === rootB) return;
|
|
4071
|
+
const rankA = rank.get(rootA);
|
|
4072
|
+
const rankB = rank.get(rootB);
|
|
4073
|
+
if (rankA < rankB) {
|
|
4074
|
+
parent.set(rootA, rootB);
|
|
4075
|
+
} else if (rankA > rankB) {
|
|
4076
|
+
parent.set(rootB, rootA);
|
|
4077
|
+
} else {
|
|
4078
|
+
parent.set(rootB, rootA);
|
|
4079
|
+
rank.set(rootA, rankA + 1);
|
|
4080
|
+
}
|
|
4081
|
+
};
|
|
4082
|
+
for (const conflict of conflicts) {
|
|
4083
|
+
if (conflict.severity === "high") {
|
|
4084
|
+
union(conflict.taskA, conflict.taskB);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
4088
|
+
for (const id of taskIds) {
|
|
4089
|
+
const root = find(id);
|
|
4090
|
+
let group = groupMap.get(root);
|
|
4091
|
+
if (group === void 0) {
|
|
4092
|
+
group = [];
|
|
4093
|
+
groupMap.set(root, group);
|
|
4094
|
+
}
|
|
4095
|
+
group.push(id);
|
|
4096
|
+
}
|
|
4097
|
+
return Array.from(groupMap.values());
|
|
4098
|
+
}
|
|
4099
|
+
groupsEqual(a, b) {
|
|
4100
|
+
if (a.length !== b.length) return false;
|
|
4101
|
+
const normalize2 = (groups) => groups.map((g) => [...g].sort()).sort((x, y) => {
|
|
4102
|
+
const xFirst = x[0];
|
|
4103
|
+
const yFirst = y[0];
|
|
4104
|
+
return xFirst.localeCompare(yFirst);
|
|
4105
|
+
});
|
|
4106
|
+
const normA = normalize2(a);
|
|
4107
|
+
const normB = normalize2(b);
|
|
4108
|
+
for (let i = 0; i < normA.length; i++) {
|
|
4109
|
+
const groupA = normA[i];
|
|
4110
|
+
const groupB = normB[i];
|
|
4111
|
+
if (groupA.length !== groupB.length) return false;
|
|
4112
|
+
for (let j = 0; j < groupA.length; j++) {
|
|
4113
|
+
if (groupA[j] !== groupB[j]) return false;
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
return true;
|
|
4117
|
+
}
|
|
4118
|
+
generateVerdict(taskIds, groups, analysisLevel, highCount, mediumCount, lowCount, regrouped) {
|
|
4119
|
+
const total = taskIds.length;
|
|
4120
|
+
const groupCount = groups.length;
|
|
4121
|
+
const parts = [];
|
|
4122
|
+
const conflictParts = [];
|
|
4123
|
+
if (highCount > 0) conflictParts.push(`${highCount} high`);
|
|
4124
|
+
if (mediumCount > 0) conflictParts.push(`${mediumCount} medium`);
|
|
4125
|
+
if (lowCount > 0) conflictParts.push(`${lowCount} low`);
|
|
4126
|
+
if (conflictParts.length === 0) {
|
|
4127
|
+
parts.push(`${total} tasks have no conflicts \u2014 can all run in parallel.`);
|
|
4128
|
+
} else {
|
|
4129
|
+
parts.push(`${total} tasks have ${conflictParts.join(", ")} severity conflicts.`);
|
|
4130
|
+
}
|
|
4131
|
+
if (groupCount === 1) {
|
|
4132
|
+
parts.push(`All tasks must run serially.`);
|
|
4133
|
+
} else if (groupCount === total) {
|
|
4134
|
+
parts.push(`${groupCount} parallel groups (all independent).`);
|
|
4135
|
+
} else {
|
|
4136
|
+
parts.push(`${groupCount} parallel groups possible.`);
|
|
4137
|
+
}
|
|
4138
|
+
if (regrouped) {
|
|
4139
|
+
parts.push(`Tasks were regrouped due to high-severity conflicts.`);
|
|
4140
|
+
}
|
|
4141
|
+
if (analysisLevel === "file-only") {
|
|
4142
|
+
parts.push(`Graph unavailable \u2014 severity based on file overlaps only.`);
|
|
4143
|
+
}
|
|
4144
|
+
return parts.join(" ");
|
|
4145
|
+
}
|
|
4146
|
+
};
|
|
3011
4147
|
var VERSION = "0.2.0";
|
|
3012
4148
|
export {
|
|
3013
4149
|
Assembler,
|
|
3014
4150
|
CIConnector,
|
|
3015
4151
|
CURRENT_SCHEMA_VERSION,
|
|
3016
4152
|
CodeIngestor,
|
|
4153
|
+
ConflictPredictor,
|
|
3017
4154
|
ConfluenceConnector,
|
|
3018
4155
|
ContextQL,
|
|
3019
4156
|
DesignConstraintAdapter,
|
|
3020
4157
|
DesignIngestor,
|
|
3021
4158
|
EDGE_TYPES,
|
|
4159
|
+
EntityExtractor,
|
|
4160
|
+
EntityResolver,
|
|
3022
4161
|
FusionLayer,
|
|
3023
4162
|
GitIngestor,
|
|
3024
4163
|
GraphAnomalyAdapter,
|
|
@@ -3030,15 +4169,21 @@ export {
|
|
|
3030
4169
|
GraphFeedbackAdapter,
|
|
3031
4170
|
GraphNodeSchema,
|
|
3032
4171
|
GraphStore,
|
|
4172
|
+
INTENTS,
|
|
4173
|
+
IntentClassifier,
|
|
3033
4174
|
JiraConnector,
|
|
3034
4175
|
KnowledgeIngestor,
|
|
3035
4176
|
NODE_TYPES,
|
|
3036
4177
|
OBSERVABILITY_TYPES,
|
|
4178
|
+
ResponseFormatter,
|
|
3037
4179
|
SlackConnector,
|
|
3038
4180
|
SyncManager,
|
|
4181
|
+
TaskIndependenceAnalyzer,
|
|
3039
4182
|
TopologicalLinker,
|
|
3040
4183
|
VERSION,
|
|
3041
4184
|
VectorStore,
|
|
4185
|
+
askGraph,
|
|
4186
|
+
groupNodesByImpact,
|
|
3042
4187
|
linkToCode,
|
|
3043
4188
|
loadGraph,
|
|
3044
4189
|
project,
|