@skyramp/mcp 0.3.7 → 0.3.8
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/build/prompts/test-recommendation/diffExecutionPlan.js +40 -9
- package/build/prompts/test-recommendation/fullRepoCatalog.js +3 -2
- package/build/prompts/test-recommendation/recommendationSections.js +3 -3
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +2 -2
- package/build/prompts/test-recommendation/scopeAssessment.js +58 -76
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +14 -1
- package/build/prompts/testbot/testbot-prompts.js +25 -8
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +39 -5
- package/build/recommendation/budgeters/shared.d.ts +21 -8
- package/build/recommendation/budgeters/shared.js +134 -56
- package/build/recommendation/planRanker.d.ts +5 -3
- package/build/recommendation/planRanker.js +1 -1
- package/build/tools/submitReportTool.js +6 -1
- package/build/tools/test-management/registerTestPlanTool.d.ts +3 -3
- package/build/tools/test-management/registerTestPlanTool.js +29 -8
- package/build/types/RepositoryAnalysis.d.ts +10 -10
- package/build/types/TestRecommendation.d.ts +11 -1
- package/build/types/TestRecommendation.js +34 -0
- package/build/utils/pathSignatures.d.ts +4 -1
- package/build/utils/pathSignatures.js +14 -2
- package/build/utils/planOnlyMode.d.ts +33 -0
- package/build/utils/planOnlyMode.js +40 -0
- package/build/utils/reportVerification.d.ts +5 -0
- package/build/utils/reportVerification.js +7 -2
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +1 -1
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ import { CandidateSource, computeCandidateId, scenarioMergeKey, DiscriminatorKin
|
|
|
12
12
|
import { selectPlan } from "../../recommendation/planRanker.js";
|
|
13
13
|
import { resolveSubjectEndpoints } from "../../utils/subjectEndpoints.js";
|
|
14
14
|
import { collectChangedRouteLines } from "../../utils/changedRoutes.js";
|
|
15
|
-
import {
|
|
15
|
+
import { generateFloors } from "../../recommendation/budgeters/shared.js";
|
|
16
16
|
import { inferScenarioType } from "../../recommendation/diversity.js";
|
|
17
17
|
import { validateDiscriminator } from "../../recommendation/discriminators.js";
|
|
18
18
|
import { isAttackSurfaceSecurityBoundary } from "../../prompts/test-recommendation/recommendationShared.js";
|
|
@@ -76,7 +76,7 @@ const discriminatorClaimSchema = z.object({
|
|
|
76
76
|
const registerCandidateSchema = z.object({
|
|
77
77
|
scenarioName: z.string().min(1).describe("Name for this test/scenario. Reused as the match key against the generation tool's scenarioName — keep it identical when you later call the generation tool."),
|
|
78
78
|
description: z.string().min(1),
|
|
79
|
-
category: z.enum(SCENARIO_CATEGORIES).describe("Test category — drives ranking (never your own priority tag)."),
|
|
79
|
+
category: z.enum(SCENARIO_CATEGORIES).describe("Test category — drives ranking (never your own priority tag). `bug_caught` targets a `<bug_found>` flaw from code review. `requirement_conflict` asserts a requirement the PR title/description (or a requirements file it references) states and the implemented behavior contradicts — it fails on the current code by design. Both rank in the top tier but are carved out independently, so a requirement conflict never loses its slot to a code-review bug."),
|
|
80
80
|
priority: z.enum(["high", "medium", "low"]).describe("Your own assessment — recorded, but ranking is derived from category, not this field."),
|
|
81
81
|
testType: z.nativeEnum(TestType).refine((t) => PLAN_TEST_TYPES.has(t), {
|
|
82
82
|
message: `testType must be one of ${[...PLAN_TEST_TYPES].join(", ")}`,
|
|
@@ -340,7 +340,26 @@ function renderGenerationDirective(plan) {
|
|
|
340
340
|
(nonUICount > 0 ? ` Generate the ${tests(nonUICount)} of other types as well.` : ""),
|
|
341
341
|
];
|
|
342
342
|
}
|
|
343
|
-
|
|
343
|
+
/** SKYR-4292: the server cannot count the diff's changed behaviors, so an
|
|
344
|
+
* under-budget plan is flagged (not rejected) with what a missing candidate looks
|
|
345
|
+
* like. Wipro PR #24 registered 3 candidates against a 20-slot UI-only budget and
|
|
346
|
+
* the tool answered "AUTHORITATIVE and FINAL" with no pushback. */
|
|
347
|
+
function renderBudgetCheck(plan, maxTotal) {
|
|
348
|
+
const filled = plan.generate.length + plan.additional.length;
|
|
349
|
+
if (filled >= maxTotal)
|
|
350
|
+
return [];
|
|
351
|
+
return [
|
|
352
|
+
"",
|
|
353
|
+
`### Budget check: ${filled} of ${maxTotal} slots filled`,
|
|
354
|
+
"Every observable behavior this diff changes — each option value added, removed, renamed or reordered; each label, " +
|
|
355
|
+
"element, route, state, validation rule, computed value or style value (spacing, size, color, radius, font weight, " +
|
|
356
|
+
"class name) that renders differently; each removed element, asserted absent — needs a candidate above, however " +
|
|
357
|
+
"small the change. A change is covered only when an existing test asserts the changed value, not when a test merely " +
|
|
358
|
+
"reaches the component. If any changed behavior has no candidate, call skyramp_register_test_plan again with your " +
|
|
359
|
+
"full updated list. If the diff truly changes fewer behaviors than the budget, this plan stands as-is.",
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
function renderPlanText(plan, dropped, maxTotal) {
|
|
344
363
|
const lines = [];
|
|
345
364
|
lines.push(`## Approved Test Plan (${plan.planId})`);
|
|
346
365
|
lines.push("");
|
|
@@ -402,6 +421,7 @@ function renderPlanText(plan, dropped) {
|
|
|
402
421
|
}
|
|
403
422
|
lines.push("");
|
|
404
423
|
lines.push(...renderGenerationDirective(plan));
|
|
424
|
+
lines.push(...renderBudgetCheck(plan, maxTotal));
|
|
405
425
|
return lines.join("\n");
|
|
406
426
|
}
|
|
407
427
|
// ── Tool registration ───────────────────────────────────────────────────────
|
|
@@ -544,10 +564,11 @@ export function registerRegisterTestPlanTool(server) {
|
|
|
544
564
|
// changes would silently plan zero UI tests (runs 32419025266,
|
|
545
565
|
// 32431042133, 32431106864) — reject so the agent self-corrects in one
|
|
546
566
|
// step, exactly like the empty-candidates rejection above.
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
errorResult = toolError(
|
|
550
|
-
"
|
|
567
|
+
const uiFloor = generateFloors(budgetContext).ui;
|
|
568
|
+
if (uiFloor > 0 && !allCandidates.some((c) => inferScenarioType(c.scenario) === TestType.UI)) {
|
|
569
|
+
errorResult = toolError(`This run has frontend changes, so the plan MUST include UI tests — at least ${uiFloor} of the ` +
|
|
570
|
+
"GENERATE slots are held for them, and more are open to UI candidates on rank. No submitted or " +
|
|
571
|
+
"recovered candidate has testType 'ui'. Resubmit your full candidate list including UI candidates " +
|
|
551
572
|
"for the changed frontend (see uiContext.changedFrontendFiles).");
|
|
552
573
|
return errorResult;
|
|
553
574
|
}
|
|
@@ -576,7 +597,7 @@ export function registerRegisterTestPlanTool(server) {
|
|
|
576
597
|
return errorResult;
|
|
577
598
|
}
|
|
578
599
|
return {
|
|
579
|
-
content: [{ type: "text", text: renderPlanText(approvedPlan, result.dropped) }],
|
|
600
|
+
content: [{ type: "text", text: renderPlanText(approvedPlan, result.dropped, budgetContext.maxTotal) }],
|
|
580
601
|
};
|
|
581
602
|
}
|
|
582
603
|
catch (error) {
|
|
@@ -1119,7 +1119,7 @@ export declare const scenarioStepSchema: z.ZodObject<{
|
|
|
1119
1119
|
export declare const draftedScenarioSchema: z.ZodObject<{
|
|
1120
1120
|
scenarioName: z.ZodString;
|
|
1121
1121
|
description: z.ZodString;
|
|
1122
|
-
category: z.ZodEnum<["new_endpoint", "bug_caught", "business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>;
|
|
1122
|
+
category: z.ZodEnum<["new_endpoint", "bug_caught", "requirement_conflict", "business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>;
|
|
1123
1123
|
priority: z.ZodEnum<["high", "medium", "low"]>;
|
|
1124
1124
|
steps: z.ZodArray<z.ZodObject<{
|
|
1125
1125
|
order: z.ZodNumber;
|
|
@@ -1231,7 +1231,7 @@ export declare const draftedScenarioSchema: z.ZodObject<{
|
|
|
1231
1231
|
description: string;
|
|
1232
1232
|
priority: "high" | "medium" | "low";
|
|
1233
1233
|
scenarioName: string;
|
|
1234
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1234
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1235
1235
|
steps: {
|
|
1236
1236
|
path: string;
|
|
1237
1237
|
method: string;
|
|
@@ -1268,7 +1268,7 @@ export declare const draftedScenarioSchema: z.ZodObject<{
|
|
|
1268
1268
|
description: string;
|
|
1269
1269
|
priority: "high" | "medium" | "low";
|
|
1270
1270
|
scenarioName: string;
|
|
1271
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1271
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1272
1272
|
steps: {
|
|
1273
1273
|
path: string;
|
|
1274
1274
|
method: string;
|
|
@@ -1607,7 +1607,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1607
1607
|
draftedScenarios: z.ZodArray<z.ZodObject<{
|
|
1608
1608
|
scenarioName: z.ZodString;
|
|
1609
1609
|
description: z.ZodString;
|
|
1610
|
-
category: z.ZodEnum<["new_endpoint", "bug_caught", "business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>;
|
|
1610
|
+
category: z.ZodEnum<["new_endpoint", "bug_caught", "requirement_conflict", "business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>;
|
|
1611
1611
|
priority: z.ZodEnum<["high", "medium", "low"]>;
|
|
1612
1612
|
steps: z.ZodArray<z.ZodObject<{
|
|
1613
1613
|
order: z.ZodNumber;
|
|
@@ -1719,7 +1719,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1719
1719
|
description: string;
|
|
1720
1720
|
priority: "high" | "medium" | "low";
|
|
1721
1721
|
scenarioName: string;
|
|
1722
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1722
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1723
1723
|
steps: {
|
|
1724
1724
|
path: string;
|
|
1725
1725
|
method: string;
|
|
@@ -1756,7 +1756,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1756
1756
|
description: string;
|
|
1757
1757
|
priority: "high" | "medium" | "low";
|
|
1758
1758
|
scenarioName: string;
|
|
1759
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1759
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1760
1760
|
steps: {
|
|
1761
1761
|
path: string;
|
|
1762
1762
|
method: string;
|
|
@@ -1799,7 +1799,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1799
1799
|
description: string;
|
|
1800
1800
|
priority: "high" | "medium" | "low";
|
|
1801
1801
|
scenarioName: string;
|
|
1802
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1802
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1803
1803
|
steps: {
|
|
1804
1804
|
path: string;
|
|
1805
1805
|
method: string;
|
|
@@ -1842,7 +1842,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1842
1842
|
description: string;
|
|
1843
1843
|
priority: "high" | "medium" | "low";
|
|
1844
1844
|
scenarioName: string;
|
|
1845
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1845
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1846
1846
|
steps: {
|
|
1847
1847
|
path: string;
|
|
1848
1848
|
method: string;
|
|
@@ -2743,7 +2743,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
2743
2743
|
description: string;
|
|
2744
2744
|
priority: "high" | "medium" | "low";
|
|
2745
2745
|
scenarioName: string;
|
|
2746
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
2746
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
2747
2747
|
steps: {
|
|
2748
2748
|
path: string;
|
|
2749
2749
|
method: string;
|
|
@@ -2956,7 +2956,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
2956
2956
|
description: string;
|
|
2957
2957
|
priority: "high" | "medium" | "low";
|
|
2958
2958
|
scenarioName: string;
|
|
2959
|
-
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
2959
|
+
category: "new_endpoint" | "bug_caught" | "requirement_conflict" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
2960
2960
|
steps: {
|
|
2961
2961
|
path: string;
|
|
2962
2962
|
method: string;
|
|
@@ -12,7 +12,7 @@ export declare enum Novelty {
|
|
|
12
12
|
EXISTING = "existing"
|
|
13
13
|
}
|
|
14
14
|
/** All categories including internal ones. */
|
|
15
|
-
export declare const SCENARIO_CATEGORIES: readonly ["new_endpoint", "bug_caught", "business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"];
|
|
15
|
+
export declare const SCENARIO_CATEGORIES: readonly ["new_endpoint", "bug_caught", "requirement_conflict", "business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"];
|
|
16
16
|
export type ScenarioCategory = typeof SCENARIO_CATEGORIES[number];
|
|
17
17
|
/** Categories valid for tool submissions (excludes internal-only categories). */
|
|
18
18
|
export declare const TEST_CATEGORIES: readonly ["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"];
|
|
@@ -21,3 +21,13 @@ export type TestCategory = typeof TEST_CATEGORIES[number];
|
|
|
21
21
|
export declare const CATEGORY_PRIORITY: Record<ScenarioCategory, PriorityTier>;
|
|
22
22
|
/** Map internal-only categories to their external equivalent for tool submission. */
|
|
23
23
|
export declare function externalCategory(cat: ScenarioCategory): TestCategory;
|
|
24
|
+
/**
|
|
25
|
+
* Categories whose scenarios target a specific identified defect — a code flaw
|
|
26
|
+
* (`bug_caught`) or a stated requirement the code contradicts
|
|
27
|
+
* (`requirement_conflict`). They survive external-test dedup: an existing test on
|
|
28
|
+
* the same endpoint exercises the surface, not the flaw, so removing them would
|
|
29
|
+
* drop the only test that fails on the defect.
|
|
30
|
+
*/
|
|
31
|
+
export declare const FLAW_TARGETING_CATEGORIES: readonly ["bug_caught", "requirement_conflict"];
|
|
32
|
+
/** Whether `category` targets a specific identified defect (see {@link FLAW_TARGETING_CATEGORIES}). */
|
|
33
|
+
export declare function isFlawTargetingCategory(category: ScenarioCategory | undefined): boolean;
|
|
@@ -17,6 +17,14 @@ export var Novelty;
|
|
|
17
17
|
const INTERNAL_CATEGORIES = [
|
|
18
18
|
"new_endpoint", // MEDIUM - diff-direct scenario; where a test came from, not a guarantee of a slot
|
|
19
19
|
"bug_caught", // CRITICAL - tests targeting a specific <bug_found> flaw identified during enrichment
|
|
20
|
+
// CRITICAL - tests asserting a requirement the PR title/description (or a
|
|
21
|
+
// requirements file it references) states, which the implemented behavior
|
|
22
|
+
// contradicts. Separate from bug_caught deliberately (SKYR-4291): labelled
|
|
23
|
+
// bug_caught, a requirement-vs-code mismatch competed with the code-review
|
|
24
|
+
// flaws for the same promotion and lost it on severity. Its own category means
|
|
25
|
+
// its own carve-out here, first place in the promotion order SKYR-4275's bound
|
|
26
|
+
// hands out, and its own coverage gate.
|
|
27
|
+
"requirement_conflict",
|
|
20
28
|
];
|
|
21
29
|
/** External categories valid for tool submissions, ordered by priority. */
|
|
22
30
|
const CATEGORIES = [
|
|
@@ -59,6 +67,12 @@ export const CATEGORY_PRIORITY = {
|
|
|
59
67
|
// fill GENERATE.
|
|
60
68
|
new_endpoint: PriorityTier.MEDIUM,
|
|
61
69
|
bug_caught: PriorityTier.CRITICAL, // tests targeting a <bug_found> flaw — always in GENERATE
|
|
70
|
+
// A stated requirement the implementation contradicts is the point of the PR,
|
|
71
|
+
// so it sits in the same top tier as bug_caught. Being its own category, it is
|
|
72
|
+
// carved out separately in planRanker and takes the FIRST slot of the promotion
|
|
73
|
+
// bound SKYR-4275 sets — the code-review flaws promote into what is left, so a
|
|
74
|
+
// requirement conflict is never the finding that loses on severity.
|
|
75
|
+
requirement_conflict: PriorityTier.CRITICAL,
|
|
62
76
|
business_rule: PriorityTier.HIGH, // formula/business-logic bugs are high priority
|
|
63
77
|
security_boundary: PriorityTier.HIGH,
|
|
64
78
|
data_integrity: PriorityTier.HIGH,
|
|
@@ -75,5 +89,25 @@ export function externalCategory(cat) {
|
|
|
75
89
|
return "crud";
|
|
76
90
|
if (cat === "bug_caught")
|
|
77
91
|
return "business_rule";
|
|
92
|
+
// The stated requirement IS the business rule the test asserts — same landing
|
|
93
|
+
// spot as bug_caught, so a requirement-conflict test reads as a rule check in
|
|
94
|
+
// the customer-facing report rather than an unrecognised label.
|
|
95
|
+
if (cat === "requirement_conflict")
|
|
96
|
+
return "business_rule";
|
|
78
97
|
return cat;
|
|
79
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Categories whose scenarios target a specific identified defect — a code flaw
|
|
101
|
+
* (`bug_caught`) or a stated requirement the code contradicts
|
|
102
|
+
* (`requirement_conflict`). They survive external-test dedup: an existing test on
|
|
103
|
+
* the same endpoint exercises the surface, not the flaw, so removing them would
|
|
104
|
+
* drop the only test that fails on the defect.
|
|
105
|
+
*/
|
|
106
|
+
export const FLAW_TARGETING_CATEGORIES = [
|
|
107
|
+
"bug_caught",
|
|
108
|
+
"requirement_conflict",
|
|
109
|
+
];
|
|
110
|
+
/** Whether `category` targets a specific identified defect (see {@link FLAW_TARGETING_CATEGORIES}). */
|
|
111
|
+
export function isFlawTargetingCategory(category) {
|
|
112
|
+
return category !== undefined && FLAW_TARGETING_CATEGORIES.includes(category);
|
|
113
|
+
}
|
|
@@ -22,7 +22,10 @@ export declare function isIndexModule(file: string): boolean;
|
|
|
22
22
|
* (`@/x`, `~/x`, `~~/x`, `#/x`, `src/x`) are accepted when the module directory
|
|
23
23
|
* ends with the aliased path AND the importer lives under the same root that the
|
|
24
24
|
* alias maps to — `packages/store/Page.tsx` importing `@/app/shop/_constants` does
|
|
25
|
-
* not match `packages/admin/app/shop/_constants/index.ts`.
|
|
25
|
+
* not match `packages/admin/app/shop/_constants/index.ts`. Prefix-less
|
|
26
|
+
* multi-segment specifiers (`Components/Foo` under tsconfig `baseUrl: "src"` —
|
|
27
|
+
* SKYR-4287) go through the same suffix + shared-root check; only single-segment
|
|
28
|
+
* prefix-less specifiers (`react`) are treated as npm packages. All paths are
|
|
26
29
|
* repo-relative with forward slashes.
|
|
27
30
|
*/
|
|
28
31
|
export declare function importsIndexModule(importerFile: string, importerContent: string, indexFile: string): boolean;
|
|
@@ -60,7 +60,10 @@ export function isIndexModule(file) {
|
|
|
60
60
|
* (`@/x`, `~/x`, `~~/x`, `#/x`, `src/x`) are accepted when the module directory
|
|
61
61
|
* ends with the aliased path AND the importer lives under the same root that the
|
|
62
62
|
* alias maps to — `packages/store/Page.tsx` importing `@/app/shop/_constants` does
|
|
63
|
-
* not match `packages/admin/app/shop/_constants/index.ts`.
|
|
63
|
+
* not match `packages/admin/app/shop/_constants/index.ts`. Prefix-less
|
|
64
|
+
* multi-segment specifiers (`Components/Foo` under tsconfig `baseUrl: "src"` —
|
|
65
|
+
* SKYR-4287) go through the same suffix + shared-root check; only single-segment
|
|
66
|
+
* prefix-less specifiers (`react`) are treated as npm packages. All paths are
|
|
64
67
|
* repo-relative with forward slashes.
|
|
65
68
|
*/
|
|
66
69
|
export function importsIndexModule(importerFile, importerContent, indexFile) {
|
|
@@ -76,7 +79,16 @@ export function importsIndexModule(importerFile, importerContent, indexFile) {
|
|
|
76
79
|
continue;
|
|
77
80
|
}
|
|
78
81
|
const aliased = spec.replace(/^(@\/|~~\/|~\/|#\/|src\/)/, "");
|
|
79
|
-
|
|
82
|
+
// A prefix-less specifier is only a bare npm package when it has a single
|
|
83
|
+
// segment ('react'). Multi-segment prefix-less specifiers are how tsconfig
|
|
84
|
+
// `baseUrl` repos import internal modules ('Components/Foo' with
|
|
85
|
+
// baseUrl: "src" — SKYR-4287: every folder-per-component index.tsx in such
|
|
86
|
+
// repos was misreported as no-importers). Those flow into the same
|
|
87
|
+
// suffix + shared-root check as aliases; a deep npm import ('lodash/get')
|
|
88
|
+
// is unlikely to match unless the repo actually contains a `lodash/get`
|
|
89
|
+
// directory under the same root, and a scoped package ('@scope/pkg') would
|
|
90
|
+
// require a source directory literally named '@scope'.
|
|
91
|
+
if (aliased === spec && !spec.includes("/"))
|
|
80
92
|
continue; // bare package specifier — not a repo path
|
|
81
93
|
if (moduleDir === aliased)
|
|
82
94
|
return true; // alias maps to the repo root
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-only mode capture for `skyramp_submit_report` (SKYR-4250).
|
|
3
|
+
*
|
|
4
|
+
* A plan-only eval run (SKYR-3879) writes no test files: `newTestsCreated`
|
|
5
|
+
* DECLARES the approved GENERATE selection instead of recording generated
|
|
6
|
+
* files, and the declaration itself is the deliverable. The SKYR-3883 guard
|
|
7
|
+
* (`findUnchangedFileClaims`) verifies every `newTestsCreated` claim against
|
|
8
|
+
* the working tree, so in that lane EVERY declaration is unbacked by
|
|
9
|
+
* construction and the report is rejected.
|
|
10
|
+
*
|
|
11
|
+
* That rejection is not evenly distributed. `findUnchangedFileClaims` exempts a
|
|
12
|
+
* claim attributed to a non-primary repo, so on a cross-repo fixture the
|
|
13
|
+
* related repo's declarations pass and only the primary repo's are checked.
|
|
14
|
+
* On a frontend-primary pair that makes the mandatory UI item (SKYR-4204's
|
|
15
|
+
* floor) the one claim that always fails, and the agent's only compliant move
|
|
16
|
+
* is to demote it to `additionalRecommendations` — which is exactly the
|
|
17
|
+
* under-declaration SKYR-4250 exists to fix (run 32903633247).
|
|
18
|
+
*
|
|
19
|
+
* `planOnly` reaches the server only as a testbot prompt/resource arg, so this
|
|
20
|
+
* mirrors the SKYR-4185 report-language capture: the prompt render is the
|
|
21
|
+
* single capture point, and the report tool reads the captured mode later in
|
|
22
|
+
* the same process.
|
|
23
|
+
*
|
|
24
|
+
* Capture is fail-open by design: if the testbot prompt was never served, the
|
|
25
|
+
* mode stays false and the guard behaves exactly as it does today.
|
|
26
|
+
*/
|
|
27
|
+
/** Capture plan-only mode at prompt-serve time. Last render wins: a later
|
|
28
|
+
* non-plan-only render disarms a mode captured earlier in a long-lived server
|
|
29
|
+
* process, so an ordinary run never silently skips the guard. */
|
|
30
|
+
export declare function setPlanOnlyMode(planOnly: boolean): void;
|
|
31
|
+
export declare function isPlanOnlyMode(): boolean;
|
|
32
|
+
/** Test isolation only — module state persists across tests in one process. */
|
|
33
|
+
export declare function resetPlanOnlyMode(): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-only mode capture for `skyramp_submit_report` (SKYR-4250).
|
|
3
|
+
*
|
|
4
|
+
* A plan-only eval run (SKYR-3879) writes no test files: `newTestsCreated`
|
|
5
|
+
* DECLARES the approved GENERATE selection instead of recording generated
|
|
6
|
+
* files, and the declaration itself is the deliverable. The SKYR-3883 guard
|
|
7
|
+
* (`findUnchangedFileClaims`) verifies every `newTestsCreated` claim against
|
|
8
|
+
* the working tree, so in that lane EVERY declaration is unbacked by
|
|
9
|
+
* construction and the report is rejected.
|
|
10
|
+
*
|
|
11
|
+
* That rejection is not evenly distributed. `findUnchangedFileClaims` exempts a
|
|
12
|
+
* claim attributed to a non-primary repo, so on a cross-repo fixture the
|
|
13
|
+
* related repo's declarations pass and only the primary repo's are checked.
|
|
14
|
+
* On a frontend-primary pair that makes the mandatory UI item (SKYR-4204's
|
|
15
|
+
* floor) the one claim that always fails, and the agent's only compliant move
|
|
16
|
+
* is to demote it to `additionalRecommendations` — which is exactly the
|
|
17
|
+
* under-declaration SKYR-4250 exists to fix (run 32903633247).
|
|
18
|
+
*
|
|
19
|
+
* `planOnly` reaches the server only as a testbot prompt/resource arg, so this
|
|
20
|
+
* mirrors the SKYR-4185 report-language capture: the prompt render is the
|
|
21
|
+
* single capture point, and the report tool reads the captured mode later in
|
|
22
|
+
* the same process.
|
|
23
|
+
*
|
|
24
|
+
* Capture is fail-open by design: if the testbot prompt was never served, the
|
|
25
|
+
* mode stays false and the guard behaves exactly as it does today.
|
|
26
|
+
*/
|
|
27
|
+
let sessionPlanOnly = false;
|
|
28
|
+
/** Capture plan-only mode at prompt-serve time. Last render wins: a later
|
|
29
|
+
* non-plan-only render disarms a mode captured earlier in a long-lived server
|
|
30
|
+
* process, so an ordinary run never silently skips the guard. */
|
|
31
|
+
export function setPlanOnlyMode(planOnly) {
|
|
32
|
+
sessionPlanOnly = planOnly;
|
|
33
|
+
}
|
|
34
|
+
export function isPlanOnlyMode() {
|
|
35
|
+
return sessionPlanOnly;
|
|
36
|
+
}
|
|
37
|
+
/** Test isolation only — module state persists across tests in one process. */
|
|
38
|
+
export function resetPlanOnlyMode() {
|
|
39
|
+
sessionPlanOnly = false;
|
|
40
|
+
}
|
|
@@ -50,6 +50,11 @@ export interface UnchangedFileClaimsInput {
|
|
|
50
50
|
/** owner/repo of the primary repo — used to exempt cross-repo newTest claims
|
|
51
51
|
* whose files live in a different checkout. */
|
|
52
52
|
primaryRepository?: string;
|
|
53
|
+
/** SKYR-4250: a plan-only eval run generates nothing — `newTestsCreated`
|
|
54
|
+
* declares the approved GENERATE list — so no declaration can be backed by
|
|
55
|
+
* a working-tree change. Exempts the declaration half only; a maintenance
|
|
56
|
+
* UPDATE still claims a real edit and is still verified. */
|
|
57
|
+
planOnly?: boolean;
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* Cross-check report claims against files that actually changed in the working
|
|
@@ -124,7 +124,7 @@ export async function listChangedFiles(repoRoot) {
|
|
|
124
124
|
* absolute path resolving outside `repoRoot`) — a different checkout owns them.
|
|
125
125
|
*/
|
|
126
126
|
export function findUnchangedFileClaims(input) {
|
|
127
|
-
const { repoRoot, changedFiles, newTests, verdicts, primaryRepository } = input;
|
|
127
|
+
const { repoRoot, changedFiles, newTests, verdicts, primaryRepository, planOnly, } = input;
|
|
128
128
|
const unbacked = [];
|
|
129
129
|
const isBacked = (claimedPath) => {
|
|
130
130
|
let candidate = claimedPath;
|
|
@@ -137,7 +137,12 @@ export function findUnchangedFileClaims(input) {
|
|
|
137
137
|
}
|
|
138
138
|
return changedFiles.some((c) => testFileMatches(c, candidate));
|
|
139
139
|
};
|
|
140
|
-
|
|
140
|
+
// A plan-only run declares its GENERATE list instead of writing files, so
|
|
141
|
+
// skip the claim scan wholesale rather than per-claim: checking it rejects
|
|
142
|
+
// every declaration the guard can see, and the ones it CANNOT see (cross-repo
|
|
143
|
+
// claims, exempted below) would pass — demoting the primary repo's items
|
|
144
|
+
// alone. Uneven enforcement is worse than none here.
|
|
145
|
+
for (const t of planOnly ? [] : newTests) {
|
|
141
146
|
if (!t.fileName)
|
|
142
147
|
continue;
|
|
143
148
|
// Cross-repo finding — its file lives in a different checkout, not this tree.
|
|
@@ -46,7 +46,7 @@ export declare function draftScenariosFromEndpoints(endpoints: Array<EndpointInp
|
|
|
46
46
|
/**
|
|
47
47
|
* Enforce a global cap on drafted scenarios while preserving category diversity.
|
|
48
48
|
*
|
|
49
|
-
* 1. CRITICAL (bug_caught) scenarios prioritized first.
|
|
49
|
+
* 1. CRITICAL (bug_caught, requirement_conflict) scenarios prioritized first.
|
|
50
50
|
* 2. One scenario per non-empty category guaranteed (breadth).
|
|
51
51
|
* 3. Remaining budget filled by priority tier (HIGH > MEDIUM > LOW).
|
|
52
52
|
* 4. Hard cap at MAX_TOTAL_SCENARIOS — applied to the combined output.
|
|
@@ -226,7 +226,7 @@ const TIER_ORDER = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1 };
|
|
|
226
226
|
/**
|
|
227
227
|
* Enforce a global cap on drafted scenarios while preserving category diversity.
|
|
228
228
|
*
|
|
229
|
-
* 1. CRITICAL (bug_caught) scenarios prioritized first.
|
|
229
|
+
* 1. CRITICAL (bug_caught, requirement_conflict) scenarios prioritized first.
|
|
230
230
|
* 2. One scenario per non-empty category guaranteed (breadth).
|
|
231
231
|
* 3. Remaining budget filled by priority tier (HIGH > MEDIUM > LOW).
|
|
232
232
|
* 4. Hard cap at MAX_TOTAL_SCENARIOS — applied to the combined output.
|