@oscharko-dev/keiko-workflows 0.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/dist/.tsbuildinfo +1 -0
- package/dist/bug-investigation/context.d.ts +7 -0
- package/dist/bug-investigation/context.d.ts.map +1 -0
- package/dist/bug-investigation/context.js +119 -0
- package/dist/bug-investigation/descriptor.d.ts +4 -0
- package/dist/bug-investigation/descriptor.d.ts.map +1 -0
- package/dist/bug-investigation/descriptor.js +46 -0
- package/dist/bug-investigation/emit.d.ts +13 -0
- package/dist/bug-investigation/emit.d.ts.map +1 -0
- package/dist/bug-investigation/emit.js +35 -0
- package/dist/bug-investigation/events.d.ts +2 -0
- package/dist/bug-investigation/events.d.ts.map +1 -0
- package/dist/bug-investigation/events.js +6 -0
- package/dist/bug-investigation/failure-parse.d.ts +4 -0
- package/dist/bug-investigation/failure-parse.d.ts.map +1 -0
- package/dist/bug-investigation/failure-parse.js +154 -0
- package/dist/bug-investigation/guard.d.ts +3 -0
- package/dist/bug-investigation/guard.d.ts.map +1 -0
- package/dist/bug-investigation/guard.js +69 -0
- package/dist/bug-investigation/index.d.ts +8 -0
- package/dist/bug-investigation/index.d.ts.map +1 -0
- package/dist/bug-investigation/index.js +13 -0
- package/dist/bug-investigation/internal.d.ts +39 -0
- package/dist/bug-investigation/internal.d.ts.map +1 -0
- package/dist/bug-investigation/internal.js +65 -0
- package/dist/bug-investigation/memory.d.ts +5 -0
- package/dist/bug-investigation/memory.d.ts.map +1 -0
- package/dist/bug-investigation/memory.js +91 -0
- package/dist/bug-investigation/model-loop.d.ts +5 -0
- package/dist/bug-investigation/model-loop.d.ts.map +1 -0
- package/dist/bug-investigation/model-loop.js +225 -0
- package/dist/bug-investigation/parse.d.ts +4 -0
- package/dist/bug-investigation/parse.d.ts.map +1 -0
- package/dist/bug-investigation/parse.js +125 -0
- package/dist/bug-investigation/prompt.d.ts +5 -0
- package/dist/bug-investigation/prompt.d.ts.map +1 -0
- package/dist/bug-investigation/prompt.js +122 -0
- package/dist/bug-investigation/report.d.ts +24 -0
- package/dist/bug-investigation/report.d.ts.map +1 -0
- package/dist/bug-investigation/report.js +151 -0
- package/dist/bug-investigation/stages.d.ts +14 -0
- package/dist/bug-investigation/stages.d.ts.map +1 -0
- package/dist/bug-investigation/stages.js +247 -0
- package/dist/bug-investigation/types.d.ts +88 -0
- package/dist/bug-investigation/types.d.ts.map +1 -0
- package/dist/bug-investigation/types.js +6 -0
- package/dist/bug-investigation/verify-stage.d.ts +11 -0
- package/dist/bug-investigation/verify-stage.d.ts.map +1 -0
- package/dist/bug-investigation/verify-stage.js +91 -0
- package/dist/bug-investigation/workflow.d.ts +3 -0
- package/dist/bug-investigation/workflow.d.ts.map +1 -0
- package/dist/bug-investigation/workflow.js +85 -0
- package/dist/contextpack/assemble.d.ts +35 -0
- package/dist/contextpack/assemble.d.ts.map +1 -0
- package/dist/contextpack/assemble.js +431 -0
- package/dist/contextpack/compaction.d.ts +23 -0
- package/dist/contextpack/compaction.d.ts.map +1 -0
- package/dist/contextpack/compaction.js +68 -0
- package/dist/contextpack/index.d.ts +9 -0
- package/dist/contextpack/index.d.ts.map +1 -0
- package/dist/contextpack/index.js +8 -0
- package/dist/contextpack/microIndex.d.ts +29 -0
- package/dist/contextpack/microIndex.d.ts.map +1 -0
- package/dist/contextpack/microIndex.js +98 -0
- package/dist/contextpack/reranker.d.ts +15 -0
- package/dist/contextpack/reranker.d.ts.map +1 -0
- package/dist/contextpack/reranker.js +31 -0
- package/dist/descriptor.d.ts +2 -0
- package/dist/descriptor.d.ts.map +1 -0
- package/dist/descriptor.js +1 -0
- package/dist/governed-handoff.d.ts +6 -0
- package/dist/governed-handoff.d.ts.map +1 -0
- package/dist/governed-handoff.js +86 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/planner/anchors.d.ts +17 -0
- package/dist/planner/anchors.d.ts.map +1 -0
- package/dist/planner/anchors.js +291 -0
- package/dist/planner/explorationPlanner.d.ts +9 -0
- package/dist/planner/explorationPlanner.d.ts.map +1 -0
- package/dist/planner/explorationPlanner.js +15 -0
- package/dist/planner/governor.d.ts +16 -0
- package/dist/planner/governor.d.ts.map +1 -0
- package/dist/planner/governor.js +106 -0
- package/dist/planner/index.d.ts +11 -0
- package/dist/planner/index.d.ts.map +1 -0
- package/dist/planner/index.js +8 -0
- package/dist/planner/intent.d.ts +8 -0
- package/dist/planner/intent.d.ts.map +1 -0
- package/dist/planner/intent.js +140 -0
- package/dist/planner/plan.d.ts +43 -0
- package/dist/planner/plan.d.ts.map +1 -0
- package/dist/planner/plan.js +237 -0
- package/dist/promptEnhancer/index.d.ts +23 -0
- package/dist/promptEnhancer/index.d.ts.map +1 -0
- package/dist/promptEnhancer/index.js +282 -0
- package/dist/qualityIntelligence/__tests__/fixtures/runEntryFixtures.d.ts +30 -0
- package/dist/qualityIntelligence/__tests__/fixtures/runEntryFixtures.d.ts.map +1 -0
- package/dist/qualityIntelligence/__tests__/fixtures/runEntryFixtures.js +114 -0
- package/dist/qualityIntelligence/cancellation.d.ts +20 -0
- package/dist/qualityIntelligence/cancellation.d.ts.map +1 -0
- package/dist/qualityIntelligence/cancellation.js +55 -0
- package/dist/qualityIntelligence/descriptors.d.ts +41 -0
- package/dist/qualityIntelligence/descriptors.d.ts.map +1 -0
- package/dist/qualityIntelligence/descriptors.js +105 -0
- package/dist/qualityIntelligence/index.d.ts +11 -0
- package/dist/qualityIntelligence/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/index.js +11 -0
- package/dist/qualityIntelligence/modelRoutedTestDesign.d.ts +100 -0
- package/dist/qualityIntelligence/modelRoutedTestDesign.d.ts.map +1 -0
- package/dist/qualityIntelligence/modelRoutedTestDesign.js +620 -0
- package/dist/qualityIntelligence/runEntries.d.ts +60 -0
- package/dist/qualityIntelligence/runEntries.d.ts.map +1 -0
- package/dist/qualityIntelligence/runEntries.js +243 -0
- package/dist/qualityIntelligence/runtimeCommon.d.ts +106 -0
- package/dist/qualityIntelligence/runtimeCommon.d.ts.map +1 -0
- package/dist/qualityIntelligence/runtimeCommon.js +258 -0
- package/dist/qualityIntelligence/scopedRegeneration.d.ts +26 -0
- package/dist/qualityIntelligence/scopedRegeneration.d.ts.map +1 -0
- package/dist/qualityIntelligence/scopedRegeneration.js +35 -0
- package/dist/ranking/filter.d.ts +20 -0
- package/dist/ranking/filter.d.ts.map +1 -0
- package/dist/ranking/filter.js +99 -0
- package/dist/ranking/index.d.ts +9 -0
- package/dist/ranking/index.d.ts.map +1 -0
- package/dist/ranking/index.js +8 -0
- package/dist/ranking/rank.d.ts +21 -0
- package/dist/ranking/rank.d.ts.map +1 -0
- package/dist/ranking/rank.js +160 -0
- package/dist/ranking/scoring.d.ts +13 -0
- package/dist/ranking/scoring.d.ts.map +1 -0
- package/dist/ranking/scoring.js +39 -0
- package/dist/ranking/signals.d.ts +20 -0
- package/dist/ranking/signals.d.ts.map +1 -0
- package/dist/ranking/signals.js +145 -0
- package/dist/unit-tests/context.d.ts +7 -0
- package/dist/unit-tests/context.d.ts.map +1 -0
- package/dist/unit-tests/context.js +129 -0
- package/dist/unit-tests/conventions.d.ts +5 -0
- package/dist/unit-tests/conventions.d.ts.map +1 -0
- package/dist/unit-tests/conventions.js +87 -0
- package/dist/unit-tests/descriptor.d.ts +5 -0
- package/dist/unit-tests/descriptor.d.ts.map +1 -0
- package/dist/unit-tests/descriptor.js +43 -0
- package/dist/unit-tests/emit.d.ts +13 -0
- package/dist/unit-tests/emit.d.ts.map +1 -0
- package/dist/unit-tests/emit.js +35 -0
- package/dist/unit-tests/events.d.ts +2 -0
- package/dist/unit-tests/events.d.ts.map +1 -0
- package/dist/unit-tests/events.js +6 -0
- package/dist/unit-tests/frontend.d.ts +42 -0
- package/dist/unit-tests/frontend.d.ts.map +1 -0
- package/dist/unit-tests/frontend.js +281 -0
- package/dist/unit-tests/index.d.ts +9 -0
- package/dist/unit-tests/index.d.ts.map +1 -0
- package/dist/unit-tests/index.js +15 -0
- package/dist/unit-tests/internal.d.ts +36 -0
- package/dist/unit-tests/internal.d.ts.map +1 -0
- package/dist/unit-tests/internal.js +43 -0
- package/dist/unit-tests/model-loop.d.ts +6 -0
- package/dist/unit-tests/model-loop.d.ts.map +1 -0
- package/dist/unit-tests/model-loop.js +98 -0
- package/dist/unit-tests/parse.d.ts +7 -0
- package/dist/unit-tests/parse.d.ts.map +1 -0
- package/dist/unit-tests/parse.js +68 -0
- package/dist/unit-tests/prompt.d.ts +6 -0
- package/dist/unit-tests/prompt.d.ts.map +1 -0
- package/dist/unit-tests/prompt.js +139 -0
- package/dist/unit-tests/report.d.ts +26 -0
- package/dist/unit-tests/report.d.ts.map +1 -0
- package/dist/unit-tests/report.js +104 -0
- package/dist/unit-tests/stages.d.ts +12 -0
- package/dist/unit-tests/stages.d.ts.map +1 -0
- package/dist/unit-tests/stages.js +202 -0
- package/dist/unit-tests/strategy.d.ts +6 -0
- package/dist/unit-tests/strategy.d.ts.map +1 -0
- package/dist/unit-tests/strategy.js +36 -0
- package/dist/unit-tests/target-guard.d.ts +5 -0
- package/dist/unit-tests/target-guard.d.ts.map +1 -0
- package/dist/unit-tests/target-guard.js +29 -0
- package/dist/unit-tests/types.d.ts +74 -0
- package/dist/unit-tests/types.d.ts.map +1 -0
- package/dist/unit-tests/types.js +6 -0
- package/dist/unit-tests/verify-stage.d.ts +10 -0
- package/dist/unit-tests/verify-stage.d.ts.map +1 -0
- package/dist/unit-tests/verify-stage.js +56 -0
- package/dist/unit-tests/workflow.d.ts +3 -0
- package/dist/unit-tests/workflow.d.ts.map +1 -0
- package/dist/unit-tests/workflow.js +69 -0
- package/package.json +38 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Scoped regeneration entry (Epic #735, Issue #743).
|
|
2
|
+
//
|
|
3
|
+
// Invokes the existing model-routed test-design workflow with a NARROWED set of ingested atoms
|
|
4
|
+
// — only atoms belonging to stale candidates — and a fresh runId. Does NOT re-implement the
|
|
5
|
+
// workflow; composes runQualityIntelligenceModelRoutedTestDesign unchanged.
|
|
6
|
+
import { QualityIntelligence as QI } from "@oscharko-dev/keiko-contracts";
|
|
7
|
+
import { runQualityIntelligenceModelRoutedTestDesign } from "./modelRoutedTestDesign.js";
|
|
8
|
+
const PLAN_STAGES = Object.freeze([
|
|
9
|
+
{ name: "plan", descriptor: "qi:plan" },
|
|
10
|
+
{ name: "candidates", descriptor: "qi:model-generate" },
|
|
11
|
+
{ name: "judge", descriptor: "qi:judge" },
|
|
12
|
+
{ name: "coverage", descriptor: "qi:coverage" },
|
|
13
|
+
{ name: "validate", descriptor: "qi:validate" },
|
|
14
|
+
{ name: "finalize", descriptor: "qi:finalize" },
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Run the model-routed test-design workflow scoped to a narrowed set of atoms. Returns the full run
|
|
18
|
+
* summary plus a count of narrowed atoms for caller bookkeeping. The caller is responsible for
|
|
19
|
+
* merging the regenerated candidates with preserved-fresh candidates from the original run.
|
|
20
|
+
*/
|
|
21
|
+
export async function runScopedRegeneration(input, deps) {
|
|
22
|
+
const plan = {
|
|
23
|
+
id: QI.asQualityIntelligenceRunId(input.newRunId),
|
|
24
|
+
requestedAt: input.requestedAt,
|
|
25
|
+
plannerKind: "model-routed",
|
|
26
|
+
stages: PLAN_STAGES,
|
|
27
|
+
};
|
|
28
|
+
const summary = await runQualityIntelligenceModelRoutedTestDesign({
|
|
29
|
+
plan,
|
|
30
|
+
envelopes: input.envelopes,
|
|
31
|
+
ingestedAtoms: input.ingestedAtoms,
|
|
32
|
+
provenanceRefs: input.provenanceRefs,
|
|
33
|
+
}, deps);
|
|
34
|
+
return { summary, narrowedAtomCount: input.ingestedAtoms.length };
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CandidateFile, OmittedContextEntry } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
2
|
+
export interface AnnotatedCandidate {
|
|
3
|
+
readonly candidate: CandidateFile;
|
|
4
|
+
readonly generatedHint: boolean;
|
|
5
|
+
readonly duplicate: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface FilterOptions {
|
|
8
|
+
readonly minScore: number;
|
|
9
|
+
readonly maxKept: number;
|
|
10
|
+
readonly omitGenerated: boolean;
|
|
11
|
+
readonly omitNearDuplicates: boolean;
|
|
12
|
+
readonly nowMs: () => number;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_FILTER_OPTIONS: Omit<FilterOptions, "nowMs">;
|
|
15
|
+
export interface FilterResult {
|
|
16
|
+
readonly kept: readonly CandidateFile[];
|
|
17
|
+
readonly omitted: readonly OmittedContextEntry[];
|
|
18
|
+
}
|
|
19
|
+
export declare function filterCandidates(entries: readonly AnnotatedCandidate[], options: FilterOptions): FilterResult;
|
|
20
|
+
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/ranking/filter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACpB,MAAM,iDAAiD,CAAC;AAKzD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAKtD,CAAC;AAEX,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAClD;AAiGD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,OAAO,EAAE,aAAa,GACrB,YAAY,CAmBd"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Negative-context filter for the ranker (Epic #177, Issue #182). Converts annotated
|
|
2
|
+
// candidates into a kept/omitted partition with explicit CandidateOmissionReason values.
|
|
3
|
+
// Priority is fixed (pre-set reason > generated > low-relevance > duplicate); a hard maxKept
|
|
4
|
+
// cap demotes the overflow to "budget-exhausted". Determinism: ties break on scopePath asc.
|
|
5
|
+
export const DEFAULT_FILTER_OPTIONS = {
|
|
6
|
+
minScore: 0.15,
|
|
7
|
+
maxKept: 50,
|
|
8
|
+
omitGenerated: true,
|
|
9
|
+
omitNearDuplicates: true,
|
|
10
|
+
};
|
|
11
|
+
function classifyReason(entry, options) {
|
|
12
|
+
const preset = entry.candidate.omitted;
|
|
13
|
+
if (preset !== undefined) {
|
|
14
|
+
return preset;
|
|
15
|
+
}
|
|
16
|
+
if (entry.generatedHint && options.omitGenerated) {
|
|
17
|
+
return "generated";
|
|
18
|
+
}
|
|
19
|
+
if (entry.candidate.score < options.minScore) {
|
|
20
|
+
return "low-relevance";
|
|
21
|
+
}
|
|
22
|
+
if (entry.duplicate && options.omitNearDuplicates) {
|
|
23
|
+
return "near-duplicate";
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
function compareKept(a, b) {
|
|
28
|
+
if (b.score !== a.score) {
|
|
29
|
+
return b.score - a.score;
|
|
30
|
+
}
|
|
31
|
+
if (a.scopePath < b.scopePath) {
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
34
|
+
if (a.scopePath > b.scopePath) {
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
function compareOmitted(a, b) {
|
|
40
|
+
if (a.scopePath < b.scopePath) {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
if (a.scopePath > b.scopePath) {
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
function toOmittedEntry(scopePath, reason, nowMs) {
|
|
49
|
+
return { scopePath, reason, omittedAtMs: nowMs };
|
|
50
|
+
}
|
|
51
|
+
function partition(entries, options, nowMs) {
|
|
52
|
+
const kept = [];
|
|
53
|
+
const lowRelevance = [];
|
|
54
|
+
const omittedEntries = [];
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const reason = classifyReason(entry, options);
|
|
57
|
+
if (reason === undefined) {
|
|
58
|
+
kept.push(entry.candidate);
|
|
59
|
+
}
|
|
60
|
+
else if (reason === "low-relevance") {
|
|
61
|
+
lowRelevance.push(entry.candidate);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
omittedEntries.push(toOmittedEntry(entry.candidate.scopePath, reason, nowMs));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { kept, lowRelevance, omittedEntries };
|
|
68
|
+
}
|
|
69
|
+
function normalizeMaxKept(maxKept) {
|
|
70
|
+
if (!Number.isFinite(maxKept)) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
return Math.max(0, Math.floor(maxKept));
|
|
74
|
+
}
|
|
75
|
+
function appendLowRelevanceOmissions(omittedEntries, candidates, nowMs) {
|
|
76
|
+
for (const candidate of candidates) {
|
|
77
|
+
omittedEntries.push(toOmittedEntry(candidate.scopePath, "low-relevance", nowMs));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function filterCandidates(entries, options) {
|
|
81
|
+
const nowMs = options.nowMs();
|
|
82
|
+
const { kept, lowRelevance, omittedEntries } = partition(entries, options, nowMs);
|
|
83
|
+
const maxKept = normalizeMaxKept(options.maxKept);
|
|
84
|
+
lowRelevance.sort(compareKept);
|
|
85
|
+
if (kept.length === 0 && maxKept > 0) {
|
|
86
|
+
const fallback = lowRelevance.shift();
|
|
87
|
+
if (fallback !== undefined) {
|
|
88
|
+
kept.push(fallback);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
appendLowRelevanceOmissions(omittedEntries, lowRelevance, nowMs);
|
|
92
|
+
kept.sort(compareKept);
|
|
93
|
+
const overflow = kept.splice(maxKept);
|
|
94
|
+
for (const overflowed of overflow) {
|
|
95
|
+
omittedEntries.push(toOmittedEntry(overflowed.scopePath, "budget-exhausted", nowMs));
|
|
96
|
+
}
|
|
97
|
+
omittedEntries.sort(compareOmitted);
|
|
98
|
+
return { kept, omitted: omittedEntries };
|
|
99
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { ExtractedSignals, RankingHints, RankingInput } from "./signals.js";
|
|
2
|
+
export { DEFAULT_GENERATED_PATTERNS, extractSignals } from "./signals.js";
|
|
3
|
+
export type { ScoringWeights } from "./scoring.js";
|
|
4
|
+
export { DEFAULT_SCORING_WEIGHTS, computeScore } from "./scoring.js";
|
|
5
|
+
export type { AnnotatedCandidate, FilterOptions, FilterResult } from "./filter.js";
|
|
6
|
+
export { DEFAULT_FILTER_OPTIONS, filterCandidates } from "./filter.js";
|
|
7
|
+
export type { RankingDiagnostics, RankingOptions, RankingResult } from "./rank.js";
|
|
8
|
+
export { rankCandidates } from "./rank.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ranking/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE1E,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAErE,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEvE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public sub-barrel for deterministic hybrid candidate ranking and negative context
|
|
2
|
+
// filtering (Epic #177, Issue #182). External consumers import every ranking symbol
|
|
3
|
+
// through this module; internal modules (signals.ts, scoring.ts, filter.ts, rank.ts)
|
|
4
|
+
// remain implementation detail.
|
|
5
|
+
export { DEFAULT_GENERATED_PATTERNS, extractSignals } from "./signals.js";
|
|
6
|
+
export { DEFAULT_SCORING_WEIGHTS, computeScore } from "./scoring.js";
|
|
7
|
+
export { DEFAULT_FILTER_OPTIONS, filterCandidates } from "./filter.js";
|
|
8
|
+
export { rankCandidates } from "./rank.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CandidateOmissionReason } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
2
|
+
import { type FilterOptions, type FilterResult } from "./filter.js";
|
|
3
|
+
import { type ScoringWeights } from "./scoring.js";
|
|
4
|
+
import { type RankingInput } from "./signals.js";
|
|
5
|
+
export interface RankingOptions {
|
|
6
|
+
readonly weights?: ScoringWeights;
|
|
7
|
+
readonly filter?: Omit<FilterOptions, "nowMs">;
|
|
8
|
+
readonly nowMs?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export interface RankingDiagnostics {
|
|
11
|
+
readonly totalAtoms: number;
|
|
12
|
+
readonly uniqueCandidates: number;
|
|
13
|
+
readonly keptCount: number;
|
|
14
|
+
readonly omittedCounts: Readonly<Record<CandidateOmissionReason, number>>;
|
|
15
|
+
readonly elapsedMs: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RankingResult extends FilterResult {
|
|
18
|
+
readonly diagnostics: RankingDiagnostics;
|
|
19
|
+
}
|
|
20
|
+
export declare function rankCandidates(input: RankingInput, options?: RankingOptions): RankingResult;
|
|
21
|
+
//# sourceMappingURL=rank.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../src/ranking/rank.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,uBAAuB,EAG7B,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAyC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;CAC1C;AA+JD,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAyB/F"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Public facade for candidate ranking and negative-context filtering (Epic #177, Issue #182).
|
|
2
|
+
// Pure composition of signals → scoring → filter. Validates scopePath via the contracts'
|
|
3
|
+
// isValidScopePath (defense-in-depth) and accounts every CANDIDATE_OMISSION_REASONS key in
|
|
4
|
+
// the returned diagnostics so UI/audit consumers can render zeros without conditionals.
|
|
5
|
+
import { CANDIDATE_OMISSION_REASONS, isValidScopePath, } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
6
|
+
import { DEFAULT_FILTER_OPTIONS, filterCandidates, } from "./filter.js";
|
|
7
|
+
import { DEFAULT_SCORING_WEIGHTS, computeScore } from "./scoring.js";
|
|
8
|
+
import { DEFAULT_GENERATED_PATTERNS, extractSignals, } from "./signals.js";
|
|
9
|
+
const AUTO_DUPLICATE_CLUSTER_MIN = 3;
|
|
10
|
+
const AUTO_DUPLICATE_BASENAME_EXEMPTIONS = new Set([
|
|
11
|
+
"agents.md",
|
|
12
|
+
"package.json",
|
|
13
|
+
"readme.md",
|
|
14
|
+
"tsconfig.json",
|
|
15
|
+
]);
|
|
16
|
+
function resolveHints(hints) {
|
|
17
|
+
return {
|
|
18
|
+
generatedPathPatterns: hints?.generatedPathPatterns ?? DEFAULT_GENERATED_PATTERNS,
|
|
19
|
+
duplicateOf: hints?.duplicateOf ?? new Map(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function resolveFilterOptions(filter, nowMs) {
|
|
23
|
+
const base = filter ?? DEFAULT_FILTER_OPTIONS;
|
|
24
|
+
return {
|
|
25
|
+
minScore: base.minScore,
|
|
26
|
+
maxKept: base.maxKept,
|
|
27
|
+
omitGenerated: base.omitGenerated,
|
|
28
|
+
omitNearDuplicates: base.omitNearDuplicates,
|
|
29
|
+
nowMs,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function groupAtomsByPath(atoms) {
|
|
33
|
+
const valid = new Map();
|
|
34
|
+
const invalidPaths = new Set();
|
|
35
|
+
for (const candidate of atoms) {
|
|
36
|
+
if (!isValidScopePath(candidate.scopePath, { mustBeRelative: true })) {
|
|
37
|
+
invalidPaths.add(candidate.scopePath);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const existing = valid.get(candidate.scopePath);
|
|
41
|
+
if (existing === undefined) {
|
|
42
|
+
valid.set(candidate.scopePath, [candidate]);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
existing.push(candidate);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { valid, invalidPaths: [...invalidPaths] };
|
|
49
|
+
}
|
|
50
|
+
function basename(scopePath) {
|
|
51
|
+
const index = scopePath.lastIndexOf("/");
|
|
52
|
+
return index >= 0 ? scopePath.slice(index + 1) : scopePath;
|
|
53
|
+
}
|
|
54
|
+
function bestAtomScore(atoms) {
|
|
55
|
+
return atoms.reduce((best, atom) => Math.max(best, atom.score), 0);
|
|
56
|
+
}
|
|
57
|
+
function compareCanonicalCandidate(a, b) {
|
|
58
|
+
const scoreDelta = bestAtomScore(b[1]) - bestAtomScore(a[1]);
|
|
59
|
+
if (scoreDelta !== 0) {
|
|
60
|
+
return scoreDelta;
|
|
61
|
+
}
|
|
62
|
+
if (a[0].length !== b[0].length) {
|
|
63
|
+
return a[0].length - b[0].length;
|
|
64
|
+
}
|
|
65
|
+
return a[0].localeCompare(b[0]);
|
|
66
|
+
}
|
|
67
|
+
function deriveDuplicateHints(group, explicit) {
|
|
68
|
+
const derived = new Map(explicit);
|
|
69
|
+
const clusters = new Map();
|
|
70
|
+
for (const entry of group.entries()) {
|
|
71
|
+
const [scopePath] = entry;
|
|
72
|
+
if (derived.has(scopePath)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const key = basename(scopePath).toLowerCase();
|
|
76
|
+
if (AUTO_DUPLICATE_BASENAME_EXEMPTIONS.has(key)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const existing = clusters.get(key);
|
|
80
|
+
if (existing === undefined) {
|
|
81
|
+
clusters.set(key, [entry]);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
existing.push(entry);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const entries of clusters.values()) {
|
|
88
|
+
if (entries.length < AUTO_DUPLICATE_CLUSTER_MIN) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const [canonical, ...duplicates] = [...entries].sort(compareCanonicalCandidate);
|
|
92
|
+
if (canonical === undefined) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const [scopePath] of duplicates) {
|
|
96
|
+
derived.set(scopePath, canonical[0]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return derived;
|
|
100
|
+
}
|
|
101
|
+
function buildAnnotated(group, input, hints, weights) {
|
|
102
|
+
const annotated = [];
|
|
103
|
+
for (const [scopePath, atomsForPath] of group) {
|
|
104
|
+
const signals = extractSignals(atomsForPath, input.anchors, hints);
|
|
105
|
+
const score = computeScore(signals, weights);
|
|
106
|
+
const candidate = {
|
|
107
|
+
scopePath,
|
|
108
|
+
score,
|
|
109
|
+
signals: signals.signals,
|
|
110
|
+
omitted: undefined,
|
|
111
|
+
};
|
|
112
|
+
annotated.push({
|
|
113
|
+
candidate,
|
|
114
|
+
generatedHint: signals.generatedHint,
|
|
115
|
+
duplicate: hints.duplicateOf.has(scopePath),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return annotated;
|
|
119
|
+
}
|
|
120
|
+
function emptyOmittedCounts() {
|
|
121
|
+
const counts = {};
|
|
122
|
+
for (const reason of CANDIDATE_OMISSION_REASONS) {
|
|
123
|
+
counts[reason] = 0;
|
|
124
|
+
}
|
|
125
|
+
return counts;
|
|
126
|
+
}
|
|
127
|
+
function tallyOmittedCounts(omitted, outsideScopeCount) {
|
|
128
|
+
const counts = emptyOmittedCounts();
|
|
129
|
+
for (const entry of omitted) {
|
|
130
|
+
counts[entry.reason] += 1;
|
|
131
|
+
}
|
|
132
|
+
counts["outside-scope"] += outsideScopeCount;
|
|
133
|
+
return counts;
|
|
134
|
+
}
|
|
135
|
+
export function rankCandidates(input, options = {}) {
|
|
136
|
+
const clock = options.nowMs ?? Date.now;
|
|
137
|
+
// Capture a single emission timestamp so every OmittedContextEntry created by this call carries
|
|
138
|
+
// the same omittedAtMs. The end-of-run clock read below measures elapsed time only.
|
|
139
|
+
const startMs = clock();
|
|
140
|
+
const frozenStartMs = () => startMs;
|
|
141
|
+
const hints = resolveHints(input.hints);
|
|
142
|
+
const weights = options.weights ?? DEFAULT_SCORING_WEIGHTS;
|
|
143
|
+
const { valid, invalidPaths } = groupAtomsByPath(input.atoms);
|
|
144
|
+
const rankingHints = { ...hints, duplicateOf: deriveDuplicateHints(valid, hints.duplicateOf) };
|
|
145
|
+
const annotated = buildAnnotated(valid, input, rankingHints, weights);
|
|
146
|
+
const filterOptions = resolveFilterOptions(options.filter, frozenStartMs);
|
|
147
|
+
const filterResult = filterCandidates(annotated, filterOptions);
|
|
148
|
+
// Invalid paths cannot be represented as OmittedContextEntry values without breaking
|
|
149
|
+
// ConnectedContextPack validation, so keep them diagnostics-only.
|
|
150
|
+
const omittedCounts = tallyOmittedCounts(filterResult.omitted, invalidPaths.length);
|
|
151
|
+
const elapsedMs = Math.max(0, clock() - startMs);
|
|
152
|
+
const diagnostics = {
|
|
153
|
+
totalAtoms: input.atoms.length,
|
|
154
|
+
uniqueCandidates: valid.size,
|
|
155
|
+
keptCount: filterResult.kept.length,
|
|
156
|
+
omittedCounts,
|
|
157
|
+
elapsedMs,
|
|
158
|
+
};
|
|
159
|
+
return { kept: filterResult.kept, omitted: filterResult.omitted, diagnostics };
|
|
160
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExtractedSignals } from "./signals.js";
|
|
2
|
+
export interface ScoringWeights {
|
|
3
|
+
readonly provenanceBestScore: number;
|
|
4
|
+
readonly provenanceCount: number;
|
|
5
|
+
readonly anchorOverlap: number;
|
|
6
|
+
readonly pathDepthAffinity: number;
|
|
7
|
+
readonly testPairBonus: number;
|
|
8
|
+
readonly stacktracePositionBonus: number;
|
|
9
|
+
readonly generatedPenalty: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const DEFAULT_SCORING_WEIGHTS: ScoringWeights;
|
|
12
|
+
export declare function computeScore(signals: ExtractedSignals, weights?: ScoringWeights): number;
|
|
13
|
+
//# sourceMappingURL=scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/ranking/scoring.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,eAAO,MAAM,uBAAuB,EAAE,cAQ5B,CAAC;AAgBX,wBAAgB,YAAY,CAC1B,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,cAAwC,GAChD,MAAM,CAUR"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Weighted scoring composition for ranked candidates (Epic #177, Issue #182).
|
|
2
|
+
// Pure function: signal vector × weight vector → clamped unit score. The default positive
|
|
3
|
+
// weights sum to 0.95 and the generated penalty weight is 0.30; the filter layer's
|
|
4
|
+
// `omitGenerated` default keeps generated files OUT of the kept set regardless of score,
|
|
5
|
+
// so the scoring penalty is a secondary defence (a fully-positive generated file scores
|
|
6
|
+
// 0.65). Callers may override weights to tune ring-specific behaviour; never uses
|
|
7
|
+
// parseFloat or .toFixed.
|
|
8
|
+
export const DEFAULT_SCORING_WEIGHTS = {
|
|
9
|
+
provenanceBestScore: 0.35,
|
|
10
|
+
provenanceCount: 0.1,
|
|
11
|
+
anchorOverlap: 0.25,
|
|
12
|
+
pathDepthAffinity: 0.1,
|
|
13
|
+
testPairBonus: 0.1,
|
|
14
|
+
stacktracePositionBonus: 0.05,
|
|
15
|
+
generatedPenalty: 0.3,
|
|
16
|
+
};
|
|
17
|
+
const SIGNAL_WEIGHT_KEYS = {
|
|
18
|
+
"provenance-best-score": "provenanceBestScore",
|
|
19
|
+
"provenance-count": "provenanceCount",
|
|
20
|
+
"anchor-overlap": "anchorOverlap",
|
|
21
|
+
"path-depth-affinity": "pathDepthAffinity",
|
|
22
|
+
"test-pair-bonus": "testPairBonus",
|
|
23
|
+
"stacktrace-position-bonus": "stacktracePositionBonus",
|
|
24
|
+
"generated-penalty": "generatedPenalty",
|
|
25
|
+
};
|
|
26
|
+
function clampUnit(value) {
|
|
27
|
+
return Math.max(0, Math.min(1, value));
|
|
28
|
+
}
|
|
29
|
+
export function computeScore(signals, weights = DEFAULT_SCORING_WEIGHTS) {
|
|
30
|
+
let raw = 0;
|
|
31
|
+
for (const signal of signals.signals) {
|
|
32
|
+
const key = SIGNAL_WEIGHT_KEYS[signal.name];
|
|
33
|
+
if (key === undefined) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
raw += signal.value * weights[key];
|
|
37
|
+
}
|
|
38
|
+
return clampUnit(raw);
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CandidateSignal, EvidenceAtom } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
2
|
+
import type { SearchAnchor } from "../planner/index.js";
|
|
3
|
+
export interface RankingInput {
|
|
4
|
+
readonly atoms: readonly EvidenceAtom[];
|
|
5
|
+
readonly anchors: readonly SearchAnchor[];
|
|
6
|
+
readonly hints?: RankingHints;
|
|
7
|
+
}
|
|
8
|
+
export interface RankingHints {
|
|
9
|
+
readonly generatedPathPatterns?: readonly string[];
|
|
10
|
+
readonly duplicateOf?: ReadonlyMap<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare const DEFAULT_GENERATED_PATTERNS: readonly string[];
|
|
13
|
+
export interface ExtractedSignals {
|
|
14
|
+
readonly scopePath: string;
|
|
15
|
+
readonly signals: readonly CandidateSignal[];
|
|
16
|
+
readonly baseScore: number;
|
|
17
|
+
readonly generatedHint: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function extractSignals(atomsForPath: readonly EvidenceAtom[], anchors: readonly SearchAnchor[], hints: Required<RankingHints>): ExtractedSignals;
|
|
20
|
+
//# sourceMappingURL=signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../../src/ranking/signals.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACb,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAS9C,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAyHD,wBAAgB,cAAc,CAC5B,YAAY,EAAE,SAAS,YAAY,EAAE,EACrC,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC5B,gBAAgB,CAuBlB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Per-candidate signal extraction for the deterministic hybrid ranker (Epic #177, Issue #182).
|
|
2
|
+
// Pure JS — no IO, no clock, no randomness. The signal name list is fixed; the scoring layer
|
|
3
|
+
// maps weights to signals by NAME (see SIGNAL_WEIGHT_KEYS in scoring.ts), not by position.
|
|
4
|
+
// Penalties are emitted alongside positive signals so the scorer treats them uniformly;
|
|
5
|
+
// downstream code interprets values via signal names only.
|
|
6
|
+
export const DEFAULT_GENERATED_PATTERNS = [
|
|
7
|
+
"/dist/",
|
|
8
|
+
"/build/",
|
|
9
|
+
"/.next/",
|
|
10
|
+
"/coverage/",
|
|
11
|
+
"/__snapshots__/",
|
|
12
|
+
".min.js",
|
|
13
|
+
".bundle.js",
|
|
14
|
+
".d.ts.map",
|
|
15
|
+
];
|
|
16
|
+
// Fixed regex for stack-frame "at fn (path:line:col)" and "at path:line:col" detection.
|
|
17
|
+
// No user input is interpolated.
|
|
18
|
+
const STACK_FRAME_RE = /^\s*at\s+(?:\S+\s+\(([^\s:]+):\d+(?::\d+)?\)|([^\s:]+):\d+(?::\d+)?)/;
|
|
19
|
+
function clampUnit(value) {
|
|
20
|
+
return Math.max(0, Math.min(1, value));
|
|
21
|
+
}
|
|
22
|
+
function detectGenerated(scopePath, patterns) {
|
|
23
|
+
// Scope paths are workspace-relative (no leading "/"), so a pattern like "/dist/" would
|
|
24
|
+
// miss a root-level "dist/foo.js". Match leading "<pattern-without-slash>/" against the
|
|
25
|
+
// path start AND the original "/<pattern>/" anywhere inside the path. .min.js / .bundle.js
|
|
26
|
+
// / .d.ts.map patterns (no leading "/") use a plain substring match.
|
|
27
|
+
const lower = scopePath.toLowerCase();
|
|
28
|
+
for (const pattern of patterns) {
|
|
29
|
+
const p = pattern.toLowerCase();
|
|
30
|
+
if (lower.includes(p)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (p.startsWith("/") && p.endsWith("/")) {
|
|
34
|
+
const stripped = p.slice(1);
|
|
35
|
+
if (lower.startsWith(stripped)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
function computeProvenanceBestScore(atoms) {
|
|
43
|
+
if (atoms.length === 0) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
let best = 0;
|
|
47
|
+
for (const candidate of atoms) {
|
|
48
|
+
if (candidate.score > best) {
|
|
49
|
+
best = candidate.score;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return clampUnit(best);
|
|
53
|
+
}
|
|
54
|
+
function computeProvenanceCount(atoms) {
|
|
55
|
+
return Math.min(atoms.length, 10) / 10;
|
|
56
|
+
}
|
|
57
|
+
function computeAnchorOverlap(scopePath, anchors) {
|
|
58
|
+
if (anchors.length === 0) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
const lowerPath = scopePath.toLowerCase();
|
|
62
|
+
let hits = 0;
|
|
63
|
+
for (const anc of anchors) {
|
|
64
|
+
if (anc.term.length > 0 && lowerPath.includes(anc.term.toLowerCase())) {
|
|
65
|
+
hits += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return clampUnit(hits / anchors.length);
|
|
69
|
+
}
|
|
70
|
+
function computePathDepthAffinity(scopePath) {
|
|
71
|
+
if (scopePath.length === 0) {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
const depth = scopePath.split("/").length - 1;
|
|
75
|
+
return 1 / (1 + depth);
|
|
76
|
+
}
|
|
77
|
+
function computeTestPairBonus(scopePath, anchors) {
|
|
78
|
+
const isTest = scopePath.endsWith(".test.ts") || scopePath.endsWith(".spec.ts");
|
|
79
|
+
if (!isTest) {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
const sourcePath = scopePath.replace(/\.test\.ts$/, ".ts").replace(/\.spec\.ts$/, ".ts");
|
|
83
|
+
const lowerSourcePath = sourcePath.toLowerCase();
|
|
84
|
+
for (const anc of anchors) {
|
|
85
|
+
if (anc.kind === "path" && anc.term.toLowerCase() === lowerSourcePath) {
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
function computeStacktracePositionBonus(scopePath, anchors) {
|
|
92
|
+
if (scopePath.length === 0) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
// The planner lowercases anchor terms, so we compare case-insensitively here too. A
|
|
96
|
+
// direct equality on the captured path would miss legitimate matches whose source file
|
|
97
|
+
// name has uppercase characters (and would also be inconsistent on case-insensitive
|
|
98
|
+
// filesystems like macOS/Windows).
|
|
99
|
+
const lowerScopePath = scopePath.toLowerCase();
|
|
100
|
+
for (const anc of anchors) {
|
|
101
|
+
if (anc.kind !== "quoted") {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const match = STACK_FRAME_RE.exec(anc.term);
|
|
105
|
+
if (match === null) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const framePath = match[1] ?? match[2];
|
|
109
|
+
if (framePath?.toLowerCase() === lowerScopePath) {
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
function deriveScopePath(atoms) {
|
|
116
|
+
if (atoms.length === 0) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
const first = atoms[0];
|
|
120
|
+
return first === undefined ? "" : first.scopePath;
|
|
121
|
+
}
|
|
122
|
+
export function extractSignals(atomsForPath, anchors, hints) {
|
|
123
|
+
const scopePath = deriveScopePath(atomsForPath);
|
|
124
|
+
const generatedHint = detectGenerated(scopePath, hints.generatedPathPatterns);
|
|
125
|
+
const provBest = computeProvenanceBestScore(atomsForPath);
|
|
126
|
+
const provCount = computeProvenanceCount(atomsForPath);
|
|
127
|
+
const overlap = computeAnchorOverlap(scopePath, anchors);
|
|
128
|
+
const depthAff = computePathDepthAffinity(scopePath);
|
|
129
|
+
const testBonus = computeTestPairBonus(scopePath, anchors);
|
|
130
|
+
const stackBonus = computeStacktracePositionBonus(scopePath, anchors);
|
|
131
|
+
const penalty = generatedHint ? -1 : 0;
|
|
132
|
+
const signals = [
|
|
133
|
+
{ name: "provenance-best-score", value: provBest },
|
|
134
|
+
{ name: "provenance-count", value: provCount },
|
|
135
|
+
{ name: "anchor-overlap", value: overlap },
|
|
136
|
+
{ name: "path-depth-affinity", value: depthAff },
|
|
137
|
+
{ name: "test-pair-bonus", value: testBonus },
|
|
138
|
+
{ name: "stacktrace-position-bonus", value: stackBonus },
|
|
139
|
+
{ name: "generated-penalty", value: penalty },
|
|
140
|
+
];
|
|
141
|
+
const positives = [provBest, provCount, overlap, depthAff, testBonus, stackBonus];
|
|
142
|
+
const positiveMean = positives.reduce((acc, n) => acc + n, 0) / positives.length;
|
|
143
|
+
const baseScore = clampUnit(positiveMean + penalty);
|
|
144
|
+
return { scopePath, signals, baseScore, generatedHint };
|
|
145
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ContextPack, type WorkspaceFs, type WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
|
|
2
|
+
import type { UnitTestWorkflowInput, WorkflowLimits } from "./types.js";
|
|
3
|
+
export interface TestGenContextDeps {
|
|
4
|
+
readonly fs?: WorkspaceFs | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildTestGenContext(workspace: WorkspaceInfo, input: UnitTestWorkflowInput, limits: WorkflowLimits, deps?: TestGenContextDeps): ContextPack;
|
|
7
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/unit-tests/context.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,KAAK,WAAW,EAOhB,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAkB,qBAAqB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAExF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACvC;AAsJD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,qBAAqB,EAC5B,MAAM,EAAE,cAAc,EACtB,IAAI,GAAE,kBAAuB,GAC5B,WAAW,CAcb"}
|