gsd-pi 2.38.0-dev.5492881 → 2.38.0-dev.7209774
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/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +8 -1
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/package.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -1
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
|
@@ -4,7 +4,7 @@ let registrationPromise = null;
|
|
|
4
4
|
async function registerBrowserTools(pi) {
|
|
5
5
|
if (!registrationPromise) {
|
|
6
6
|
registrationPromise = (async () => {
|
|
7
|
-
const [lifecycle, capture, settle, refs, utils, navigation, screenshot, interaction, inspection, session, assertions, refTools, wait, pages, forms, intent, pdf, statePersistence, networkMock, device, extract, visualDiff, zoom, codegen, actionCache, injectionDetection,] = await Promise.all([
|
|
7
|
+
const [lifecycle, capture, settle, refs, utils, navigation, screenshot, interaction, inspection, session, assertions, refTools, wait, pages, forms, intent, pdf, statePersistence, networkMock, device, extract, visualDiff, zoom, codegen, actionCache, injectionDetection, verify,] = await Promise.all([
|
|
8
8
|
importExtensionModule(import.meta.url, "./lifecycle.js"),
|
|
9
9
|
importExtensionModule(import.meta.url, "./capture.js"),
|
|
10
10
|
importExtensionModule(import.meta.url, "./settle.js"),
|
|
@@ -31,6 +31,7 @@ async function registerBrowserTools(pi) {
|
|
|
31
31
|
importExtensionModule(import.meta.url, "./tools/codegen.js"),
|
|
32
32
|
importExtensionModule(import.meta.url, "./tools/action-cache.js"),
|
|
33
33
|
importExtensionModule(import.meta.url, "./tools/injection-detect.js"),
|
|
34
|
+
importExtensionModule(import.meta.url, "./tools/verify.js"),
|
|
34
35
|
]);
|
|
35
36
|
const deps = {
|
|
36
37
|
ensureBrowser: lifecycle.ensureBrowser,
|
|
@@ -99,6 +100,7 @@ async function registerBrowserTools(pi) {
|
|
|
99
100
|
codegen.registerCodegenTools(pi, deps);
|
|
100
101
|
actionCache.registerActionCacheTools(pi, deps);
|
|
101
102
|
injectionDetection.registerInjectionDetectionTools(pi, deps);
|
|
103
|
+
verify.registerVerifyTools(pi, deps);
|
|
102
104
|
})().catch((error) => {
|
|
103
105
|
registrationPromise = null;
|
|
104
106
|
throw error;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
export function registerVerifyTools(pi, deps) {
|
|
3
|
+
pi.registerTool({
|
|
4
|
+
name: "browser_verify",
|
|
5
|
+
label: "Browser Verify",
|
|
6
|
+
description: "Run a structured browser verification flow: navigate to a URL, run checks (element visibility, text content), capture screenshots as evidence, and return structured pass/fail results.",
|
|
7
|
+
promptGuidelines: [
|
|
8
|
+
"Use browser_verify for UAT verification flows that need structured evidence.",
|
|
9
|
+
"Each check produces a pass/fail result with captured evidence.",
|
|
10
|
+
"Prefer this over manual navigation + assertion sequences for verification tasks.",
|
|
11
|
+
],
|
|
12
|
+
parameters: Type.Object({
|
|
13
|
+
url: Type.String({ description: "URL to navigate to" }),
|
|
14
|
+
checks: Type.Array(Type.Object({
|
|
15
|
+
description: Type.String({ description: "What this check verifies" }),
|
|
16
|
+
selector: Type.Optional(Type.String({ description: "CSS selector to check" })),
|
|
17
|
+
expectedText: Type.Optional(Type.String({ description: "Expected text content" })),
|
|
18
|
+
expectedVisible: Type.Optional(Type.Boolean({ description: "Whether element should be visible" })),
|
|
19
|
+
screenshot: Type.Optional(Type.Boolean({ description: "Capture screenshot as evidence" })),
|
|
20
|
+
}), { description: "Verification checks to run" }),
|
|
21
|
+
timeout: Type.Optional(Type.Number({ description: "Navigation timeout in ms", default: 10000 })),
|
|
22
|
+
}),
|
|
23
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
const { page } = await deps.ensureBrowser();
|
|
26
|
+
const timeout = params.timeout ?? 10000;
|
|
27
|
+
try {
|
|
28
|
+
await page.goto(params.url, { waitUntil: "domcontentloaded", timeout });
|
|
29
|
+
}
|
|
30
|
+
catch (navErr) {
|
|
31
|
+
const msg = navErr instanceof Error ? navErr.message : String(navErr);
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Navigation failed: ${msg}` }],
|
|
34
|
+
details: {
|
|
35
|
+
url: params.url,
|
|
36
|
+
passed: false,
|
|
37
|
+
checks: params.checks.map((c) => ({ description: c.description, passed: false, error: msg })),
|
|
38
|
+
duration: Date.now() - startTime,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const check of params.checks) {
|
|
44
|
+
try {
|
|
45
|
+
let passed = true;
|
|
46
|
+
let actual;
|
|
47
|
+
let evidence;
|
|
48
|
+
if (check.selector) {
|
|
49
|
+
const element = await page.$(check.selector);
|
|
50
|
+
if (check.expectedVisible !== undefined) {
|
|
51
|
+
const isVisible = element ? await element.isVisible() : false;
|
|
52
|
+
passed = isVisible === check.expectedVisible;
|
|
53
|
+
actual = `visible=${isVisible}`;
|
|
54
|
+
}
|
|
55
|
+
if (check.expectedText !== undefined && element) {
|
|
56
|
+
const text = await element.textContent();
|
|
57
|
+
passed = passed && (text?.includes(check.expectedText) ?? false);
|
|
58
|
+
actual = `text="${text?.slice(0, 200)}"`;
|
|
59
|
+
}
|
|
60
|
+
if (!element && (check.expectedVisible === true || check.expectedText)) {
|
|
61
|
+
passed = false;
|
|
62
|
+
actual = "element not found";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (check.screenshot) {
|
|
66
|
+
try {
|
|
67
|
+
const buf = await page.screenshot({ type: "png" });
|
|
68
|
+
evidence = `screenshot captured (${buf.length} bytes)`;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
evidence = "screenshot failed";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
results.push({ description: check.description, passed, actual, evidence });
|
|
75
|
+
}
|
|
76
|
+
catch (checkErr) {
|
|
77
|
+
results.push({
|
|
78
|
+
description: check.description,
|
|
79
|
+
passed: false,
|
|
80
|
+
error: checkErr instanceof Error ? checkErr.message : String(checkErr),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const allPassed = results.every((r) => r.passed);
|
|
85
|
+
const summary = results.map((r) => `${r.passed ? "PASS" : "FAIL"}: ${r.description}${r.actual ? ` (${r.actual})` : ""}${r.error ? ` — ${r.error}` : ""}`).join("\n");
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `Verification ${allPassed ? "PASSED" : "FAILED"} (${results.filter(r => r.passed).length}/${results.length})\n\n${summary}` }],
|
|
88
|
+
details: {
|
|
89
|
+
url: params.url,
|
|
90
|
+
passed: allPassed,
|
|
91
|
+
checks: results,
|
|
92
|
+
duration: Date.now() - startTime,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
9
9
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
10
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, } from "./paths.js";
|
|
10
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
11
11
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
@@ -767,8 +767,15 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
767
767
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
768
768
|
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
769
769
|
}
|
|
770
|
+
// Inline RUNTIME.md if present
|
|
771
|
+
const runtimePath = resolveRuntimeFile(base);
|
|
772
|
+
const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
|
|
773
|
+
const runtimeContext = runtimeContent
|
|
774
|
+
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
775
|
+
: "";
|
|
770
776
|
return loadPrompt("execute-task", {
|
|
771
777
|
overridesSection,
|
|
778
|
+
runtimeContext,
|
|
772
779
|
workingDirectory: base,
|
|
773
780
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
774
781
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -343,6 +343,9 @@ function probeGsdRoot(rawBasePath) {
|
|
|
343
343
|
export function milestonesDir(basePath) {
|
|
344
344
|
return join(gsdRoot(basePath), "milestones");
|
|
345
345
|
}
|
|
346
|
+
export function resolveRuntimeFile(basePath) {
|
|
347
|
+
return join(gsdRoot(basePath), "RUNTIME.md");
|
|
348
|
+
}
|
|
346
349
|
export function resolveGsdRootFile(basePath, key) {
|
|
347
350
|
const root = gsdRoot(basePath);
|
|
348
351
|
const canonical = join(root, GSD_ROOT_FILES[key]);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Runtime Context
|
|
2
|
+
|
|
3
|
+
## Stack
|
|
4
|
+
- **Language:** (e.g., TypeScript, Python, Go)
|
|
5
|
+
- **Framework:** (e.g., Next.js, FastAPI, Gin)
|
|
6
|
+
- **Build:** (e.g., npm run build, cargo build)
|
|
7
|
+
- **Test:** (e.g., npm run test, pytest)
|
|
8
|
+
- **Lint:** (e.g., npm run lint, ruff check)
|
|
9
|
+
|
|
10
|
+
## Environment
|
|
11
|
+
- **Node version:** (e.g., 20.x)
|
|
12
|
+
- **Package manager:** (e.g., npm, pnpm, yarn)
|
|
13
|
+
- **Required env vars:** (list any needed for local dev)
|
|
14
|
+
|
|
15
|
+
## Dev Server
|
|
16
|
+
- **Start command:** (e.g., npm run dev)
|
|
17
|
+
- **Default port:** (e.g., 3000)
|
|
18
|
+
- **Health check:** (e.g., curl http://localhost:3000/health)
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
(Any runtime-specific context the executor needs to know)
|
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ async function registerBrowserTools(pi: ExtensionAPI): Promise<void> {
|
|
|
33
33
|
codegen,
|
|
34
34
|
actionCache,
|
|
35
35
|
injectionDetection,
|
|
36
|
+
verify,
|
|
36
37
|
] = await Promise.all([
|
|
37
38
|
importExtensionModule<typeof import("./lifecycle.js")>(import.meta.url, "./lifecycle.js"),
|
|
38
39
|
importExtensionModule<typeof import("./capture.js")>(import.meta.url, "./capture.js"),
|
|
@@ -60,6 +61,7 @@ async function registerBrowserTools(pi: ExtensionAPI): Promise<void> {
|
|
|
60
61
|
importExtensionModule<typeof import("./tools/codegen.js")>(import.meta.url, "./tools/codegen.js"),
|
|
61
62
|
importExtensionModule<typeof import("./tools/action-cache.js")>(import.meta.url, "./tools/action-cache.js"),
|
|
62
63
|
importExtensionModule<typeof import("./tools/injection-detect.js")>(import.meta.url, "./tools/injection-detect.js"),
|
|
64
|
+
importExtensionModule<typeof import("./tools/verify.js")>(import.meta.url, "./tools/verify.js"),
|
|
63
65
|
]);
|
|
64
66
|
|
|
65
67
|
const deps = {
|
|
@@ -132,6 +134,7 @@ async function registerBrowserTools(pi: ExtensionAPI): Promise<void> {
|
|
|
132
134
|
codegen.registerCodegenTools(pi, deps);
|
|
133
135
|
actionCache.registerActionCacheTools(pi, deps);
|
|
134
136
|
injectionDetection.registerInjectionDetectionTools(pi, deps);
|
|
137
|
+
verify.registerVerifyTools(pi, deps);
|
|
135
138
|
})().catch((error) => {
|
|
136
139
|
registrationPromise = null;
|
|
137
140
|
throw error;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { ToolDeps } from "../state.js";
|
|
4
|
+
|
|
5
|
+
export function registerVerifyTools(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
6
|
+
pi.registerTool({
|
|
7
|
+
name: "browser_verify",
|
|
8
|
+
label: "Browser Verify",
|
|
9
|
+
description:
|
|
10
|
+
"Run a structured browser verification flow: navigate to a URL, run checks (element visibility, text content), capture screenshots as evidence, and return structured pass/fail results.",
|
|
11
|
+
promptGuidelines: [
|
|
12
|
+
"Use browser_verify for UAT verification flows that need structured evidence.",
|
|
13
|
+
"Each check produces a pass/fail result with captured evidence.",
|
|
14
|
+
"Prefer this over manual navigation + assertion sequences for verification tasks.",
|
|
15
|
+
],
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
url: Type.String({ description: "URL to navigate to" }),
|
|
18
|
+
checks: Type.Array(
|
|
19
|
+
Type.Object({
|
|
20
|
+
description: Type.String({ description: "What this check verifies" }),
|
|
21
|
+
selector: Type.Optional(Type.String({ description: "CSS selector to check" })),
|
|
22
|
+
expectedText: Type.Optional(Type.String({ description: "Expected text content" })),
|
|
23
|
+
expectedVisible: Type.Optional(Type.Boolean({ description: "Whether element should be visible" })),
|
|
24
|
+
screenshot: Type.Optional(Type.Boolean({ description: "Capture screenshot as evidence" })),
|
|
25
|
+
}),
|
|
26
|
+
{ description: "Verification checks to run" },
|
|
27
|
+
),
|
|
28
|
+
timeout: Type.Optional(Type.Number({ description: "Navigation timeout in ms", default: 10000 })),
|
|
29
|
+
}),
|
|
30
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
const { page } = await deps.ensureBrowser();
|
|
33
|
+
const timeout = params.timeout ?? 10000;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await page.goto(params.url, { waitUntil: "domcontentloaded", timeout });
|
|
37
|
+
} catch (navErr) {
|
|
38
|
+
const msg = navErr instanceof Error ? navErr.message : String(navErr);
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text" as const, text: `Navigation failed: ${msg}` }],
|
|
41
|
+
details: {
|
|
42
|
+
url: params.url,
|
|
43
|
+
passed: false,
|
|
44
|
+
checks: params.checks.map((c) => ({ description: c.description, passed: false, error: msg })),
|
|
45
|
+
duration: Date.now() - startTime,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const results: Array<{
|
|
51
|
+
description: string;
|
|
52
|
+
passed: boolean;
|
|
53
|
+
actual?: string;
|
|
54
|
+
evidence?: string;
|
|
55
|
+
error?: string;
|
|
56
|
+
}> = [];
|
|
57
|
+
|
|
58
|
+
for (const check of params.checks) {
|
|
59
|
+
try {
|
|
60
|
+
let passed = true;
|
|
61
|
+
let actual: string | undefined;
|
|
62
|
+
let evidence: string | undefined;
|
|
63
|
+
|
|
64
|
+
if (check.selector) {
|
|
65
|
+
const element = await page.$(check.selector);
|
|
66
|
+
|
|
67
|
+
if (check.expectedVisible !== undefined) {
|
|
68
|
+
const isVisible = element ? await element.isVisible() : false;
|
|
69
|
+
passed = isVisible === check.expectedVisible;
|
|
70
|
+
actual = `visible=${isVisible}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (check.expectedText !== undefined && element) {
|
|
74
|
+
const text = await element.textContent();
|
|
75
|
+
passed = passed && (text?.includes(check.expectedText) ?? false);
|
|
76
|
+
actual = `text="${text?.slice(0, 200)}"`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!element && (check.expectedVisible === true || check.expectedText)) {
|
|
80
|
+
passed = false;
|
|
81
|
+
actual = "element not found";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (check.screenshot) {
|
|
86
|
+
try {
|
|
87
|
+
const buf = await page.screenshot({ type: "png" });
|
|
88
|
+
evidence = `screenshot captured (${buf.length} bytes)`;
|
|
89
|
+
} catch {
|
|
90
|
+
evidence = "screenshot failed";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
results.push({ description: check.description, passed, actual, evidence });
|
|
95
|
+
} catch (checkErr) {
|
|
96
|
+
results.push({
|
|
97
|
+
description: check.description,
|
|
98
|
+
passed: false,
|
|
99
|
+
error: checkErr instanceof Error ? checkErr.message : String(checkErr),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const allPassed = results.every((r) => r.passed);
|
|
105
|
+
const summary = results.map((r) => `${r.passed ? "PASS" : "FAIL"}: ${r.description}${r.actual ? ` (${r.actual})` : ""}${r.error ? ` — ${r.error}` : ""}`).join("\n");
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text" as const, text: `Verification ${allPassed ? "PASSED" : "FAILED"} (${results.filter(r => r.passed).length}/${results.length})\n\n${summary}` }],
|
|
108
|
+
details: {
|
|
109
|
+
url: params.url,
|
|
110
|
+
passed: allPassed,
|
|
111
|
+
checks: results,
|
|
112
|
+
duration: Date.now() - startTime,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
14
14
|
resolveTasksDir, resolveTaskFiles, resolveTaskFile,
|
|
15
15
|
relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath,
|
|
16
|
-
resolveGsdRootFile, relGsdRootFile,
|
|
16
|
+
resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile,
|
|
17
17
|
} from "./paths.js";
|
|
18
18
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
19
19
|
import type { GSDState, InlineLevel } from "./types.js";
|
|
@@ -891,8 +891,16 @@ export async function buildExecuteTaskPrompt(
|
|
|
891
891
|
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
892
892
|
}
|
|
893
893
|
|
|
894
|
+
// Inline RUNTIME.md if present
|
|
895
|
+
const runtimePath = resolveRuntimeFile(base);
|
|
896
|
+
const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
|
|
897
|
+
const runtimeContext = runtimeContent
|
|
898
|
+
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
899
|
+
: "";
|
|
900
|
+
|
|
894
901
|
return loadPrompt("execute-task", {
|
|
895
902
|
overridesSection,
|
|
903
|
+
runtimeContext,
|
|
896
904
|
workingDirectory: base,
|
|
897
905
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
898
906
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -356,6 +356,10 @@ export function milestonesDir(basePath: string): string {
|
|
|
356
356
|
return join(gsdRoot(basePath), "milestones");
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
export function resolveRuntimeFile(basePath: string): string {
|
|
360
|
+
return join(gsdRoot(basePath), "RUNTIME.md");
|
|
361
|
+
}
|
|
362
|
+
|
|
359
363
|
export function resolveGsdRootFile(basePath: string, key: GSDRootFileKey): string {
|
|
360
364
|
const root = gsdRoot(basePath);
|
|
361
365
|
const canonical = join(root, GSD_ROOT_FILES[key]);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Runtime Context
|
|
2
|
+
|
|
3
|
+
## Stack
|
|
4
|
+
- **Language:** (e.g., TypeScript, Python, Go)
|
|
5
|
+
- **Framework:** (e.g., Next.js, FastAPI, Gin)
|
|
6
|
+
- **Build:** (e.g., npm run build, cargo build)
|
|
7
|
+
- **Test:** (e.g., npm run test, pytest)
|
|
8
|
+
- **Lint:** (e.g., npm run lint, ruff check)
|
|
9
|
+
|
|
10
|
+
## Environment
|
|
11
|
+
- **Node version:** (e.g., 20.x)
|
|
12
|
+
- **Package manager:** (e.g., npm, pnpm, yarn)
|
|
13
|
+
- **Required env vars:** (list any needed for local dev)
|
|
14
|
+
|
|
15
|
+
## Dev Server
|
|
16
|
+
- **Start command:** (e.g., npm run dev)
|
|
17
|
+
- **Default port:** (e.g., 3000)
|
|
18
|
+
- **Health check:** (e.g., curl http://localhost:3000/health)
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
(Any runtime-specific context the executor needs to know)
|
|
@@ -37,6 +37,21 @@ export interface AuditWarningJSON {
|
|
|
37
37
|
fixAvailable: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export interface BrowserEvidenceCheckJSON {
|
|
41
|
+
description: string;
|
|
42
|
+
passed: boolean;
|
|
43
|
+
actual?: string;
|
|
44
|
+
evidence?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BrowserEvidenceJSON {
|
|
49
|
+
url: string;
|
|
50
|
+
passed: boolean;
|
|
51
|
+
checks: BrowserEvidenceCheckJSON[];
|
|
52
|
+
duration: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
export interface EvidenceJSON {
|
|
41
56
|
schemaVersion: 1;
|
|
42
57
|
taskId: string;
|
|
@@ -49,6 +64,7 @@ export interface EvidenceJSON {
|
|
|
49
64
|
maxRetries?: number;
|
|
50
65
|
runtimeErrors?: RuntimeErrorJSON[];
|
|
51
66
|
auditWarnings?: AuditWarningJSON[];
|
|
67
|
+
browser?: BrowserEvidenceJSON;
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
/**
|