@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,15 @@
|
|
|
1
|
+
import type { CandidateFile, EvidenceAtom } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
2
|
+
export type RerankerAvailability = {
|
|
3
|
+
readonly available: true;
|
|
4
|
+
readonly modelLabel: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly available: false;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
};
|
|
9
|
+
export interface RerankerSeam {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
isAvailable(): Promise<RerankerAvailability>;
|
|
12
|
+
rerank(candidates: readonly CandidateFile[], atomsByPath: ReadonlyMap<string, readonly EvidenceAtom[]>, topK: number): Promise<readonly CandidateFile[]>;
|
|
13
|
+
}
|
|
14
|
+
export declare const disabledReranker: RerankerSeam;
|
|
15
|
+
//# sourceMappingURL=reranker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reranker.d.ts","sourceRoot":"","sources":["../../src/contextpack/reranker.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iDAAiD,CAAC;AAEnG,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,WAAW,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC7C,MAAM,CACJ,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC,EACzD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;CACtC;AA2BD,eAAO,MAAM,gBAAgB,EAAE,YAI9B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Optional semantic reranker seam for the connected-context assembler (Epic #177,
|
|
2
|
+
// Issue #183). Interface ONLY — no live model calls in this PR. A future PR will add a
|
|
3
|
+
// keiko-model-gateway-backed implementation behind capability + privacy + budget guards.
|
|
4
|
+
// The default `disabledReranker` is always unavailable and the assembler treats that as
|
|
5
|
+
// "skip reranking" rather than as an error.
|
|
6
|
+
// No `await` in the body — use the resolve/reject pattern from prior PRs to avoid the
|
|
7
|
+
// `@typescript-eslint/require-await` lint while preserving the async surface.
|
|
8
|
+
function unavailable() {
|
|
9
|
+
try {
|
|
10
|
+
return Promise.resolve({
|
|
11
|
+
available: false,
|
|
12
|
+
reason: "reranker-not-configured",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function identity(candidates, _atomsByPath, _topK) {
|
|
20
|
+
try {
|
|
21
|
+
return Promise.resolve(candidates);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export const disabledReranker = {
|
|
28
|
+
name: "disabled-reranker",
|
|
29
|
+
isAvailable: unavailable,
|
|
30
|
+
rerank: identity,
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../src/descriptor.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ProposedPatchEntry, type WorkflowHandoffRequest } from "@oscharko-dev/keiko-contracts/workflow-handoff";
|
|
2
|
+
import type { PatchValidation, WorkspaceWriter } from "@oscharko-dev/keiko-tools";
|
|
3
|
+
export declare function proposedPatchEntriesFromValidation(validation: PatchValidation): readonly ProposedPatchEntry[];
|
|
4
|
+
export declare function governedPatchRejectionCode(handoff: WorkflowHandoffRequest | undefined, validation: PatchValidation): string | undefined;
|
|
5
|
+
export declare function createScopedWriter(baseWriter: WorkspaceWriter, workspaceRoot: string, editablePaths: readonly string[]): WorkspaceWriter;
|
|
6
|
+
//# sourceMappingURL=governed-handoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governed-handoff.d.ts","sourceRoot":"","sources":["../src/governed-handoff.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,gDAAgD,CAAC;AACxD,OAAO,KAAK,EAAmB,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAwCnG,wBAAgB,kCAAkC,CAChD,UAAU,EAAE,eAAe,GAC1B,SAAS,kBAAkB,EAAE,CAkB/B;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,sBAAsB,GAAG,SAAS,EAC3C,UAAU,EAAE,eAAe,GAC1B,MAAM,GAAG,SAAS,CASpB;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,eAAe,EAC3B,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,eAAe,CA4BjB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { sep } from "node:path";
|
|
2
|
+
import { checkPatchAgainstScope, } from "@oscharko-dev/keiko-contracts/workflow-handoff";
|
|
3
|
+
import { resolveWithinWorkspace } from "@oscharko-dev/keiko-workspace";
|
|
4
|
+
function fileHeader(file) {
|
|
5
|
+
if (file.kind === "create") {
|
|
6
|
+
return ["--- /dev/null", `+++ b/${file.path}`];
|
|
7
|
+
}
|
|
8
|
+
if (file.kind === "delete") {
|
|
9
|
+
return [`--- a/${file.path}`, "+++ /dev/null"];
|
|
10
|
+
}
|
|
11
|
+
return [`--- a/${file.path}`, `+++ b/${file.path}`];
|
|
12
|
+
}
|
|
13
|
+
function filePatchBytes(file) {
|
|
14
|
+
const lines = [...fileHeader(file)];
|
|
15
|
+
for (const hunk of file.hunks) {
|
|
16
|
+
lines.push(`@@ -${String(hunk.oldStart)},${String(hunk.oldLines)} +${String(hunk.newStart)},${String(hunk.newLines)} @@`);
|
|
17
|
+
lines.push(...hunk.lines);
|
|
18
|
+
}
|
|
19
|
+
return Buffer.byteLength(lines.join("\n"), "utf8");
|
|
20
|
+
}
|
|
21
|
+
function absoluteEditablePaths(workspaceRoot, editablePaths) {
|
|
22
|
+
return new Set(editablePaths.map((path) => resolveWithinWorkspace(workspaceRoot, path)));
|
|
23
|
+
}
|
|
24
|
+
function canCreateDirectory(dir, allowedFiles) {
|
|
25
|
+
for (const allowed of allowedFiles) {
|
|
26
|
+
if (allowed === dir || allowed.startsWith(`${dir}${sep}`)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
export function proposedPatchEntriesFromValidation(validation) {
|
|
33
|
+
if (validation.files.length === 0) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const entries = validation.files.map((file) => ({
|
|
37
|
+
path: file.path,
|
|
38
|
+
newFile: file.kind === "create",
|
|
39
|
+
patchBytes: filePatchBytes(file),
|
|
40
|
+
}));
|
|
41
|
+
const observed = entries.reduce((sum, entry) => sum + entry.patchBytes, 0);
|
|
42
|
+
const delta = Math.max(0, validation.totalBytes - observed);
|
|
43
|
+
if (delta > 0) {
|
|
44
|
+
const last = entries[entries.length - 1];
|
|
45
|
+
if (last !== undefined) {
|
|
46
|
+
last.patchBytes += delta;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return entries;
|
|
50
|
+
}
|
|
51
|
+
export function governedPatchRejectionCode(handoff, validation) {
|
|
52
|
+
if (handoff === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const scopeCheck = checkPatchAgainstScope(handoff.patchScope, proposedPatchEntriesFromValidation(validation));
|
|
56
|
+
return scopeCheck.ok ? undefined : "out-of-scope";
|
|
57
|
+
}
|
|
58
|
+
export function createScopedWriter(baseWriter, workspaceRoot, editablePaths) {
|
|
59
|
+
const allowedFiles = absoluteEditablePaths(workspaceRoot, editablePaths);
|
|
60
|
+
const assertAllowed = (absolutePath) => {
|
|
61
|
+
if (!allowedFiles.has(absolutePath)) {
|
|
62
|
+
throw new Error(`Patch scope forbids writing ${absolutePath}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
writeFileUtf8(absolutePath, content) {
|
|
67
|
+
assertAllowed(absolutePath);
|
|
68
|
+
baseWriter.writeFileUtf8(absolutePath, content);
|
|
69
|
+
},
|
|
70
|
+
mkdirp(absoluteDir) {
|
|
71
|
+
if (!canCreateDirectory(absoluteDir, allowedFiles)) {
|
|
72
|
+
throw new Error(`Patch scope forbids creating ${absoluteDir}`);
|
|
73
|
+
}
|
|
74
|
+
baseWriter.mkdirp(absoluteDir);
|
|
75
|
+
},
|
|
76
|
+
remove(absolutePath) {
|
|
77
|
+
assertAllowed(absolutePath);
|
|
78
|
+
baseWriter.remove(absolutePath);
|
|
79
|
+
},
|
|
80
|
+
rename(fromAbsolute, toAbsolute) {
|
|
81
|
+
assertAllowed(fromAbsolute);
|
|
82
|
+
assertAllowed(toAbsolute);
|
|
83
|
+
baseWriter.rename(fromAbsolute, toAbsolute);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { WorkflowDescriptor, WorkflowInputSpec } from "./descriptor.js";
|
|
2
|
+
export * from "./unit-tests/index.js";
|
|
3
|
+
export * from "./bug-investigation/index.js";
|
|
4
|
+
export * from "./planner/index.js";
|
|
5
|
+
export * from "./ranking/index.js";
|
|
6
|
+
export * from "./contextpack/index.js";
|
|
7
|
+
export * from "./qualityIntelligence/index.js";
|
|
8
|
+
export * from "./promptEnhancer/index.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE7E,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAG7C,cAAc,oBAAoB,CAAC;AAGnC,cAAc,oBAAoB,CAAC;AAGnC,cAAc,wBAAwB,CAAC;AAIvC,cAAc,gCAAgC,CAAC;AAG/C,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./unit-tests/index.js";
|
|
2
|
+
export * from "./bug-investigation/index.js";
|
|
3
|
+
// ─── Exploration planner & budget governor (Issue #181 / Epic #177) ──────────
|
|
4
|
+
export * from "./planner/index.js";
|
|
5
|
+
// ─── Candidate ranking & negative context filter (Issue #182 / Epic #177) ────
|
|
6
|
+
export * from "./ranking/index.js";
|
|
7
|
+
// ─── Context-pack assembler & micro-index (Issue #183 / Epic #177) ──────────
|
|
8
|
+
export * from "./contextpack/index.js";
|
|
9
|
+
// ─── Quality Intelligence workflow execution (Epic #270, Issue #273/#279) ────
|
|
10
|
+
// Scripted + model-routed run entries, descriptors, cancellation, and the run-lifecycle types.
|
|
11
|
+
export * from "./qualityIntelligence/index.js";
|
|
12
|
+
// ─── Prompt Enhancer workflow execution (Epic #1307, Issue #1314) ─────────────
|
|
13
|
+
export * from "./promptEnhancer/index.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type SearchAnchorKind = "literal" | "identifier" | "path" | "quoted";
|
|
2
|
+
export interface SearchAnchor {
|
|
3
|
+
readonly term: string;
|
|
4
|
+
readonly weight: number;
|
|
5
|
+
readonly kind: SearchAnchorKind;
|
|
6
|
+
}
|
|
7
|
+
export interface AnchorExtractionInput {
|
|
8
|
+
readonly text: string;
|
|
9
|
+
readonly maxAnchors: number;
|
|
10
|
+
}
|
|
11
|
+
export interface AnchorExtractionResult {
|
|
12
|
+
readonly anchors: readonly SearchAnchor[];
|
|
13
|
+
readonly truncated: boolean;
|
|
14
|
+
readonly tokensConsidered: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function extractAnchors(input: AnchorExtractionInput): AnchorExtractionResult;
|
|
17
|
+
//# sourceMappingURL=anchors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchors.d.ts","sourceRoot":"","sources":["../../src/planner/anchors.ts"],"names":[],"mappings":"AA+LA,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAgHD,wBAAgB,cAAc,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAoBnF"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Deterministic search-anchor extraction for the exploration planner (Epic #177, Issue #181).
|
|
2
|
+
// Pure JS — no IO, no clock, no randomness. Given free-form prompt text, this module produces
|
|
3
|
+
// a small, stable, weight-ordered set of search anchors. The stop-word list is intentionally
|
|
4
|
+
// fixed and English-only; expanding language coverage is a follow-up issue.
|
|
5
|
+
const MAX_INPUT_LENGTH = 4096;
|
|
6
|
+
const STOP_WORDS = new Set([
|
|
7
|
+
"the",
|
|
8
|
+
"and",
|
|
9
|
+
"for",
|
|
10
|
+
"with",
|
|
11
|
+
"from",
|
|
12
|
+
"this",
|
|
13
|
+
"that",
|
|
14
|
+
"what",
|
|
15
|
+
"where",
|
|
16
|
+
"when",
|
|
17
|
+
"which",
|
|
18
|
+
"have",
|
|
19
|
+
"has",
|
|
20
|
+
"had",
|
|
21
|
+
"are",
|
|
22
|
+
"was",
|
|
23
|
+
"were",
|
|
24
|
+
"is",
|
|
25
|
+
"be",
|
|
26
|
+
"been",
|
|
27
|
+
"being",
|
|
28
|
+
"do",
|
|
29
|
+
"does",
|
|
30
|
+
"did",
|
|
31
|
+
"doing",
|
|
32
|
+
"of",
|
|
33
|
+
"in",
|
|
34
|
+
"on",
|
|
35
|
+
"at",
|
|
36
|
+
"to",
|
|
37
|
+
"an",
|
|
38
|
+
"as",
|
|
39
|
+
"or",
|
|
40
|
+
"but",
|
|
41
|
+
"not",
|
|
42
|
+
"no",
|
|
43
|
+
"yes",
|
|
44
|
+
"if",
|
|
45
|
+
"by",
|
|
46
|
+
"it",
|
|
47
|
+
"its",
|
|
48
|
+
"you",
|
|
49
|
+
"your",
|
|
50
|
+
"we",
|
|
51
|
+
"our",
|
|
52
|
+
"they",
|
|
53
|
+
"their",
|
|
54
|
+
"them",
|
|
55
|
+
"he",
|
|
56
|
+
"she",
|
|
57
|
+
"his",
|
|
58
|
+
"her",
|
|
59
|
+
"my",
|
|
60
|
+
"me",
|
|
61
|
+
"i",
|
|
62
|
+
"us",
|
|
63
|
+
"how",
|
|
64
|
+
"why",
|
|
65
|
+
"who",
|
|
66
|
+
"whom",
|
|
67
|
+
"whose",
|
|
68
|
+
"than",
|
|
69
|
+
"then",
|
|
70
|
+
"there",
|
|
71
|
+
"can",
|
|
72
|
+
"could",
|
|
73
|
+
"would",
|
|
74
|
+
"should",
|
|
75
|
+
"may",
|
|
76
|
+
"might",
|
|
77
|
+
"must",
|
|
78
|
+
"will",
|
|
79
|
+
"so",
|
|
80
|
+
"such",
|
|
81
|
+
"any",
|
|
82
|
+
"all",
|
|
83
|
+
"some",
|
|
84
|
+
"every",
|
|
85
|
+
"each",
|
|
86
|
+
"aber",
|
|
87
|
+
"alle",
|
|
88
|
+
"als",
|
|
89
|
+
"am",
|
|
90
|
+
"an",
|
|
91
|
+
"auch",
|
|
92
|
+
"auf",
|
|
93
|
+
"aus",
|
|
94
|
+
"bei",
|
|
95
|
+
"bin",
|
|
96
|
+
"bis",
|
|
97
|
+
"bitte",
|
|
98
|
+
"da",
|
|
99
|
+
"das",
|
|
100
|
+
"dass",
|
|
101
|
+
"dein",
|
|
102
|
+
"deine",
|
|
103
|
+
"dem",
|
|
104
|
+
"den",
|
|
105
|
+
"der",
|
|
106
|
+
"des",
|
|
107
|
+
"die",
|
|
108
|
+
"dir",
|
|
109
|
+
"du",
|
|
110
|
+
"durch",
|
|
111
|
+
"ein",
|
|
112
|
+
"eine",
|
|
113
|
+
"einem",
|
|
114
|
+
"einen",
|
|
115
|
+
"einer",
|
|
116
|
+
"es",
|
|
117
|
+
"für",
|
|
118
|
+
"habe",
|
|
119
|
+
"haben",
|
|
120
|
+
"hat",
|
|
121
|
+
"ich",
|
|
122
|
+
"im",
|
|
123
|
+
"ist",
|
|
124
|
+
"kann",
|
|
125
|
+
"kannst",
|
|
126
|
+
"kein",
|
|
127
|
+
"keine",
|
|
128
|
+
"mit",
|
|
129
|
+
"mir",
|
|
130
|
+
"nach",
|
|
131
|
+
"nicht",
|
|
132
|
+
"noch",
|
|
133
|
+
"oder",
|
|
134
|
+
"sagen",
|
|
135
|
+
"sind",
|
|
136
|
+
"und",
|
|
137
|
+
"uns",
|
|
138
|
+
"von",
|
|
139
|
+
"war",
|
|
140
|
+
"was",
|
|
141
|
+
"welche",
|
|
142
|
+
"welchen",
|
|
143
|
+
"welcher",
|
|
144
|
+
"welches",
|
|
145
|
+
"wenn",
|
|
146
|
+
"wer",
|
|
147
|
+
"wie",
|
|
148
|
+
"wir",
|
|
149
|
+
"wird",
|
|
150
|
+
"wo",
|
|
151
|
+
"zu",
|
|
152
|
+
"zum",
|
|
153
|
+
"zur",
|
|
154
|
+
]);
|
|
155
|
+
// Module-scope regex pool. Each pattern uses character classes only (no nested quantifiers),
|
|
156
|
+
// so scanning is linear in input length — ReDoS-safe.
|
|
157
|
+
const QUOTED_DOUBLE_RE = /"([^"\n]+)"/g;
|
|
158
|
+
const QUOTED_SINGLE_RE = /'([^'\n]+)'/g;
|
|
159
|
+
const BACKTICK_RE = /`([^`\n]+)`/g;
|
|
160
|
+
const PATH_RE = /(?:[\w.-]+\/)+[\w.-]+\.[A-Za-z]{1,8}/g;
|
|
161
|
+
// Requires a genuine lower/digit -> upper transition so all-caps acronyms and SHOUTING words
|
|
162
|
+
// (WHY, HTTP, BROKEN) are NOT mistaken for code identifiers. A spurious 0.85 identifier anchor
|
|
163
|
+
// would both satisfy the clarification gate for a vague question and seed symbol-file retrieval
|
|
164
|
+
// with a non-symbol — see planner/plan.ts decideClarification and grounded symbolFileAnchorTerms.
|
|
165
|
+
const CAMEL_IDENTIFIER_RE = /\b([A-Za-z_$][A-Za-z0-9_$]*[a-z0-9][A-Z][A-Za-z0-9_$]*)\b/g;
|
|
166
|
+
const TOKEN_SPLIT_RE = /[^A-Za-z0-9_.]+/;
|
|
167
|
+
const TECHNICAL_TERM_PATTERNS = [
|
|
168
|
+
{ pattern: /\btype[\s_-]?script\b/gi, term: "typescript" },
|
|
169
|
+
{ pattern: /\bjava[\s_-]?script\b/gi, term: "javascript" },
|
|
170
|
+
{ pattern: /\bnode(?:\.js)?\b/gi, term: "node" },
|
|
171
|
+
{ pattern: /\bnext(?:\.js)?\b/gi, term: "nextjs" },
|
|
172
|
+
{ pattern: /\bpackage\.json\b/gi, term: "package.json" },
|
|
173
|
+
{ pattern: /\bpackage[\s_-]?manager\b/gi, term: "package-manager" },
|
|
174
|
+
{ pattern: /\btsconfig(?:\.[a-z0-9]+)?\b/gi, term: "tsconfig" },
|
|
175
|
+
{ pattern: /\bvitest\b/gi, term: "vitest" },
|
|
176
|
+
{ pattern: /\bvite\b/gi, term: "vite" },
|
|
177
|
+
{ pattern: /\bplaywright\b/gi, term: "playwright" },
|
|
178
|
+
{ pattern: /\bjest\b/gi, term: "jest" },
|
|
179
|
+
{ pattern: /\bcypress\b/gi, term: "cypress" },
|
|
180
|
+
{ pattern: /\breact\b/gi, term: "react" },
|
|
181
|
+
{ pattern: /\bnpm\b/gi, term: "npm" },
|
|
182
|
+
{ pattern: /\bpnpm\b/gi, term: "pnpm" },
|
|
183
|
+
{ pattern: /\byarn\b/gi, term: "yarn" },
|
|
184
|
+
];
|
|
185
|
+
function pushAnchor(out, raw, kind, weight) {
|
|
186
|
+
const term = raw.trim().toLowerCase();
|
|
187
|
+
if (term.length > 0) {
|
|
188
|
+
out.push({ term, weight, kind });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function collectMatches(source, pattern, kind, weight, out) {
|
|
192
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
193
|
+
const parts = [];
|
|
194
|
+
let cursor = 0;
|
|
195
|
+
let match = re.exec(source);
|
|
196
|
+
while (match !== null) {
|
|
197
|
+
const full = match[0];
|
|
198
|
+
const captured = match[1] ?? full;
|
|
199
|
+
pushAnchor(out, captured, kind, weight);
|
|
200
|
+
parts.push(source.slice(cursor, match.index));
|
|
201
|
+
parts.push(" ".repeat(full.length));
|
|
202
|
+
cursor = match.index + full.length;
|
|
203
|
+
match = re.exec(source);
|
|
204
|
+
}
|
|
205
|
+
parts.push(source.slice(cursor));
|
|
206
|
+
return parts.join("");
|
|
207
|
+
}
|
|
208
|
+
function collectTechnicalTerms(source, out) {
|
|
209
|
+
let remaining = source;
|
|
210
|
+
for (const entry of TECHNICAL_TERM_PATTERNS) {
|
|
211
|
+
const re = new RegExp(entry.pattern.source, entry.pattern.flags);
|
|
212
|
+
const parts = [];
|
|
213
|
+
let cursor = 0;
|
|
214
|
+
let match = re.exec(remaining);
|
|
215
|
+
while (match !== null) {
|
|
216
|
+
const full = match[0];
|
|
217
|
+
pushAnchor(out, entry.term, "identifier", 0.85);
|
|
218
|
+
parts.push(remaining.slice(cursor, match.index));
|
|
219
|
+
parts.push(" ".repeat(full.length));
|
|
220
|
+
cursor = match.index + full.length;
|
|
221
|
+
match = re.exec(remaining);
|
|
222
|
+
}
|
|
223
|
+
parts.push(remaining.slice(cursor));
|
|
224
|
+
remaining = parts.join("");
|
|
225
|
+
}
|
|
226
|
+
return remaining;
|
|
227
|
+
}
|
|
228
|
+
function tokenizeRemaining(remaining, out) {
|
|
229
|
+
let considered = 0;
|
|
230
|
+
for (const raw of remaining.split(TOKEN_SPLIT_RE)) {
|
|
231
|
+
if (raw.length === 0) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
considered += 1;
|
|
235
|
+
const token = raw.toLowerCase();
|
|
236
|
+
if (token.length < 3) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (STOP_WORDS.has(token)) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (token.includes(".")) {
|
|
243
|
+
out.push({ term: token, weight: 0.8, kind: "identifier" });
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
out.push({ term: token, weight: 0.5, kind: "literal" });
|
|
247
|
+
}
|
|
248
|
+
return considered;
|
|
249
|
+
}
|
|
250
|
+
function dedup(anchors) {
|
|
251
|
+
const best = new Map();
|
|
252
|
+
for (const anchor of anchors) {
|
|
253
|
+
const existing = best.get(anchor.term);
|
|
254
|
+
if (existing === undefined || anchor.weight > existing.weight) {
|
|
255
|
+
best.set(anchor.term, { ...anchor });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return Array.from(best.values());
|
|
259
|
+
}
|
|
260
|
+
function sortAnchors(anchors) {
|
|
261
|
+
return anchors.sort((a, b) => {
|
|
262
|
+
if (a.weight !== b.weight) {
|
|
263
|
+
return b.weight - a.weight;
|
|
264
|
+
}
|
|
265
|
+
return a.term.localeCompare(b.term);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function freeze(anchors) {
|
|
269
|
+
return anchors.map((a) => ({ term: a.term, weight: a.weight, kind: a.kind }));
|
|
270
|
+
}
|
|
271
|
+
export function extractAnchors(input) {
|
|
272
|
+
const { text, maxAnchors } = input;
|
|
273
|
+
if (text.length === 0) {
|
|
274
|
+
return { anchors: [], truncated: false, tokensConsidered: 0 };
|
|
275
|
+
}
|
|
276
|
+
if (text.length > MAX_INPUT_LENGTH) {
|
|
277
|
+
return { anchors: [], truncated: true, tokensConsidered: 0 };
|
|
278
|
+
}
|
|
279
|
+
const collected = [];
|
|
280
|
+
let remaining = collectMatches(text, QUOTED_DOUBLE_RE, "quoted", 1, collected);
|
|
281
|
+
remaining = collectMatches(remaining, QUOTED_SINGLE_RE, "quoted", 1, collected);
|
|
282
|
+
remaining = collectMatches(remaining, BACKTICK_RE, "identifier", 0.9, collected);
|
|
283
|
+
remaining = collectMatches(remaining, PATH_RE, "path", 0.95, collected);
|
|
284
|
+
remaining = collectMatches(remaining, CAMEL_IDENTIFIER_RE, "identifier", 0.85, collected);
|
|
285
|
+
remaining = collectTechnicalTerms(remaining, collected);
|
|
286
|
+
const tokensConsidered = tokenizeRemaining(remaining, collected);
|
|
287
|
+
const merged = sortAnchors(dedup(collected));
|
|
288
|
+
const truncated = merged.length > maxAnchors;
|
|
289
|
+
const final = truncated ? merged.slice(0, maxAnchors) : merged;
|
|
290
|
+
return { anchors: freeze(final), truncated, tokensConsidered };
|
|
291
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CreatePlanDeps, type CreatePlanInput, type ExplorationPlan } from "./plan.js";
|
|
2
|
+
import { type GovernorState } from "./governor.js";
|
|
3
|
+
export declare function planExploration(input: CreatePlanInput, deps?: CreatePlanDeps): ExplorationPlan;
|
|
4
|
+
export interface PlanAndGovernResult {
|
|
5
|
+
readonly plan: ExplorationPlan;
|
|
6
|
+
readonly governor: GovernorState | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function planAndGovern(input: CreatePlanInput, deps?: CreatePlanDeps): PlanAndGovernResult;
|
|
9
|
+
//# sourceMappingURL=explorationPlanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorationPlanner.d.ts","sourceRoot":"","sources":["../../src/planner/explorationPlanner.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnE,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,eAAe,CAE9F;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,CAAC;CAC9C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,mBAAmB,CAMhG"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Public facade for the exploration planner and budget governor (Epic #177, Issue #181).
|
|
2
|
+
// Thin wrapper over createExplorationPlan + createGovernor so external consumers can plan
|
|
3
|
+
// and arm a governor in one call without knowing the internal file layout.
|
|
4
|
+
import { createExplorationPlan, } from "./plan.js";
|
|
5
|
+
import { createGovernor } from "./governor.js";
|
|
6
|
+
export function planExploration(input, deps) {
|
|
7
|
+
return createExplorationPlan(input, deps);
|
|
8
|
+
}
|
|
9
|
+
export function planAndGovern(input, deps) {
|
|
10
|
+
const plan = createExplorationPlan(input, deps);
|
|
11
|
+
if (plan.state !== "ready") {
|
|
12
|
+
return { plan, governor: undefined };
|
|
13
|
+
}
|
|
14
|
+
return { plan, governor: createGovernor(plan) };
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ExplorationUsage } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
2
|
+
import type { ExplorationPlan } from "./plan.js";
|
|
3
|
+
export type GovernorStatus = "running" | "completed" | "budget-exhausted";
|
|
4
|
+
export interface GovernorState {
|
|
5
|
+
readonly plan: ExplorationPlan;
|
|
6
|
+
readonly usage: ExplorationUsage;
|
|
7
|
+
readonly currentRingIndex: number;
|
|
8
|
+
readonly status: GovernorStatus;
|
|
9
|
+
readonly stopReason: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function createGovernor(plan: ExplorationPlan): GovernorState;
|
|
12
|
+
export declare function applyUsage(state: GovernorState, delta: ExplorationUsage): GovernorState;
|
|
13
|
+
export declare function canContinue(state: GovernorState): boolean;
|
|
14
|
+
export declare function advanceRing(state: GovernorState): GovernorState;
|
|
15
|
+
export declare function complete(state: GovernorState): GovernorState;
|
|
16
|
+
//# sourceMappingURL=governor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../src/planner/governor.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,kBAAkB,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAgED,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,CAanE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,gBAAgB,GAAG,aAAa,CAevF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAEzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAQ/D;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAK5D"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Budget governor state machine (Epic #177, Issue #181).
|
|
2
|
+
// Accumulates ExplorationUsage and stops execution when any dimension exceeds its budget cap.
|
|
3
|
+
// Immutable state objects only — every transition returns a fresh GovernorState. No persistence;
|
|
4
|
+
// callers persist via the audit ledger in #187.
|
|
5
|
+
import { isWithinBudget, } from "@oscharko-dev/keiko-contracts/connected-context";
|
|
6
|
+
const ZERO_USAGE = {
|
|
7
|
+
searchCalls: 0,
|
|
8
|
+
filesRead: 0,
|
|
9
|
+
excerptBytes: 0,
|
|
10
|
+
modelInputTokens: 0,
|
|
11
|
+
modelOutputTokens: 0,
|
|
12
|
+
elapsedMs: 0,
|
|
13
|
+
rerankCalls: 0,
|
|
14
|
+
};
|
|
15
|
+
const USAGE_KEYS = [
|
|
16
|
+
"searchCalls",
|
|
17
|
+
"filesRead",
|
|
18
|
+
"excerptBytes",
|
|
19
|
+
"modelInputTokens",
|
|
20
|
+
"modelOutputTokens",
|
|
21
|
+
"elapsedMs",
|
|
22
|
+
"rerankCalls",
|
|
23
|
+
];
|
|
24
|
+
const BUDGET_KEY_FOR_USAGE = {
|
|
25
|
+
searchCalls: "searchCallsMax",
|
|
26
|
+
filesRead: "filesReadMax",
|
|
27
|
+
excerptBytes: "excerptBytesMax",
|
|
28
|
+
modelInputTokens: "modelInputTokensMax",
|
|
29
|
+
modelOutputTokens: "modelOutputTokensMax",
|
|
30
|
+
elapsedMs: "elapsedMsMax",
|
|
31
|
+
rerankCalls: "rerankCallsMax",
|
|
32
|
+
};
|
|
33
|
+
function assertUsageDelta(delta) {
|
|
34
|
+
for (const key of USAGE_KEYS) {
|
|
35
|
+
const value = delta[key];
|
|
36
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
37
|
+
throw new RangeError(`Governor usage delta has invalid ${key}: ${String(value)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function addUsage(a, b) {
|
|
42
|
+
return {
|
|
43
|
+
searchCalls: a.searchCalls + b.searchCalls,
|
|
44
|
+
filesRead: a.filesRead + b.filesRead,
|
|
45
|
+
excerptBytes: a.excerptBytes + b.excerptBytes,
|
|
46
|
+
modelInputTokens: a.modelInputTokens + b.modelInputTokens,
|
|
47
|
+
modelOutputTokens: a.modelOutputTokens + b.modelOutputTokens,
|
|
48
|
+
elapsedMs: a.elapsedMs + b.elapsedMs,
|
|
49
|
+
rerankCalls: a.rerankCalls + b.rerankCalls,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function violatedDimensions(usage, budget) {
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const key of USAGE_KEYS) {
|
|
55
|
+
const cap = budget[BUDGET_KEY_FOR_USAGE[key]];
|
|
56
|
+
if (usage[key] > cap) {
|
|
57
|
+
out.push(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
export function createGovernor(plan) {
|
|
63
|
+
if (plan.state !== "ready") {
|
|
64
|
+
throw new RangeError(`Cannot govern a plan in state "${plan.state}"; only "ready" plans are runnable.`);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
plan,
|
|
68
|
+
usage: ZERO_USAGE,
|
|
69
|
+
currentRingIndex: 0,
|
|
70
|
+
status: "running",
|
|
71
|
+
stopReason: undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function applyUsage(state, delta) {
|
|
75
|
+
assertUsageDelta(delta);
|
|
76
|
+
const nextUsage = addUsage(state.usage, delta);
|
|
77
|
+
if (isWithinBudget(nextUsage, state.plan.budget)) {
|
|
78
|
+
return { ...state, usage: nextUsage };
|
|
79
|
+
}
|
|
80
|
+
const violated = violatedDimensions(nextUsage, state.plan.budget);
|
|
81
|
+
const stopReason = violated.length > 0 ? `budget-exhausted on ${violated.join(", ")}` : "budget-exhausted";
|
|
82
|
+
return {
|
|
83
|
+
...state,
|
|
84
|
+
usage: nextUsage,
|
|
85
|
+
status: "budget-exhausted",
|
|
86
|
+
stopReason,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function canContinue(state) {
|
|
90
|
+
return state.status === "running" && isWithinBudget(state.usage, state.plan.budget);
|
|
91
|
+
}
|
|
92
|
+
export function advanceRing(state) {
|
|
93
|
+
if (state.status !== "running") {
|
|
94
|
+
return state;
|
|
95
|
+
}
|
|
96
|
+
if (state.currentRingIndex >= state.plan.rings.length) {
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
99
|
+
return { ...state, currentRingIndex: state.currentRingIndex + 1 };
|
|
100
|
+
}
|
|
101
|
+
export function complete(state) {
|
|
102
|
+
if (state.status === "budget-exhausted") {
|
|
103
|
+
return state;
|
|
104
|
+
}
|
|
105
|
+
return { ...state, status: "completed" };
|
|
106
|
+
}
|