@dailephd/my-dev-kit-lab 0.9.0 → 0.9.2
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/CHANGELOG.md +21 -0
- package/README.md +10 -3
- package/dist/src/agents/adapters/claudeAdapter.js +6 -3
- package/dist/src/agents/adapters/codexAdapter.js +51 -10
- package/dist/src/agents/index.js +1 -0
- package/dist/src/agents/normalizeAgentTelemetry.js +50 -0
- package/dist/src/agents/parseAgentTokenUsage.js +71 -49
- package/dist/src/agents/providerBuildObservation.js +97 -0
- package/dist/src/audits/core/projectInventory.js +1 -0
- package/dist/src/cli/help.js +8 -2
- package/dist/src/commands/runExperimentDescribeCommand.js +31 -0
- package/dist/src/commands/runExperimentRunCommand.js +193 -5
- package/dist/src/core/runMeasuredCommand.js +4 -0
- package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +3 -1
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpoint.js +216 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +588 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignEvidenceQuality.js +77 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +314 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +111 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignProgress.js +175 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignQueue.js +11 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignRecovery.js +256 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +207 -0
- package/dist/src/experiments/plugins/agentSuccessRate/index.js +6 -0
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +32 -22
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +29 -7
- package/dist/src/experiments/plugins/agentSuccessRate/timeoutPresets.js +9 -0
- package/dist/src/report/experiments/agentSuccessCampaignProgressReportModel.js +1 -0
- package/dist/src/report/experiments/buildAgentSuccessCampaignProgressReport.js +6 -0
- package/dist/src/report/experiments/buildAgentSuccessRateReport.js +11 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +1 -1
- package/dist/src/report/experiments/renderAgentSuccessCampaignProgressHtml.js +6 -0
- package/dist/src/report/experiments/renderAgentSuccessCampaignProgressText.js +25 -0
- package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +3 -1
- package/dist/src/report/experiments/renderAgentSuccessRateText.js +7 -0
- package/dist/src/report/experiments/writeAgentSuccessCampaignProgressReport.js +139 -0
- package/dist/src/report/experiments/writePluginExperimentReports.js +4 -3
- package/dist/src/securityValidation/attackScenarios/payloadCorpus.js +0 -0
- package/dist/src/securityValidation/packageChecks/parseNpmPackDryRun.js +34 -4
- package/dist/src/securityValidation/packageChecks/requiredPackageContents.js +106 -2
- package/dist/src/securityValidation/packageChecks/runPackageChecks.js +31 -16
- package/dist/src/securityValidation/staticScans/semgrep.js +68 -64
- package/dist/src/securityValidation/types.js +0 -4
- package/dist/src/securityValidation/validate/runSecurityValidation.js +1 -1
- package/docs/METRICS.md +9 -0
- package/examples/tutorial-browser/prepare.mjs +13 -13
- package/examples/tutorial-browser/server.mjs +13 -13
- package/package.json +1 -1
|
@@ -3,7 +3,9 @@ import { runSecurityCommand, resolveNpmCommand } from "../commandRunner.js";
|
|
|
3
3
|
import { writeCheckResult } from "../artifacts.js";
|
|
4
4
|
import { parseNpmPackDryRun } from "./parseNpmPackDryRun.js";
|
|
5
5
|
import { detectForbiddenContents } from "./forbiddenPackageContents.js";
|
|
6
|
-
import { detectMissingRequiredContents } from "./requiredPackageContents.js";
|
|
6
|
+
import { detectMissingRequiredContents, requiredContentsForTarget } from "./requiredPackageContents.js";
|
|
7
|
+
import { resolveValidationTarget } from "../validate/resolveTarget.js";
|
|
8
|
+
import { discoverPackageRoot } from "../../runtime/packageRoot.js";
|
|
7
9
|
// Run npm pack --dry-run and inspect the resulting file list for forbidden contents.
|
|
8
10
|
// Does not publish anything.
|
|
9
11
|
export async function runPackageChecks(options) {
|
|
@@ -12,20 +14,23 @@ export async function runPackageChecks(options) {
|
|
|
12
14
|
const allFindings = [];
|
|
13
15
|
const checks = [];
|
|
14
16
|
const npm = resolveNpmCommand();
|
|
17
|
+
const target = options.target ?? resolveValidationTarget(path.resolve(cwd), discoverPackageRoot());
|
|
18
|
+
if (path.resolve(target.targetRoot) !== path.resolve(cwd)) {
|
|
19
|
+
throw new Error("Package check cwd does not match the resolved target root");
|
|
20
|
+
}
|
|
21
|
+
const policy = await requiredContentsForTarget(target);
|
|
15
22
|
// npm pack --dry-run to get the tarball file list
|
|
16
23
|
const startedAt = new Date().toISOString();
|
|
17
24
|
const cmd = await runSecurityCommand({
|
|
18
25
|
command: npm,
|
|
19
|
-
args: ["pack", "--dry-run"],
|
|
26
|
+
args: ["pack", "--dry-run", "--json", "--ignore-scripts", "--cache", path.resolve(rawOutputDir, "npm-cache")],
|
|
20
27
|
cwd,
|
|
21
28
|
timeoutMs: commandTimeoutMs,
|
|
22
29
|
});
|
|
23
30
|
const finishedAt = new Date().toISOString();
|
|
24
|
-
// npm
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
const packOutput = [cmd.stdout, cmd.stderr].find((stream) => /tarball contents/i.test(stream)) ??
|
|
28
|
-
[cmd.stdout, cmd.stderr].filter(Boolean).join("\n");
|
|
31
|
+
// Supported npm versions emit JSON on stdout. Lifecycle scripts are disabled
|
|
32
|
+
// so inspection cannot build, install, or rewrite the target.
|
|
33
|
+
const packOutput = cmd.stdout;
|
|
29
34
|
const parsed = parseNpmPackDryRun(packOutput);
|
|
30
35
|
const { findings: contentFindings } = detectForbiddenContents({
|
|
31
36
|
files: parsed.files,
|
|
@@ -36,20 +41,28 @@ export async function runPackageChecks(options) {
|
|
|
36
41
|
const { findings: missingRequiredFindings } = detectMissingRequiredContents({
|
|
37
42
|
files: parsed.files,
|
|
38
43
|
checkId: "npm-pack",
|
|
44
|
+
requiredFiles: policy.requiredFiles,
|
|
39
45
|
});
|
|
40
|
-
const
|
|
46
|
+
const evidenceFindings = [];
|
|
47
|
+
if (cmd.exitCode !== 0 || cmd.timedOut || cmd.skipped || parsed.parseError || parsed.files.length === 0) {
|
|
48
|
+
evidenceFindings.push({
|
|
49
|
+
id: "npm-pack-evidence-invalid",
|
|
50
|
+
title: "npm package inventory could not be validated",
|
|
51
|
+
severity: "blocker",
|
|
52
|
+
category: "package-content",
|
|
53
|
+
description: "A successful npm pack operation and a nonempty, parseable inventory are required.",
|
|
54
|
+
evidence: `exitCode=${String(cmd.exitCode)}; timedOut=${cmd.timedOut}; skipped=${cmd.skipped}; ${parsed.parseError ?? ""}; ${cmd.stderr.slice(0, 500)}`,
|
|
55
|
+
recommendation: "Resolve the package command or inventory failure and rerun validation.",
|
|
56
|
+
releaseImpact: "Blocker: required package evidence is unavailable",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const packageFindings = [...policy.findings, ...evidenceFindings, ...contentFindings, ...missingRequiredFindings];
|
|
41
60
|
allFindings.push(...packageFindings);
|
|
42
61
|
const packCheck = {
|
|
43
62
|
id: "npm-pack-dry-run",
|
|
44
63
|
name: "npm pack --dry-run (tarball file list)",
|
|
45
64
|
category: "package-content",
|
|
46
|
-
status:
|
|
47
|
-
? "failed"
|
|
48
|
-
: parsed.parseError && parsed.files.length === 0
|
|
49
|
-
? "warning"
|
|
50
|
-
: packageFindings.length > 0
|
|
51
|
-
? "failed"
|
|
52
|
-
: "passed",
|
|
65
|
+
status: packageFindings.length > 0 ? "failed" : "passed",
|
|
53
66
|
severity: packageFindings.length > 0
|
|
54
67
|
? packageFindings.some((f) => f.severity === "blocker")
|
|
55
68
|
? "blocker"
|
|
@@ -60,7 +73,9 @@ export async function runPackageChecks(options) {
|
|
|
60
73
|
durationMs: cmd.durationMs,
|
|
61
74
|
findings: packageFindings,
|
|
62
75
|
skippedReason: undefined,
|
|
63
|
-
command: "npm pack --dry-run",
|
|
76
|
+
command: "npm pack --dry-run --json --ignore-scripts",
|
|
77
|
+
commandCwd: cwd,
|
|
78
|
+
exitCode: cmd.exitCode,
|
|
64
79
|
};
|
|
65
80
|
await writeCheckResult({
|
|
66
81
|
result: packCheck,
|
|
@@ -5,16 +5,35 @@ import { resolveCommand } from "../../core/resolveCommand.js";
|
|
|
5
5
|
import { skippedCheck } from "../cliAdversarial/runAdversarialCheck.js";
|
|
6
6
|
export function parseSemgrepJson(raw) {
|
|
7
7
|
if (!raw.trim()) {
|
|
8
|
-
return { findings: [], rawOutput: raw };
|
|
8
|
+
return { findings: [], parseError: "Semgrep produced no JSON scan evidence", rawOutput: raw };
|
|
9
9
|
}
|
|
10
10
|
try {
|
|
11
11
|
const parsed = JSON.parse(raw);
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.results)) {
|
|
13
|
+
return { findings: [], parseError: "Semgrep JSON requires a results array", rawOutput: raw };
|
|
14
|
+
}
|
|
15
|
+
const findings = [];
|
|
16
|
+
let parseError = !Array.isArray(parsed.errors) || !isRecord(parsed.paths) ||
|
|
17
|
+
!Array.isArray(parsed.paths.scanned) || parsed.paths.scanned.some((file) => typeof file !== "string")
|
|
18
|
+
? "Semgrep JSON requires errors and paths.scanned arrays" : undefined;
|
|
19
|
+
for (const [index, r] of parsed.results.entries()) {
|
|
20
|
+
if (!isRecord(r) || typeof r.check_id !== "string" || !r.check_id ||
|
|
21
|
+
typeof r.path !== "string" || !r.path || !isRecord(r.start) ||
|
|
22
|
+
!Number.isInteger(r.start.line) || Number(r.start.line) < 1 || !isRecord(r.extra) ||
|
|
23
|
+
(r.extra.message !== undefined && typeof r.extra.message !== "string") ||
|
|
24
|
+
(r.extra.metadata !== undefined && !isRecord(r.extra.metadata))) {
|
|
25
|
+
parseError ??= `Malformed Semgrep result at index ${index}`;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
14
28
|
const item = r;
|
|
15
29
|
const checkId = typeof item["check_id"] === "string" ? item["check_id"] : "unknown";
|
|
16
30
|
const extra = item["extra"] ?? {};
|
|
17
31
|
const metadata = extra["metadata"] ?? {};
|
|
32
|
+
if ((extra.severity !== undefined && (typeof extra.severity !== "string" || !extra.severity.trim())) ||
|
|
33
|
+
(extra.severity === undefined && (typeof metadata.severity !== "string" || !metadata.severity.trim()))) {
|
|
34
|
+
parseError ??= `Missing or malformed Semgrep severity at index ${index}`;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
18
37
|
const severity = typeof extra["severity"] === "string"
|
|
19
38
|
? extra["severity"]
|
|
20
39
|
: typeof metadata["severity"] === "string"
|
|
@@ -24,9 +43,14 @@ export function parseSemgrepJson(raw) {
|
|
|
24
43
|
const filePath = typeof item["path"] === "string" ? item["path"] : "";
|
|
25
44
|
const startObj = item["start"] ?? {};
|
|
26
45
|
const line = typeof startObj["line"] === "number" ? startObj["line"] : 0;
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
46
|
+
findings.push({ ruleId: checkId, severity, message, path: filePath, line });
|
|
47
|
+
}
|
|
48
|
+
// Semgrep's CLI contract reports partial/fatal analysis in errors even
|
|
49
|
+
// when the process exits zero. Retain matches, but never call this clean.
|
|
50
|
+
const analysisError = Array.isArray(parsed.errors) && parsed.errors.length > 0
|
|
51
|
+
? `Semgrep reported ${parsed.errors.length} analysis error(s): ${JSON.stringify(parsed.errors).slice(0, 500)}`
|
|
52
|
+
: undefined;
|
|
53
|
+
return { findings, parseError, analysisError, rawOutput: raw };
|
|
30
54
|
}
|
|
31
55
|
catch (err) {
|
|
32
56
|
return {
|
|
@@ -36,6 +60,9 @@ export function parseSemgrepJson(raw) {
|
|
|
36
60
|
};
|
|
37
61
|
}
|
|
38
62
|
}
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
65
|
+
}
|
|
39
66
|
function semgrepSeverityToSecurity(semgrepSeverity) {
|
|
40
67
|
switch (semgrepSeverity.toUpperCase()) {
|
|
41
68
|
case "ERROR":
|
|
@@ -88,67 +115,25 @@ export async function runSemgrepCheck(options) {
|
|
|
88
115
|
"--quiet",
|
|
89
116
|
scanTarget,
|
|
90
117
|
];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
args: scanArgs,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
let cmd;
|
|
119
|
+
try {
|
|
120
|
+
cmd = await runSecurityCommand({ command, args: scanArgs, cwd: targetRoot, timeoutMs });
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
cmd = { command, args: scanArgs, cwd: targetRoot, exitCode: null, durationMs: 0,
|
|
124
|
+
stdout: "", stderr: error instanceof Error ? error.message : String(error), timedOut: false, skipped: false };
|
|
125
|
+
}
|
|
97
126
|
const finishedAt = new Date().toISOString();
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
status: "failed",
|
|
107
|
-
severity: "major",
|
|
108
|
-
startedAt,
|
|
109
|
-
finishedAt,
|
|
110
|
-
durationMs: cmd.durationMs,
|
|
111
|
-
findings: [
|
|
112
|
-
{
|
|
113
|
-
id: "semgrep-execution-error",
|
|
114
|
-
title: "Semgrep execution failed",
|
|
115
|
-
severity: "major",
|
|
116
|
-
category: "static-scan",
|
|
117
|
-
description: isTimedOut
|
|
118
|
-
? "Semgrep timed out."
|
|
119
|
-
: `Semgrep exited with code ${String(cmd.exitCode)}. stderr: ${cmd.stderr.slice(0, 500)}`,
|
|
120
|
-
recommendation: "Check semgrep installation and .semgrep.yml config.",
|
|
121
|
-
releaseImpact: "Review before release",
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
command: [command, ...scanArgs].join(" "),
|
|
125
|
-
};
|
|
127
|
+
// Distinguish positively established absence from a failed attempted scan.
|
|
128
|
+
// Network, permission, generic npm, or native spawn errors are not absence.
|
|
129
|
+
const unavailable = !cmd.timedOut && cmd.exitCode !== 0 && !cmd.stdout.trim() && ((cmd.skipped && cmd.exitCode === null && Boolean(cmd.skippedReason?.match(/\b(semgrep|scanner|cli)\b.*(unavailable|not installed|not found|not on PATH)/i))) ||
|
|
130
|
+
(useNpx && !cmd.skipped && cmd.exitCode === 1 && /could not determine executable to run/i.test(cmd.stderr)));
|
|
131
|
+
if (unavailable) {
|
|
132
|
+
return { ...skippedCheck({ id: "semgrep-scan", name: "Semgrep static analysis", category: "static-scan",
|
|
133
|
+
reason: cmd.skippedReason ?? "Semgrep CLI is unavailable; the npx fallback package provides no executable." }),
|
|
134
|
+
command: [command, ...scanArgs].join(" "), commandCwd: targetRoot, exitCode: cmd.exitCode };
|
|
126
135
|
}
|
|
127
136
|
const parsed = parseSemgrepJson(cmd.stdout);
|
|
128
|
-
if (parsed.parseError) {
|
|
129
|
-
return {
|
|
130
|
-
id: "semgrep-scan",
|
|
131
|
-
name: "Semgrep static analysis",
|
|
132
|
-
category: "static-scan",
|
|
133
|
-
status: "warning",
|
|
134
|
-
severity: "minor",
|
|
135
|
-
startedAt,
|
|
136
|
-
finishedAt,
|
|
137
|
-
durationMs: cmd.durationMs,
|
|
138
|
-
findings: [
|
|
139
|
-
{
|
|
140
|
-
id: "semgrep-parse-error",
|
|
141
|
-
title: "Could not parse Semgrep output",
|
|
142
|
-
severity: "minor",
|
|
143
|
-
category: "static-scan",
|
|
144
|
-
description: `JSON parse error: ${parsed.parseError}`,
|
|
145
|
-
recommendation: "Verify semgrep version produces valid JSON output.",
|
|
146
|
-
releaseImpact: "Review before release",
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
command: [command, ...scanArgs].join(" "),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
137
|
const securityFindings = parsed.findings.map((f) => ({
|
|
153
138
|
id: `semgrep-${f.ruleId.replace(/[^a-z0-9-]/gi, "-")}`,
|
|
154
139
|
title: f.ruleId,
|
|
@@ -160,6 +145,23 @@ export async function runSemgrepCheck(options) {
|
|
|
160
145
|
? "Should fix before release"
|
|
161
146
|
: "Review before release",
|
|
162
147
|
}));
|
|
148
|
+
const executionFailed = cmd.timedOut || cmd.skipped || (cmd.exitCode !== 0 && cmd.exitCode !== 1) ||
|
|
149
|
+
(cmd.exitCode === 1 && parsed.findings.length === 0);
|
|
150
|
+
const evidenceError = executionFailed
|
|
151
|
+
? `Semgrep execution failed: exitCode=${String(cmd.exitCode)}, timedOut=${cmd.timedOut}, skipped=${cmd.skipped}. ${cmd.stderr.slice(0, 500)}`
|
|
152
|
+
: parsed.parseError ?? parsed.analysisError;
|
|
153
|
+
if (evidenceError) {
|
|
154
|
+
securityFindings.push({
|
|
155
|
+
id: executionFailed ? "semgrep-execution-error" : parsed.parseError ? "semgrep-parse-error" : "semgrep-analysis-error",
|
|
156
|
+
title: "Semgrep scan evidence is incomplete or unsuccessful",
|
|
157
|
+
severity: "major",
|
|
158
|
+
category: "static-scan",
|
|
159
|
+
description: evidenceError,
|
|
160
|
+
evidence: `exitCode=${String(cmd.exitCode)}; ${parsed.parseError ?? parsed.analysisError ?? ""}`,
|
|
161
|
+
recommendation: "Resolve the scanner execution or analysis error and rerun with valid JSON scan evidence.",
|
|
162
|
+
releaseImpact: "Review before release",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
163
165
|
const hasBlockerOrMajor = securityFindings.some((f) => f.severity === "blocker" || f.severity === "major");
|
|
164
166
|
return {
|
|
165
167
|
id: "semgrep-scan",
|
|
@@ -176,5 +178,7 @@ export async function runSemgrepCheck(options) {
|
|
|
176
178
|
durationMs: cmd.durationMs,
|
|
177
179
|
findings: securityFindings,
|
|
178
180
|
command: [command, ...scanArgs].join(" "),
|
|
181
|
+
commandCwd: targetRoot,
|
|
182
|
+
exitCode: cmd.exitCode,
|
|
179
183
|
};
|
|
180
184
|
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// Core types for the my-dev-kit-lab security-validation framework.
|
|
2
|
-
// These types describe the vocabulary for checks, findings, results, and verdicts
|
|
3
|
-
// used by dependency checks (Prompt 3), CLI adversarial tests (Prompts 4–5),
|
|
4
|
-
// static scans (Prompt 6), fuzz smoke tests (Prompt 7), and the release report (Prompt 8).
|
|
5
1
|
export const SECURITY_SEVERITIES = [
|
|
6
2
|
"blocker",
|
|
7
3
|
"major",
|
|
@@ -80,7 +80,7 @@ export async function runSecurityValidation(options) {
|
|
|
80
80
|
// missing package.json gracefully.
|
|
81
81
|
if (selectedChecks.has("package")) {
|
|
82
82
|
try {
|
|
83
|
-
const pkgOutput = await runPackageChecks({ cwd: target.targetRoot, config });
|
|
83
|
+
const pkgOutput = await runPackageChecks({ cwd: target.targetRoot, config, target });
|
|
84
84
|
collectMany(pkgOutput.checks);
|
|
85
85
|
}
|
|
86
86
|
catch (err) {
|
package/docs/METRICS.md
CHANGED
|
@@ -938,3 +938,12 @@ Cumulative versus final: durations and tokens accumulate over all provider attem
|
|
|
938
938
|
- Paired differences report the mean of the `raw-full-file` values, the mean of the `context-pack` values, and the mean of `context-pack` minus `raw-full-file` over matched cases.
|
|
939
939
|
- These are **descriptive**. No weighted composite score, ranking, automatic winner, causal claim, or statistical-significance test is produced (`statisticalEffect` is `not-assessed`). The corpus is finite and controlled, so results do not generalize beyond it, and a single run carries no variance estimate.
|
|
940
940
|
- Provider telemetry can be unavailable, and in deterministic-fixture mode both treatments receive the same reference patch, so those results measure the pipeline only.
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
## Campaign progress evidence (v0.9.1 released)
|
|
944
|
+
|
|
945
|
+
Token evidence levels are `provider-reported`, `cli-reported`, `parsed-from-output`, `unavailable`, and `estimated`. The original telemetry source is retained. Numeric availability is recorded per token field, so partial measurements remain partial and normalization never fills missing fields or invents totals; unattributed OpenTelemetry evidence is unavailable. Duration provenance is `lab-wall-clock` for valid observed elapsed time and `unavailable` for missing or invalid values; campaign comparison time is Lab-measured wall-clock duration, not provider-native time.
|
|
946
|
+
|
|
947
|
+
The plugin-specific progress projection is `my-dev-kit-lab-agent-success-rate-campaign-progress-v1`; the writer publishes immutable revision-specific JSON, HTML, and text snapshots. It accounts for selected cases and treatment units, while execution epochs remain recovery history and are excluded from selected-unit counts. Paused provider-limit and uncertain states remain distinct. Partial progress is not a complete scientific analysis artifact.
|
|
948
|
+
|
|
949
|
+
Evidence quality is metric-specific: task outcomes, token measurements, and duration can have different strong, partial, or unavailable classifications. Strong describes the completeness and provenance of the available evidence; it is not statistical significance. Partial evidence does not mean task failure. Missing token evidence is unavailable, never zero; estimated tokens are not provider usage. Duration is Lab-measured wall-clock time, not provider-native time. Comparisons use matched completed cases and state their denominator and exclusions; the existing paired direction is preserved. These descriptive comparisons establish no causal effect, winner, p-value, or general provider performance.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const sourceRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const targetRoot = process.argv[2];
|
|
7
|
-
if (!targetRoot) {
|
|
8
|
-
console.error("prepare requires a target root argument");
|
|
9
|
-
process.exit(2);
|
|
10
|
-
}
|
|
11
|
-
fs.mkdirSync(path.join(targetRoot, "app"), { recursive: true });
|
|
12
|
-
fs.mkdirSync(path.join(targetRoot, "out"), { recursive: true });
|
|
13
|
-
for (const file of ["index.html", "server.mjs"]) fs.copyFileSync(path.join(sourceRoot, file), path.join(targetRoot, "app", file));
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const sourceRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const targetRoot = process.argv[2];
|
|
7
|
+
if (!targetRoot) {
|
|
8
|
+
console.error("prepare requires a target root argument");
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
fs.mkdirSync(path.join(targetRoot, "app"), { recursive: true });
|
|
12
|
+
fs.mkdirSync(path.join(targetRoot, "out"), { recursive: true });
|
|
13
|
+
for (const file of ["index.html", "server.mjs"]) fs.copyFileSync(path.join(sourceRoot, file), path.join(targetRoot, "app", file));
|
|
14
14
|
fs.writeFileSync(path.join(targetRoot, "out", "report.json"), JSON.stringify({ prepared: true, counts: { widgets: 3 } }, null, 2), "utf8");
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const port = Number(process.env.TUTORIAL_FIXTURE_PORT || 0);
|
|
8
|
-
const server = http.createServer((req, res) => {
|
|
9
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10
|
-
if (url.pathname === "/api/status") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, widgets: 3 })); return; }
|
|
11
|
-
const filePath = url.pathname === "/" ? path.join(root, "index.html") : path.join(root, url.pathname);
|
|
12
|
-
fs.readFile(filePath, (error, contents) => { if (error) { res.writeHead(404); res.end("not found"); return; } res.writeHead(200, { "content-type": filePath.endsWith(".html") ? "text/html" : "application/octet-stream" }); res.end(contents); });
|
|
13
|
-
});
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const port = Number(process.env.TUTORIAL_FIXTURE_PORT || 0);
|
|
8
|
+
const server = http.createServer((req, res) => {
|
|
9
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10
|
+
if (url.pathname === "/api/status") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, widgets: 3 })); return; }
|
|
11
|
+
const filePath = url.pathname === "/" ? path.join(root, "index.html") : path.join(root, url.pathname);
|
|
12
|
+
fs.readFile(filePath, (error, contents) => { if (error) { res.writeHead(404); res.end("not found"); return; } res.writeHead(200, { "content-type": filePath.endsWith(".html") ? "text/html" : "application/octet-stream" }); res.end(contents); });
|
|
13
|
+
});
|
|
14
14
|
server.listen(port, "127.0.0.1", () => console.log("tutorial fixture listening on " + server.address().port));
|