@dailephd/my-dev-kit-lab 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  my-dev-kit-lab is the experiment, evidence, reporting, and demo companion for [my-dev-kit](https://github.com/your-org/my-dev-kit). It runs reproducible experiments that test whether my-dev-kit's graph-guided retrieval helps coding-agent workflows, collects metrics, renders reports, generates plots, captures screenshots, and builds gallery outputs.
4
4
 
5
- As of v0.2.0, my-dev-kit-lab also exposes a generic experiment-plugin framework for validating local developer tools, security checks, codebase workflows, retrieval strategies, and experiment outcomes. The first plugin is `context-strategy-comparison`, which preserves the existing raw-full-file vs my-dev-kit-guided workflow through the plugin runner.
5
+ As of v0.2.1, my-dev-kit-lab also exposes a generic experiment-plugin framework for validating local developer tools, security checks, codebase workflows, retrieval strategies, and experiment outcomes. The first plugin is `context-strategy-comparison`, which preserves the existing raw-full-file vs my-dev-kit-guided workflow through the plugin runner.
6
6
 
7
7
  **my-dev-kit** is the repo indexing and graph-guided retrieval engine.
8
8
  **my-dev-kit-lab** is the separate lab layer that feeds it benchmark inputs and records evaluation outputs.
@@ -205,7 +205,7 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
205
205
 
206
206
  ## Current baseline release positioning
207
207
 
208
- my-dev-kit-lab is at a working baseline. The raw-vs-indexed experiment pipeline is fully implemented and produces reproducible artifacts. Real-agent campaign support exists for Codex and Claude. v0.2.0 adds the generic experiment-plugin framework, keeps `context-strategy-comparison` as the first plugin, and keeps future experiment types on the roadmap.
208
+ my-dev-kit-lab is at a working baseline. The raw-vs-indexed experiment pipeline is fully implemented and produces reproducible artifacts. Real-agent campaign support exists for Codex and Claude. v0.2.1 keeps the generic experiment-plugin framework, preserves `context-strategy-comparison` as the first plugin, and fixes target-aware security validation so installed-package checks run `npm run test:security` in the external target project root.
209
209
 
210
210
  ---
211
211
 
@@ -239,7 +239,9 @@ The release gate is implemented as of v0.1.4. It combines static scans, dependen
239
239
 
240
240
  CodeQL, Semgrep, and OSV-Scanner are optional. When unavailable locally, they are recorded as `skipped` in the report — not as failures — and the verdict is `ready except optional manual checks` rather than `not ready`.
241
241
 
242
- Each security command can validate my-dev-kit-lab itself or another local project via `--target <path>`. When `--target` is omitted, the framework performs self-validation. Target projects are inspected in place: their source files are not modified, generated artifacts stay under `reports/security/`, and external-target reports identify both the tool root and the target root.
242
+ Each security command can validate my-dev-kit-lab itself or another local project via `--target <path>`. When `--target` is omitted, the framework performs self-validation. Target projects are inspected in place: their source files are not modified, generated artifacts stay under `reports/security/`, and external-target reports identify the tool root, target root, target package metadata, target git metadata, command cwd, exit code, and stdout/stderr summaries.
243
+
244
+ For external-target validation, `security:validate` reads the target `package.json`, detects `scripts.test:security`, and runs `npm run test:security` in the target project root when that script exists. This behavior is validated both from the source checkout and from an installed packed tarball because published-package execution differs from local source execution.
243
245
 
244
246
  Generated security reports under `reports/security/` are excluded from git by default. They are produced locally or in CI as release-gate evidence and are not committed to the repository.
245
247
 
@@ -0,0 +1,16 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ export function resolveToolRoot(moduleUrl) {
5
+ let current = path.dirname(fileURLToPath(moduleUrl));
6
+ while (true) {
7
+ if (fs.existsSync(path.join(current, "package.json"))) {
8
+ return current;
9
+ }
10
+ const parent = path.dirname(current);
11
+ if (parent === current) {
12
+ return process.cwd();
13
+ }
14
+ current = parent;
15
+ }
16
+ }
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { runCodeqlCheck } from "../../src/securityValidation/staticScans/codeql.js";
3
3
  import { resolveValidationTarget } from "../../src/securityValidation/validate/resolveTarget.js";
4
+ import { resolveToolRoot } from "./resolveToolRoot.js";
4
5
  const rawArgs = process.argv.slice(2);
5
6
  const args = parseArgs(rawArgs);
6
- const toolRoot = process.cwd();
7
+ const toolRoot = resolveToolRoot(import.meta.url);
7
8
  let targetRoot;
8
9
  try {
9
10
  const target = resolveValidationTarget(args.target, toolRoot);
@@ -3,9 +3,10 @@ import path from "node:path";
3
3
  import { runDependencyChecks } from "../../src/securityValidation/index.js";
4
4
  import { DEFAULT_SECURITY_CONFIG } from "../../src/securityValidation/index.js";
5
5
  import { resolveValidationTarget } from "../../src/securityValidation/validate/resolveTarget.js";
6
+ import { resolveToolRoot } from "./resolveToolRoot.js";
6
7
  const rawArgs = process.argv.slice(2);
7
8
  const args = parseArgs(rawArgs);
8
- const toolRoot = process.cwd();
9
+ const toolRoot = resolveToolRoot(import.meta.url);
9
10
  let targetRoot;
10
11
  try {
11
12
  const target = resolveValidationTarget(args.target, toolRoot);
@@ -3,9 +3,10 @@ import path from "node:path";
3
3
  import { runPackageChecks } from "../../src/securityValidation/index.js";
4
4
  import { DEFAULT_SECURITY_CONFIG } from "../../src/securityValidation/index.js";
5
5
  import { resolveValidationTarget } from "../../src/securityValidation/validate/resolveTarget.js";
6
+ import { resolveToolRoot } from "./resolveToolRoot.js";
6
7
  const rawArgs = process.argv.slice(2);
7
8
  const args = parseArgs(rawArgs);
8
- const toolRoot = process.cwd();
9
+ const toolRoot = resolveToolRoot(import.meta.url);
9
10
  let targetRoot;
10
11
  try {
11
12
  const target = resolveValidationTarget(args.target, toolRoot);
@@ -2,9 +2,10 @@
2
2
  import path from "node:path";
3
3
  import { runSemgrepCheck } from "../../src/securityValidation/staticScans/semgrep.js";
4
4
  import { resolveValidationTarget } from "../../src/securityValidation/validate/resolveTarget.js";
5
+ import { resolveToolRoot } from "./resolveToolRoot.js";
5
6
  const rawArgs = process.argv.slice(2);
6
7
  const args = parseArgs(rawArgs);
7
- const toolRoot = process.cwd();
8
+ const toolRoot = resolveToolRoot(import.meta.url);
8
9
  let targetRoot;
9
10
  try {
10
11
  const target = resolveValidationTarget(args.target, toolRoot);
@@ -4,10 +4,11 @@ import fs from "node:fs";
4
4
  import { runSecurityValidation } from "../../src/securityValidation/validate/runSecurityValidation.js";
5
5
  import { resolveValidationTarget, reportFilenamePrefix } from "../../src/securityValidation/validate/resolveTarget.js";
6
6
  import { renderTextReport, renderJsonReport } from "../../src/securityValidation/report/renderSecurityReport.js";
7
+ import { resolveToolRoot } from "./resolveToolRoot.js";
7
8
  // Parse CLI arguments from process.argv (after the node/tsx and script path).
8
9
  const rawArgs = process.argv.slice(2);
9
10
  const args = parseArgs(rawArgs);
10
- const toolRoot = process.cwd();
11
+ const toolRoot = resolveToolRoot(import.meta.url);
11
12
  // Resolve and validate target early so we can fail fast with a clean error.
12
13
  let target;
13
14
  try {
@@ -27,6 +27,7 @@ export function resolveLocalProjectTarget(targetPathArg, toolRoot) {
27
27
  packageName: packageMetadata.packageName,
28
28
  packageVersion: packageMetadata.packageVersion,
29
29
  hasPackageJson: packageMetadata.hasPackageJson,
30
+ hasSecurityTestScript: packageMetadata.hasSecurityTestScript,
30
31
  hasLockfile: hasLockfile(resolvedTargetRoot),
31
32
  ...readGitMetadata(resolvedTargetRoot),
32
33
  isSelf: resolvedTargetRoot === resolvedToolRoot,
@@ -36,10 +37,12 @@ function readPackageMetadata(targetRoot) {
36
37
  try {
37
38
  const pkgRaw = fs.readFileSync(path.join(targetRoot, "package.json"), "utf8");
38
39
  const pkg = JSON.parse(pkgRaw);
40
+ const testSecurityScript = pkg.scripts && typeof pkg.scripts["test:security"] === "string";
39
41
  return {
40
42
  hasPackageJson: true,
41
43
  packageName: typeof pkg.name === "string" ? pkg.name : null,
42
44
  packageVersion: typeof pkg.version === "string" ? pkg.version : null,
45
+ hasSecurityTestScript: Boolean(testSecurityScript),
43
46
  };
44
47
  }
45
48
  catch {
@@ -47,6 +50,7 @@ function readPackageMetadata(targetRoot) {
47
50
  hasPackageJson: false,
48
51
  packageName: null,
49
52
  packageVersion: null,
53
+ hasSecurityTestScript: false,
50
54
  };
51
55
  }
52
56
  }
@@ -70,9 +70,9 @@ export async function runMeasuredCommand(options) {
70
70
  resolvedCommand: resolution
71
71
  };
72
72
  void Promise.all([
73
- writeFile(stdoutPath, stdout, "utf8"),
74
- writeFile(stderrPath, stderr, "utf8"),
75
- writeFile(telemetryPath, JSON.stringify(measured, null, 2), "utf8")
73
+ writeArtifact(stdoutPath, stdout),
74
+ writeArtifact(stderrPath, stderr),
75
+ writeArtifact(telemetryPath, JSON.stringify(measured, null, 2))
76
76
  ]).then(() => resolve(measured));
77
77
  return;
78
78
  }
@@ -98,8 +98,8 @@ export async function runMeasuredCommand(options) {
98
98
  }
99
99
  const endedAt = new Date().toISOString();
100
100
  const durationMs = Date.now() - started;
101
- await writeFile(stdoutPath, stdout, "utf8");
102
- await writeFile(stderrPath, stderr, "utf8");
101
+ await writeArtifact(stdoutPath, stdout);
102
+ await writeArtifact(stderrPath, stderr);
103
103
  const measured = {
104
104
  commandId: options.commandId,
105
105
  commandString: options.commandString,
@@ -119,12 +119,16 @@ export async function runMeasuredCommand(options) {
119
119
  error: spawnError,
120
120
  resolvedCommand: resolution
121
121
  };
122
- await writeFile(telemetryPath, JSON.stringify(measured, null, 2), "utf8");
122
+ await writeArtifact(telemetryPath, JSON.stringify(measured, null, 2));
123
123
  resolve(measured);
124
124
  });
125
125
  });
126
126
  return result;
127
127
  }
128
+ async function writeArtifact(filePath, value) {
129
+ await mkdir(path.dirname(filePath), { recursive: true });
130
+ await writeFile(filePath, value, "utf8");
131
+ }
128
132
  function killProcessTree(pid) {
129
133
  if (!pid) {
130
134
  return;
@@ -72,7 +72,7 @@ export function renderTextReport(report) {
72
72
  { num: 7, title: "npm audit Result", ids: ["npm-audit-full", "npm-audit-runtime"] },
73
73
  { num: 8, title: "OSV-Scanner Result", ids: ["osv-scanner"] },
74
74
  { num: 9, title: "Package Tarball Inspection", ids: ["npm-pack-dry-run"] },
75
- { num: 10, title: "File-System / Path Traversal Tests", ids: ["path-traversal-root", "path-traversal-out", "path-traversal-index", "absolute-path-escape", "path-harness-escape-detection"] },
75
+ { num: 10, title: "CLI / File-System / Path Traversal Tests", ids: ["cli-adversarial-suite", "path-traversal-root", "path-traversal-out", "path-traversal-index", "absolute-path-escape", "path-harness-escape-detection"] },
76
76
  { num: 11, title: "Source Read-Only Boundary Tests", ids: ["source-files-not-modified", "writes-limited-to-output", "index-write-containment", "artifact-cleanup-safe"] },
77
77
  { num: 12, title: "Graphviz / Subprocess Safety Tests", ids: ["graphviz-label-escaping", "subprocess-no-shell-interpolation"] },
78
78
  { num: 13, title: "JSON stdout/stderr Safety Tests", ids: ["json-mode-parseable-output", "stderr-not-in-stdout", "json-failure-error-object", "progress-not-in-json-stdout"] },
@@ -144,6 +144,21 @@ export function renderTextReport(report) {
144
144
  }
145
145
  for (const check of sectionChecks) {
146
146
  lines.push(`[${statusIcon(check.status)}] ${pad(check.name)} ${formatDuration(check.durationMs)}`);
147
+ if (check.command) {
148
+ lines.push(` Command: ${check.command}`);
149
+ }
150
+ if (check.commandCwd) {
151
+ lines.push(` Cwd: ${check.commandCwd}`);
152
+ }
153
+ if (check.exitCode !== undefined) {
154
+ lines.push(` Exit code: ${String(check.exitCode)}`);
155
+ }
156
+ if (check.stdoutSummary) {
157
+ lines.push(` Stdout: ${check.stdoutSummary.slice(0, 200)}`);
158
+ }
159
+ if (check.stderrSummary) {
160
+ lines.push(` Stderr: ${check.stderrSummary.slice(0, 200)}`);
161
+ }
147
162
  if (check.status === "skipped" && check.skippedReason) {
148
163
  lines.push(` Reason: ${check.skippedReason}`);
149
164
  }
@@ -212,6 +227,10 @@ export function renderJsonReport(report) {
212
227
  findingCount: c.findings.length,
213
228
  skippedReason: c.skippedReason ?? null,
214
229
  command: c.command ?? null,
230
+ commandCwd: c.commandCwd ?? null,
231
+ exitCode: c.exitCode ?? null,
232
+ stdoutSummary: c.stdoutSummary ?? null,
233
+ stderrSummary: c.stderrSummary ?? null,
215
234
  }));
216
235
  const sanitizedFindings = allFindings.map((f) => ({
217
236
  id: f.id,
@@ -0,0 +1,124 @@
1
+ import { runSecurityCommand, resolveNpmCommand } from "../commandRunner.js";
2
+ export async function runCliSecuritySuiteCheck(options) {
3
+ const startedAt = new Date().toISOString();
4
+ if (!options.target.isSelf && !options.target.hasPackageJson) {
5
+ const finishedAt = new Date().toISOString();
6
+ return {
7
+ id: "cli-adversarial-suite",
8
+ name: "Target security test suite",
9
+ category: "cli-adversarial",
10
+ status: "failed",
11
+ severity: "major",
12
+ startedAt,
13
+ finishedAt,
14
+ durationMs: 0,
15
+ command: "npm run test:security",
16
+ commandCwd: options.target.targetRoot,
17
+ exitCode: null,
18
+ findings: [
19
+ {
20
+ id: "target-security-suite-missing-package-json",
21
+ title: "Target package.json is missing",
22
+ severity: "major",
23
+ category: "cli-adversarial",
24
+ description: "Target-aware security suite validation requires a package.json at the target root.",
25
+ recommendation: "Add a package.json with a test:security script or validate a package-based target.",
26
+ releaseImpact: "Should fix before release",
27
+ },
28
+ ],
29
+ };
30
+ }
31
+ if (!options.target.isSelf && !options.target.hasSecurityTestScript) {
32
+ const finishedAt = new Date().toISOString();
33
+ return {
34
+ id: "cli-adversarial-suite",
35
+ name: "Target security test suite",
36
+ category: "cli-adversarial",
37
+ status: "failed",
38
+ severity: "major",
39
+ startedAt,
40
+ finishedAt,
41
+ durationMs: 0,
42
+ command: "npm run test:security",
43
+ commandCwd: options.target.targetRoot,
44
+ exitCode: null,
45
+ findings: [
46
+ {
47
+ id: "target-security-suite-missing-script",
48
+ title: "Target test:security script is missing",
49
+ severity: "major",
50
+ category: "cli-adversarial",
51
+ description: "The target package.json does not define scripts.test:security, so the target security suite could not be validated.",
52
+ recommendation: "Add a target test:security script and rerun security:validate.",
53
+ releaseImpact: "Should fix before release",
54
+ },
55
+ ],
56
+ };
57
+ }
58
+ const npm = resolveNpmCommand();
59
+ const isSelf = options.target.isSelf;
60
+ const commandCwd = isSelf ? options.toolRoot : options.target.targetRoot;
61
+ const cmd = await runSecurityCommand({
62
+ command: npm,
63
+ args: ["run", "test:security"],
64
+ cwd: commandCwd,
65
+ timeoutMs: Math.min(options.timeoutMs * 3, 180_000),
66
+ });
67
+ const finishedAt = new Date().toISOString();
68
+ const passed = cmd.exitCode === 0;
69
+ const name = isSelf
70
+ ? "CLI adversarial test suite (tool self-tests)"
71
+ : "Target security test suite";
72
+ return {
73
+ id: "cli-adversarial-suite",
74
+ name,
75
+ category: "cli-adversarial",
76
+ status: passed ? "passed" : "failed",
77
+ severity: "major",
78
+ startedAt,
79
+ finishedAt,
80
+ durationMs: cmd.durationMs,
81
+ findings: passed
82
+ ? []
83
+ : [
84
+ {
85
+ id: "cli-adversarial-suite-failed",
86
+ title: isSelf ? "CLI adversarial test suite had failures" : "Target test:security script failed",
87
+ severity: "major",
88
+ category: "cli-adversarial",
89
+ description: isSelf
90
+ ? `Tool-root test:security exited with code ${String(cmd.exitCode)}.`
91
+ : `Target-root test:security exited with code ${String(cmd.exitCode)}.`,
92
+ evidence: buildEvidence(cmd),
93
+ recommendation: isSelf
94
+ ? "Fix failing tool security tests before release."
95
+ : "Fix the target test:security failure and rerun security:validate.",
96
+ releaseImpact: "Should fix before release",
97
+ },
98
+ ],
99
+ command: `${npm} run test:security`,
100
+ commandCwd,
101
+ exitCode: cmd.exitCode,
102
+ stdoutSummary: summarizeOutput(cmd.stdout),
103
+ stderrSummary: summarizeOutput(cmd.stderr),
104
+ };
105
+ }
106
+ function summarizeOutput(value) {
107
+ const normalized = value.replace(/\r\n/g, "\n").trim();
108
+ if (!normalized) {
109
+ return undefined;
110
+ }
111
+ return normalized.slice(0, 500);
112
+ }
113
+ function buildEvidence(cmd) {
114
+ const parts = [`cwd=${cmd.cwd}`, `exitCode=${String(cmd.exitCode)}`];
115
+ const stdoutSummary = summarizeOutput(cmd.stdout);
116
+ const stderrSummary = summarizeOutput(cmd.stderr);
117
+ if (stdoutSummary) {
118
+ parts.push(`stdout=${stdoutSummary}`);
119
+ }
120
+ if (stderrSummary) {
121
+ parts.push(`stderr=${stderrSummary}`);
122
+ }
123
+ return parts.join(" | ");
124
+ }
@@ -6,10 +6,10 @@ import { runCodeqlCheck } from "../staticScans/codeql.js";
6
6
  import { runSemgrepCheck } from "../staticScans/semgrep.js";
7
7
  import { runAllFuzzTargets } from "../fuzz/fuzzHarness.js";
8
8
  import { ALL_FUZZ_TARGETS } from "../fuzz/fuzzTargets.js";
9
- import { runSecurityCommand, resolveNpmCommand } from "../commandRunner.js";
10
9
  import { calculateVerdict } from "./verdict.js";
11
10
  import { resolveValidationTarget, targetDescription as describeTarget } from "./resolveTarget.js";
12
11
  import { DEFAULT_SECURITY_CONFIG } from "../config.js";
12
+ import { runCliSecuritySuiteCheck } from "./runCliSecuritySuiteCheck.js";
13
13
  export async function runSecurityValidation(options) {
14
14
  const toolRoot = options.cwd;
15
15
  const config = {
@@ -68,45 +68,14 @@ export async function runSecurityValidation(options) {
68
68
  catch (err) {
69
69
  collect(errorCheck("semgrep-scan", "Semgrep static analysis", "static-scan", err));
70
70
  }
71
- // --- CLI adversarial tests (always tool-internal; tests my-dev-kit-lab harness) ---
72
- // These run in the tool root because they use fake-adversarial-cli.js fixtures.
73
- // Labeled clearly so the report shows they are not target-specific checks.
74
- {
75
- const startedAt2 = new Date().toISOString();
76
- const npm = resolveNpmCommand();
77
- const cmd = await runSecurityCommand({
78
- command: npm,
79
- args: ["run", "test:security", "--", "--reporter=json"],
80
- cwd: toolRoot,
81
- timeoutMs: Math.min(config.commandTimeoutMs * 3, 180_000),
82
- });
83
- const finishedAt2 = new Date().toISOString();
84
- const passed = cmd.exitCode === 0;
85
- collect({
86
- id: "cli-adversarial-suite",
87
- name: "CLI adversarial test suite (tool self-tests, not target-specific)",
88
- category: "cli-adversarial",
89
- status: passed ? "passed" : "failed",
90
- severity: "major",
91
- startedAt: startedAt2,
92
- finishedAt: finishedAt2,
93
- durationMs: cmd.durationMs,
94
- findings: passed
95
- ? []
96
- : [
97
- {
98
- id: "cli-adversarial-suite-failed",
99
- title: "CLI adversarial test suite had failures",
100
- severity: "major",
101
- category: "cli-adversarial",
102
- description: `test:security exited with code ${String(cmd.exitCode)}. Run 'npm run test:security' for details.`,
103
- recommendation: "Fix failing adversarial tests before release.",
104
- releaseImpact: "Should fix before release",
105
- },
106
- ],
107
- command: `${npm} run test:security`,
108
- });
109
- }
71
+ // --- Security suite check ---
72
+ // Self-validation runs the lab's own test:security suite.
73
+ // External validation runs the target project's test:security script when present.
74
+ collect(await runCliSecuritySuiteCheck({
75
+ toolRoot,
76
+ target,
77
+ timeoutMs: config.commandTimeoutMs,
78
+ }));
110
79
  // --- Fuzz smoke (always tool-internal; tests library parser/helper code) ---
111
80
  try {
112
81
  collect(await runAllFuzzTargets(ALL_FUZZ_TARGETS, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@dailephd/my-dev-kit-lab",
3
- "version": "0.2.0",
2
+ "name": "@dailephd/my-dev-kit-lab",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Evidence, benchmark, and evaluation companion for my-dev-kit.",
6
6
  "bin": {
@@ -19,13 +19,13 @@
19
19
  "url": "git+https://github.com/dailephd/my-dev-kit-lab.git"
20
20
  },
21
21
  "homepage": "https://github.com/dailephd/my-dev-kit-lab#readme",
22
- "bugs": {
23
- "url": "https://github.com/dailephd/my-dev-kit-lab/issues"
24
- },
25
- "publishConfig": {
26
- "access": "public"
27
- },
28
- "funding": [
22
+ "bugs": {
23
+ "url": "https://github.com/dailephd/my-dev-kit-lab/issues"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "funding": [
29
29
  "https://github.com/sponsors/dailephd",
30
30
  "https://paypal.me/daile88"
31
31
  ],
@@ -41,6 +41,7 @@
41
41
  "license": "UNLICENSED",
42
42
  "scripts": {
43
43
  "build": "node scripts/clean-dist.mjs && tsc -p tsconfig.json",
44
+ "typecheck": "tsc --noEmit",
44
45
  "test": "vitest run",
45
46
  "test:benchmarks": "vitest run tests/benchmarks tests/scripts",
46
47
  "test:report": "vitest run tests/report tests/commands",
@@ -78,14 +79,17 @@
78
79
  "test:fuzz:smoke": "tsx scripts/security/runFuzzSmoke.ts",
79
80
  "security:validate": "tsx scripts/security/validate.ts"
80
81
  },
81
- "devDependencies": {
82
- "@types/node": "^24.0.0",
83
- "playwright": "^1.54.1",
84
- "tsx": "^4.20.3",
85
- "typescript": "^5.8.3",
86
- "vitest": "^3.2.4"
87
- },
88
- "engines": {
89
- "node": ">=20"
90
- }
91
- }
82
+ "devDependencies": {
83
+ "@types/node": "^24.0.0",
84
+ "playwright": "^1.54.1",
85
+ "tsx": "^4.20.3",
86
+ "typescript": "^5.8.3",
87
+ "vitest": "^3.2.4"
88
+ },
89
+ "overrides": {
90
+ "vite": "7.3.6"
91
+ },
92
+ "engines": {
93
+ "node": ">=20"
94
+ }
95
+ }