@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,129 @@
|
|
|
1
|
+
// Context assembly for the generation call (ADR-0008 D1). Delegates to #5 buildContextPack so the
|
|
2
|
+
// target file, nearby test files, manifests/config, and type definitions are selected, ranked, and
|
|
3
|
+
// excerpted under a byte budget — every excerpt already redacted by #5 at the IO boundary. Pure
|
|
4
|
+
// except for the WorkspaceFs seam threaded into buildContextPack. The `task` hint is a forward-
|
|
5
|
+
// compatible natural-language description of the target for a future embedding ranker; the Wave-1
|
|
6
|
+
// lexical strategy tolerates it.
|
|
7
|
+
import { buildContextPack, DEFAULT_DISCOVERY_OPTIONS, lexicalRetrievalStrategy, SELECTION_REASON_PRIORITY, } from "@oscharko-dev/keiko-workspace";
|
|
8
|
+
import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
|
|
9
|
+
function toPosix(path) {
|
|
10
|
+
return path.split("\\").join("/");
|
|
11
|
+
}
|
|
12
|
+
function underDir(path, dir) {
|
|
13
|
+
const normalized = toPosix(dir);
|
|
14
|
+
return path === normalized || path.startsWith(`${normalized}/`);
|
|
15
|
+
}
|
|
16
|
+
function basename(path) {
|
|
17
|
+
const normalized = toPosix(path);
|
|
18
|
+
const idx = normalized.lastIndexOf("/");
|
|
19
|
+
return idx === -1 ? normalized : normalized.slice(idx + 1);
|
|
20
|
+
}
|
|
21
|
+
function stem(path) {
|
|
22
|
+
const base = basename(path);
|
|
23
|
+
const idx = base.lastIndexOf(".");
|
|
24
|
+
return idx <= 0 ? base : base.slice(0, idx);
|
|
25
|
+
}
|
|
26
|
+
function testStem(path) {
|
|
27
|
+
const parts = stem(path).split(".");
|
|
28
|
+
const marker = parts.findIndex((part) => part === "test" || part === "spec");
|
|
29
|
+
return marker === -1 ? parts.join(".") : parts.slice(0, marker).join(".");
|
|
30
|
+
}
|
|
31
|
+
function targetPaths(target) {
|
|
32
|
+
if (target.kind === "file") {
|
|
33
|
+
return [toPosix(target.filePath)];
|
|
34
|
+
}
|
|
35
|
+
if (target.kind === "changedFiles") {
|
|
36
|
+
return target.filePaths.map(toPosix);
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
function moduleDir(target) {
|
|
41
|
+
return target.kind === "module" ? toPosix(target.moduleDir) : undefined;
|
|
42
|
+
}
|
|
43
|
+
function isRequestedTarget(path, input) {
|
|
44
|
+
const module = moduleDir(input.target);
|
|
45
|
+
return (targetPaths(input.target).includes(path) || (module !== undefined && underDir(path, module)));
|
|
46
|
+
}
|
|
47
|
+
function isTestCandidate(path, workspace, selectionReason) {
|
|
48
|
+
return (selectionReason === "test" ||
|
|
49
|
+
workspace.testDirs.some((testDir) => underDir(path, testDir)) ||
|
|
50
|
+
stem(path)
|
|
51
|
+
.split(".")
|
|
52
|
+
.some((part) => part === "test" || part === "spec"));
|
|
53
|
+
}
|
|
54
|
+
function isNearbyTest(path, workspace, input, selectionReason) {
|
|
55
|
+
if (!isTestCandidate(path, workspace, selectionReason)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const targets = targetPaths(input.target);
|
|
59
|
+
if (targets.length === 0) {
|
|
60
|
+
const module = moduleDir(input.target);
|
|
61
|
+
return module !== undefined && path.includes(basename(module));
|
|
62
|
+
}
|
|
63
|
+
const candidateStem = testStem(path);
|
|
64
|
+
return targets.some((target) => candidateStem === stem(target));
|
|
65
|
+
}
|
|
66
|
+
function priorityIndex(reason) {
|
|
67
|
+
return SELECTION_REASON_PRIORITY.indexOf(reason);
|
|
68
|
+
}
|
|
69
|
+
function issue8Priority(ranked, workspace, input) {
|
|
70
|
+
const path = toPosix(ranked.file.relativePath);
|
|
71
|
+
if (isRequestedTarget(path, input)) {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (isNearbyTest(path, workspace, input, ranked.selectionReason)) {
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
return 2;
|
|
78
|
+
}
|
|
79
|
+
function isSupportContext(ranked) {
|
|
80
|
+
return ranked.selectionReason === "manifest" || ranked.selectionReason === "config";
|
|
81
|
+
}
|
|
82
|
+
function focusedContext(ranked, workspace, input) {
|
|
83
|
+
const focused = ranked.filter((item) => issue8Priority(item, workspace, input) < 2 || isSupportContext(item));
|
|
84
|
+
return focused.length === 0 ? ranked : focused;
|
|
85
|
+
}
|
|
86
|
+
function createUnitTestRetrievalStrategy(workspace, input) {
|
|
87
|
+
return {
|
|
88
|
+
rank: (files, task) => {
|
|
89
|
+
const ranked = focusedContext(lexicalRetrievalStrategy.rank(files, task), workspace, input);
|
|
90
|
+
return [...ranked].sort((a, b) => {
|
|
91
|
+
const byIssue8 = issue8Priority(a, workspace, input) - issue8Priority(b, workspace, input);
|
|
92
|
+
if (byIssue8 !== 0) {
|
|
93
|
+
return byIssue8;
|
|
94
|
+
}
|
|
95
|
+
const byReason = priorityIndex(a.selectionReason) - priorityIndex(b.selectionReason);
|
|
96
|
+
if (byReason !== 0) {
|
|
97
|
+
return byReason;
|
|
98
|
+
}
|
|
99
|
+
return a.file.relativePath.localeCompare(b.file.relativePath);
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function taskHint(target) {
|
|
105
|
+
if (target.kind === "file") {
|
|
106
|
+
return target.targetFunction === undefined
|
|
107
|
+
? `unit tests for ${target.filePath}`
|
|
108
|
+
: `unit tests for ${target.targetFunction} in ${target.filePath}`;
|
|
109
|
+
}
|
|
110
|
+
if (target.kind === "module") {
|
|
111
|
+
return `unit tests for module ${target.moduleDir}`;
|
|
112
|
+
}
|
|
113
|
+
return `unit tests for ${target.filePaths.join(", ")}`;
|
|
114
|
+
}
|
|
115
|
+
export function buildTestGenContext(workspace, input, limits, deps = {}) {
|
|
116
|
+
const request = {
|
|
117
|
+
task: taskHint(input.target),
|
|
118
|
+
budgetBytes: limits.contextBudgetBytes,
|
|
119
|
+
maxBytesPerFile: limits.maxBytesPerFile,
|
|
120
|
+
discovery: DEFAULT_DISCOVERY_OPTIONS,
|
|
121
|
+
};
|
|
122
|
+
// buildContextPack defaults to the lexical strategy, but ContextPackDeps requires the field; pass
|
|
123
|
+
// the #5 barrel default explicitly so we hand a complete deps object without reaching past #5.
|
|
124
|
+
const packDeps = {
|
|
125
|
+
fs: deps.fs ?? nodeWorkspaceFs,
|
|
126
|
+
strategy: createUnitTestRetrievalStrategy(workspace, input),
|
|
127
|
+
};
|
|
128
|
+
return buildContextPack(workspace, request, packDeps);
|
|
129
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContextPack, WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
|
|
2
|
+
import type { TestConventions } from "./types.js";
|
|
3
|
+
export declare function isTestPath(workspace: WorkspaceInfo, relPath: string): boolean;
|
|
4
|
+
export declare function detectConventions(workspace: WorkspaceInfo, pack: ContextPack): TestConventions;
|
|
5
|
+
//# sourceMappingURL=conventions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conventions.d.ts","sourceRoot":"","sources":["../../src/unit-tests/conventions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,YAAY,CAAC;AA0CnE,wBAAgB,UAAU,CAAC,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAM7E;AA4CD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,eAAe,CAO9F"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Convention detection (ADR-0008 D7) and the production-code guard predicate (D6). Pure except
|
|
2
|
+
// for the WorkspaceInfo/ContextPack values handed in (no IO, no clock, no RNG). ALL path and
|
|
3
|
+
// naming predicates use plain string ops (split('.'), startsWith, equality) — zero regex — so
|
|
4
|
+
// there is no ReDoS surface (CodeQL js/polynomial-redos, steering note F).
|
|
5
|
+
import { basename, dirname, extname } from "node:path";
|
|
6
|
+
const MAX_ASSERTION_SAMPLES = 2;
|
|
7
|
+
const TEST_SEGMENTS = ["test", "spec"];
|
|
8
|
+
// Normalises a path to forward slashes so dir/segment checks are platform-independent.
|
|
9
|
+
function toPosix(path) {
|
|
10
|
+
return path.split("\\").join("/");
|
|
11
|
+
}
|
|
12
|
+
// True when the basename (without its final extension) contains "test" or "spec" as a
|
|
13
|
+
// dot-separated segment. `foo.test.ts` -> ["foo","test"] -> pass; `testUtils.ts` ->
|
|
14
|
+
// ["testUtils"] -> FAIL (prefix, not a segment); `foo.spec.utils.ts` -> ["foo","spec","utils"] -> pass.
|
|
15
|
+
function basenameMarksTest(path) {
|
|
16
|
+
const ext = extname(path);
|
|
17
|
+
const stem = basename(path, ext);
|
|
18
|
+
const segments = stem.split(".");
|
|
19
|
+
return segments.some((segment) => TEST_SEGMENTS.includes(segment));
|
|
20
|
+
}
|
|
21
|
+
// True when the path's directory equals or sits under one of the configured testDirs.
|
|
22
|
+
function underTestDir(testDirs, posixPath) {
|
|
23
|
+
const dir = dirname(posixPath);
|
|
24
|
+
return testDirs.some((rawTestDir) => {
|
|
25
|
+
const testDir = toPosix(rawTestDir);
|
|
26
|
+
return dir === testDir || dir.startsWith(`${testDir}/`) || posixPath.startsWith(`${testDir}/`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// A path containing a `..` segment or an absolute leading slash is traversal: it can resolve to a
|
|
30
|
+
// production file OUTSIDE the apparent test directory. `tests/../src/auth.ts` lexically starts with
|
|
31
|
+
// `tests/`, but #6 resolveWithinWorkspace collapses it to the in-workspace `src/auth.ts` and writes
|
|
32
|
+
// THAT. A legitimately generated test path is always a clean workspace-relative path, so we reject
|
|
33
|
+
// traversal fail-closed before any test/testDir check (security fix: D6 bypass via path traversal).
|
|
34
|
+
function isTraversal(posixPath) {
|
|
35
|
+
return posixPath.startsWith("/") || posixPath.split("/").includes("..");
|
|
36
|
+
}
|
|
37
|
+
// The production-code guard predicate (D6): a path passes if its basename marks it a test file OR
|
|
38
|
+
// it lies under a detected testDir. Used to reject any patch that touches a non-test path before
|
|
39
|
+
// renderDryRun/applyPatch — the second barrier against prompt-injected source modification. Any
|
|
40
|
+
// traversal/absolute path is rejected outright so the guarded path matches the path #6 would write.
|
|
41
|
+
export function isTestPath(workspace, relPath) {
|
|
42
|
+
const posixPath = toPosix(relPath);
|
|
43
|
+
if (isTraversal(posixPath)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return basenameMarksTest(posixPath) || underTestDir(workspace.testDirs, posixPath);
|
|
47
|
+
}
|
|
48
|
+
// The set of test excerpts already selected by #5 (redacted, bounded), capped to MAX_ASSERTION_SAMPLES.
|
|
49
|
+
function sampleAssertionStyle(pack) {
|
|
50
|
+
return pack.selected
|
|
51
|
+
.filter((entry) => entry.selectionReason === "test")
|
|
52
|
+
.slice(0, MAX_ASSERTION_SAMPLES)
|
|
53
|
+
.map((entry) => entry.excerpt);
|
|
54
|
+
}
|
|
55
|
+
// Whether any selected pack entry is a sibling test (a test file whose directory matches a
|
|
56
|
+
// non-test source path's directory). We approximate "sibling" as a test-marked path NOT under a
|
|
57
|
+
// configured testDir, and "mirrored" as a test-marked path under a testDir.
|
|
58
|
+
function hasSiblingTest(workspace, pack) {
|
|
59
|
+
return pack.selected.some((entry) => basenameMarksTest(toPosix(entry.path)) &&
|
|
60
|
+
!underTestDir(workspace.testDirs, toPosix(entry.path)));
|
|
61
|
+
}
|
|
62
|
+
function hasMirroredTest(workspace, pack) {
|
|
63
|
+
return pack.selected.some((entry) => basenameMarksTest(toPosix(entry.path)) &&
|
|
64
|
+
underTestDir(workspace.testDirs, toPosix(entry.path)));
|
|
65
|
+
}
|
|
66
|
+
function deriveNamingStyle(workspace, pack) {
|
|
67
|
+
if (hasSiblingTest(workspace, pack)) {
|
|
68
|
+
return "sibling";
|
|
69
|
+
}
|
|
70
|
+
if (workspace.testDirs.length > 0 && hasMirroredTest(workspace, pack)) {
|
|
71
|
+
return "mirrored";
|
|
72
|
+
}
|
|
73
|
+
// testDirs present but no observed test sample: default to mirrored (the conventional placement
|
|
74
|
+
// under tests/). Only when there is neither a testDir nor a sample is the style unknown.
|
|
75
|
+
if (workspace.testDirs.length > 0) {
|
|
76
|
+
return "mirrored";
|
|
77
|
+
}
|
|
78
|
+
return "unknown";
|
|
79
|
+
}
|
|
80
|
+
export function detectConventions(workspace, pack) {
|
|
81
|
+
return {
|
|
82
|
+
framework: workspace.testFramework,
|
|
83
|
+
testDirs: workspace.testDirs,
|
|
84
|
+
fileNamingStyle: deriveNamingStyle(workspace, pack),
|
|
85
|
+
assertionStyleSamples: sampleAssertionStyle(pack),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type WorkflowLimits } from "./types.js";
|
|
2
|
+
import type { WorkflowDescriptor } from "../descriptor.js";
|
|
3
|
+
export type { WorkflowDescriptor, WorkflowInputSpec } from "../descriptor.js";
|
|
4
|
+
export declare const UNIT_TEST_WORKFLOW_DESCRIPTOR: WorkflowDescriptor<WorkflowLimits>;
|
|
5
|
+
//# sourceMappingURL=descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptor.d.ts","sourceRoot":"","sources":["../../src/unit-tests/descriptor.ts"],"names":[],"mappings":"AAKA,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE9E,eAAO,MAAM,6BAA6B,EAAE,kBAAkB,CAAC,cAAc,CAuCnE,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The static UI workflow descriptor (ADR-0008 D10). Issue #13 reads this to render the workflow
|
|
2
|
+
// UI without knowing the implementation. Frozen and JSON-serializable. The descriptor interfaces
|
|
3
|
+
// are the shared base (ADR-0009 D12) — re-exported here so #8's existing import surface (the
|
|
4
|
+
// unit-tests barrel) is unchanged; both workflows depend on the base and neither on the other.
|
|
5
|
+
import { DEFAULT_WORKFLOW_LIMITS } from "./types.js";
|
|
6
|
+
export const UNIT_TEST_WORKFLOW_DESCRIPTOR = {
|
|
7
|
+
workflowId: "unit-test-generation",
|
|
8
|
+
name: "Unit Test Generation",
|
|
9
|
+
description: "Generates a reviewable unit-test patch for a target TypeScript file, function, or module. " +
|
|
10
|
+
"Detects the project's test framework and naming conventions. Dry-run by default; " +
|
|
11
|
+
"pass apply:true to write the tests and run verification.",
|
|
12
|
+
inputs: [
|
|
13
|
+
{
|
|
14
|
+
name: "target",
|
|
15
|
+
type: "object",
|
|
16
|
+
required: true,
|
|
17
|
+
description: "Target: { kind: 'file', filePath } | { kind: 'module', moduleDir } | { kind: 'changedFiles', filePaths }",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "apply",
|
|
21
|
+
type: "boolean",
|
|
22
|
+
required: false,
|
|
23
|
+
description: "Write tests to disk and run verification",
|
|
24
|
+
defaultValue: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "modelId",
|
|
28
|
+
type: "string",
|
|
29
|
+
required: true,
|
|
30
|
+
description: "Model ID registered in gateway config",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "limits",
|
|
34
|
+
type: "object",
|
|
35
|
+
required: false,
|
|
36
|
+
description: "Partial<WorkflowLimits> overrides",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
defaultLimits: DEFAULT_WORKFLOW_LIMITS,
|
|
40
|
+
modelSelectionOptions: { arbitrary: true, preferredCostClass: "medium" },
|
|
41
|
+
supportsDryRun: true,
|
|
42
|
+
supportsApply: true,
|
|
43
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UnitTestTarget } from "./types.js";
|
|
2
|
+
import type { WorkflowEvent, WorkflowEventSink } from "./events.js";
|
|
3
|
+
export declare function computeFingerprint(target: UnitTestTarget, modelId: string): string;
|
|
4
|
+
type EnvelopeFields = "schemaVersion" | "runId" | "fingerprint" | "seq" | "ts";
|
|
5
|
+
export type WorkflowEventBody = {
|
|
6
|
+
[E in WorkflowEvent as E["type"]]: Omit<E, EnvelopeFields>;
|
|
7
|
+
}[WorkflowEvent["type"]];
|
|
8
|
+
export interface EventEmitter {
|
|
9
|
+
readonly emit: (body: WorkflowEventBody) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function createEventEmitter(sink: WorkflowEventSink, runId: string, fingerprint: string, now: () => number): EventEmitter;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/unit-tests/emit.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMlF;AAID,KAAK,cAAc,GAAG,eAAe,GAAG,OAAO,GAAG,aAAa,GAAG,KAAK,GAAG,IAAI,CAAC;AAC/E,MAAM,MAAM,iBAAiB,GAAG;KAC7B,CAAC,IAAI,aAAa,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,cAAc,CAAC;CAC3D,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAClD;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,MAAM,GAChB,YAAY,CAkBd"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Event emission helper for the workflow (ADR-0008 D4). Owns the monotonic seq counter and stamps
|
|
2
|
+
// every event with the shared envelope ({ schemaVersion, runId, fingerprint, seq, ts }). The
|
|
3
|
+
// fingerprint is SHA-256(workflowId + canonical(target) + modelId) truncated to 16 hex chars — the
|
|
4
|
+
// same shape as the harness fingerprinter — reusing `canonicalise` from the harness barrel so two
|
|
5
|
+
// structurally equal targets fingerprint identically regardless of key order. Sensitive fields are
|
|
6
|
+
// redacted by the CALLER before handing the event here (this helper does not inspect payloads).
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { canonicalise } from "@oscharko-dev/keiko-harness";
|
|
9
|
+
const FINGERPRINT_HEX_CHARS = 16;
|
|
10
|
+
export function computeFingerprint(target, modelId) {
|
|
11
|
+
const canonical = canonicalise({ workflowId: "unit-test-generation", target, modelId });
|
|
12
|
+
return createHash("sha256")
|
|
13
|
+
.update(canonical, "utf8")
|
|
14
|
+
.digest("hex")
|
|
15
|
+
.slice(0, FINGERPRINT_HEX_CHARS);
|
|
16
|
+
}
|
|
17
|
+
export function createEventEmitter(sink, runId, fingerprint, now) {
|
|
18
|
+
let seq = 0;
|
|
19
|
+
return {
|
|
20
|
+
emit: (body) => {
|
|
21
|
+
seq += 1;
|
|
22
|
+
// The body is a complete discriminated-union member minus the envelope; re-attaching the
|
|
23
|
+
// envelope yields a valid WorkflowEvent. The cast is over the union, not a widening to any.
|
|
24
|
+
const event = {
|
|
25
|
+
schemaVersion: "1",
|
|
26
|
+
runId,
|
|
27
|
+
fingerprint,
|
|
28
|
+
seq,
|
|
29
|
+
ts: now(),
|
|
30
|
+
...body,
|
|
31
|
+
};
|
|
32
|
+
sink.emit(event);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type { WorkflowStartedEvent, ConventionsDetectedEvent, ContextSelectedEvent, ModelCallStartedEvent, ModelCallCompletedEvent, PatchValidatedEvent, PatchAppliedEvent, VerificationResultEvent, WorkflowCompletedEvent, WorkflowFailedEvent, WorkflowEvent, WorkflowEventSink, } from "@oscharko-dev/keiko-contracts/unit-test-events";
|
|
2
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/unit-tests/events.ts"],"names":[],"mappings":"AAMA,YAAY,EACV,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,GAClB,MAAM,gDAAgD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Re-export shim: the unit-test workflow event union and every member interface live in
|
|
2
|
+
// @oscharko-dev/keiko-contracts (issue #158). The contracts BARREL exposes only WorkflowEvent +
|
|
3
|
+
// WorkflowEventSink because several member names collide with HarnessEvent members by structural
|
|
4
|
+
// convention (ADR-0008 D4); the contracts subpath `unit-test-events` carries the full member set
|
|
5
|
+
// so internal consumers can keep importing concrete shapes from "./events.js" unchanged.
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TestFramework, WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
|
|
2
|
+
export type ComponentFramework = "react" | "none";
|
|
3
|
+
export interface FrontendTestStack {
|
|
4
|
+
readonly componentFramework: ComponentFramework;
|
|
5
|
+
readonly hasReactTestingLibrary: boolean;
|
|
6
|
+
readonly hasUserEvent: boolean;
|
|
7
|
+
readonly hasJestDom: boolean;
|
|
8
|
+
readonly hasAccessibilityMatchers: boolean;
|
|
9
|
+
readonly hasDomEnvironment: boolean;
|
|
10
|
+
readonly hasPlaywright: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const EMPTY_FRONTEND_TEST_STACK: FrontendTestStack;
|
|
13
|
+
export type TestStyle = "unit" | "component" | "interaction" | "accessibility-smoke" | "browser-smoke" | "unsupported";
|
|
14
|
+
export declare const TEST_STYLES: readonly TestStyle[];
|
|
15
|
+
export type TestVerification = "vitest" | "jest" | "mocha" | "playwright" | "none";
|
|
16
|
+
export interface TestStrategy {
|
|
17
|
+
readonly style: TestStyle;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
readonly verification: TestVerification;
|
|
20
|
+
}
|
|
21
|
+
export declare function manifestDependencyNames(manifestJson: string): ReadonlySet<string>;
|
|
22
|
+
export interface FrontendStackSignals {
|
|
23
|
+
readonly hasPlaywrightConfig: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function detectFrontendStackFromDependencies(names: ReadonlySet<string>, signals: FrontendStackSignals): FrontendTestStack;
|
|
26
|
+
export interface ManifestReaderFs {
|
|
27
|
+
readonly exists: (absolutePath: string) => boolean;
|
|
28
|
+
readonly readFileUtf8: (absolutePath: string) => string;
|
|
29
|
+
}
|
|
30
|
+
export declare function detectFrontendStack(workspace: WorkspaceInfo, fs: ManifestReaderFs, anchorRelPath?: string): FrontendTestStack;
|
|
31
|
+
export declare function isReactComponentPath(path: string): boolean;
|
|
32
|
+
export declare function looksLikeReactComponentSource(source: string | undefined): boolean;
|
|
33
|
+
export declare function isPlaywrightEntryPath(path: string): boolean;
|
|
34
|
+
export declare function isPlaywrightSmokeAppropriate(path: string, stack: FrontendTestStack): boolean;
|
|
35
|
+
export interface StrategyInput {
|
|
36
|
+
readonly targetPath: string;
|
|
37
|
+
readonly targetSource?: string | undefined;
|
|
38
|
+
readonly framework: TestFramework;
|
|
39
|
+
readonly stack: FrontendTestStack;
|
|
40
|
+
}
|
|
41
|
+
export declare function selectTestStrategy(input: StrategyInput): TestStrategy;
|
|
42
|
+
//# sourceMappingURL=frontend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/unit-tests/frontend.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAIlF,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAC;AAIlD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAEhD,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;IAEzC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAE/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,QAAQ,CAAC,wBAAwB,EAAE,OAAO,CAAC;IAE3C,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IAEpC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED,eAAO,MAAM,yBAAyB,EAAE,iBAQ9B,CAAC;AAIX,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,WAAW,GACX,aAAa,GACb,qBAAqB,GACrB,eAAe,GACf,aAAa,CAAC;AAElB,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAOlC,CAAC;AAKX,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAG1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;CACzC;AAiBD,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAoBjF;AAsBD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACvC;AAID,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAUnB;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IACnD,QAAQ,CAAC,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAqDD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,EAAE,EAAE,gBAAgB,EACpB,aAAa,CAAC,EAAE,MAAM,GACrB,iBAAiB,CAYnB;AAqBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAKD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYjF;AAmBD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAU3D;AAMD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAE5F;AAID,MAAM,WAAW,aAAa;IAE5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE3C,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;CACnC;AAsDD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAerE"}
|