@allurereport/plugin-agent 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/harness.js CHANGED
@@ -10,20 +10,27 @@ const FALLBACK_ACTION = {
10
10
  };
11
11
  export const AGENT_ENRICHMENT_ACTIONS = Object.fromEntries(Object.entries(ENRICHMENT_ACTIONS_BY_CHECK_NAME).map(([checkName, action]) => [checkName, { checkName, ...action }]));
12
12
  export const SCOPE_REJECTING_CHECKS = [
13
- "missing-expected-test",
14
- "missing-expected-prefix",
15
- "missing-expected-environment",
13
+ "expected-test-missing",
14
+ "expected-count-mismatch",
15
+ "expected-prefix-missing",
16
+ "expected-label-missing",
17
+ "expected-environment-missing",
18
+ "no-tests-observed",
16
19
  "unexpected-environment",
17
- "forbidden-selector-match",
20
+ "forbidden-label-observed",
18
21
  "unexpected-test",
19
22
  ];
20
23
  export const ITERATION_REQUIRED_CHECKS = [
21
- "invalid-expectations-file",
22
- "no-visible-tests",
24
+ "expectations-invalid",
25
+ "expectations-empty",
26
+ "expectations-unsupported-control",
23
27
  "runner-failures-outside-logical-results",
24
- "missing-expected-label-selector",
25
28
  "metadata-mismatch",
26
29
  "history-id-collision",
30
+ "expected-step-containing-missing",
31
+ "insufficient-expected-steps",
32
+ "insufficient-expected-attachments",
33
+ "missing-expected-attachment",
27
34
  "failed-without-useful-steps",
28
35
  "failed-without-attachments",
29
36
  "nontrivial-run-with-empty-trace",
@@ -42,6 +49,22 @@ const IMPACT_ORDER = {
42
49
  advisory: 2,
43
50
  };
44
51
  const uniqueValues = (values) => Array.from(new Set(values));
52
+ const checkNameForFinding = (finding) => finding.check_id ?? finding.check_name;
53
+ const subjectRefForFinding = (finding) => {
54
+ if (finding.subject_ref) {
55
+ return finding.subject_ref;
56
+ }
57
+ if (typeof finding.subject === "string") {
58
+ return finding.subject;
59
+ }
60
+ return finding.subject.path ?? finding.subject.id ?? finding.subject.type;
61
+ };
62
+ const subjectTypeForFinding = (finding) => finding.subject_type ??
63
+ (typeof finding.subject === "object" && finding.subject.type === "test"
64
+ ? "test"
65
+ : subjectRefForFinding(finding) === "run"
66
+ ? "run"
67
+ : "test");
45
68
  const normalizeStringArray = (value) => {
46
69
  if (typeof value === "string") {
47
70
  return value.length ? [value] : [];
@@ -112,14 +135,18 @@ const sortPlan = (items) => [...items].sort((left, right) => {
112
135
  return left.checkName.localeCompare(right.checkName);
113
136
  });
114
137
  const impactForFinding = (finding, antiDummyConfidenceThreshold) => {
115
- if (SCOPE_REJECTING_CHECKS.includes(finding.check_name)) {
138
+ if (finding.impact === "reject" || finding.impact === "iterate" || finding.impact === "advisory") {
139
+ return finding.impact;
140
+ }
141
+ const checkName = checkNameForFinding(finding);
142
+ if (SCOPE_REJECTING_CHECKS.includes(checkName)) {
116
143
  return "reject";
117
144
  }
118
- if (ANTI_DUMMY_CHECKS.includes(finding.check_name) &&
145
+ if (ANTI_DUMMY_CHECKS.includes(checkName) &&
119
146
  (finding.confidence ?? 0) >= antiDummyConfidenceThreshold) {
120
147
  return "reject";
121
148
  }
122
- if (ITERATION_REQUIRED_CHECKS.includes(finding.check_name)) {
149
+ if (ITERATION_REQUIRED_CHECKS.includes(checkName)) {
123
150
  return "iterate";
124
151
  }
125
152
  if (finding.severity === "high") {
@@ -140,7 +167,7 @@ export const buildAgentExpectations = (input) => {
140
167
  };
141
168
  };
142
169
  export const mapFindingToEnrichmentAction = (finding) => {
143
- const checkName = typeof finding === "string" ? finding : finding.check_name;
170
+ const checkName = typeof finding === "string" ? finding : checkNameForFinding(finding);
144
171
  const mapped = AGENT_ENRICHMENT_ACTIONS[checkName];
145
172
  return mapped ?? { ...FALLBACK_ACTION, checkName };
146
173
  };
@@ -166,16 +193,17 @@ export const planAgentEnrichmentReview = (output, options = {}) => {
166
193
  const testsByPath = new Map(output.tests.map((test) => [test.markdown_path, test]));
167
194
  const plan = sortPlan(output.findings.map((finding) => {
168
195
  const action = mapFindingToEnrichmentAction(finding);
169
- const matchedTest = testsByPath.get(finding.subject);
196
+ const subject = subjectRefForFinding(finding);
197
+ const matchedTest = testsByPath.get(subject);
170
198
  return {
171
199
  ...action,
172
- subject: finding.subject,
173
- subjectType: finding.subject === "run" ? "run" : "test",
200
+ subject,
201
+ subjectType: subjectTypeForFinding(finding),
174
202
  severity: finding.severity,
175
203
  message: finding.message,
176
204
  explanation: finding.explanation,
177
- remediationHint: finding.remediation_hint,
178
- evidencePaths: finding.evidence_paths,
205
+ remediationHint: finding.action ?? finding.remediation_hint,
206
+ evidencePaths: finding.evidence?.paths ?? finding.evidence_paths,
179
207
  expectedReference: finding.expected_reference,
180
208
  confidence: finding.confidence,
181
209
  acceptanceImpact: impactForFinding(finding, antiDummyConfidenceThreshold),
@@ -189,7 +217,7 @@ export const planAgentEnrichmentReview = (output, options = {}) => {
189
217
  const advisory = plan.filter((item) => item.acceptanceImpact === "advisory");
190
218
  const notes = [];
191
219
  if (!output.run.expectations_present) {
192
- notes.push("Generate ALLURE_AGENT_EXPECTATIONS before the next enrichment iteration so scope checks are comparable.");
220
+ notes.push("Declare inline expectations or provide an expectations file before the next enrichment iteration so scope checks are comparable.");
193
221
  }
194
222
  if (rejecting.some((item) => item.checkName === "noop-dominated-steps")) {
195
223
  notes.push("Reject noop-dominated enrichment: keep only steps tied to real actions or checks, and use real runtime attachments instead of placeholders.");
package/dist/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
- export { type AgentPluginOptions } from "./model.js";
1
+ export { type AgentAttachmentExpectationInput, type AgentEvidenceExpectationInput, type AgentExpectationSelectorInput, type AgentExpectationsInput, type AgentPluginOptions, parseAgentExpectations, } from "./model.js";
2
+ export * from "./capabilities.js";
3
+ export * from "./errors.js";
2
4
  export * from "./harness.js";
5
+ export * from "./inline-expectations.js";
6
+ export * from "./invalid-output.js";
7
+ export * from "./paths.js";
8
+ export * from "./query.js";
9
+ export * from "./selection.js";
10
+ export * from "./state.js";
3
11
  export { AgentPlugin as default } from "./plugin.js";
package/dist/index.js CHANGED
@@ -1,2 +1,11 @@
1
+ export { parseAgentExpectations, } from "./model.js";
2
+ export * from "./capabilities.js";
3
+ export * from "./errors.js";
1
4
  export * from "./harness.js";
5
+ export * from "./inline-expectations.js";
6
+ export * from "./invalid-output.js";
7
+ export * from "./paths.js";
8
+ export * from "./query.js";
9
+ export * from "./selection.js";
10
+ export * from "./state.js";
2
11
  export { AgentPlugin as default } from "./plugin.js";
@@ -0,0 +1,23 @@
1
+ import type { AgentExpectationsInput } from "./model.js";
2
+ type SingleStringOptionValue = string | string[] | undefined;
3
+ export type AgentInlineExpectationOptions = {
4
+ goal?: SingleStringOptionValue;
5
+ taskId?: SingleStringOptionValue;
6
+ expectTests?: SingleStringOptionValue;
7
+ expectLabels?: string[];
8
+ expectEnvironments?: string[];
9
+ expectFullNames?: string[];
10
+ expectPrefixes?: string[];
11
+ forbidLabels?: string[];
12
+ expectStepContains?: string[];
13
+ expectSteps?: SingleStringOptionValue;
14
+ expectAttachments?: SingleStringOptionValue;
15
+ expectAttachmentFilters?: string[];
16
+ };
17
+ export declare const buildAgentInlineExpectations: (options: AgentInlineExpectationOptions) => AgentExpectationsInput | undefined;
18
+ export declare const validateAgentExpectationsFile: (params: {
19
+ cwd: string;
20
+ output?: string;
21
+ expectations?: string;
22
+ }) => Promise<void>;
23
+ export {};
@@ -0,0 +1,186 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { AgentExpectationUsageError, AgentUsageError } from "./errors.js";
4
+ import { parseAgentExpectations } from "./model.js";
5
+ import { isPathInside } from "./paths.js";
6
+ const readNonNegativeInteger = (value, optionName) => {
7
+ if (value === undefined) {
8
+ return undefined;
9
+ }
10
+ if (!/^\d+$/.test(value)) {
11
+ throw new AgentExpectationUsageError(`${optionName} must be a non-negative integer`, optionName);
12
+ }
13
+ const parsed = Number(value);
14
+ if (!Number.isSafeInteger(parsed)) {
15
+ throw new AgentExpectationUsageError(`${optionName} must be a non-negative integer`, optionName);
16
+ }
17
+ return parsed;
18
+ };
19
+ const readPositiveInteger = (value, optionName) => {
20
+ if (value === undefined) {
21
+ return undefined;
22
+ }
23
+ if (!/^[1-9]\d*$/.test(value)) {
24
+ throw new AgentExpectationUsageError(`${optionName} must be a positive integer`, optionName);
25
+ }
26
+ const parsed = Number(value);
27
+ if (!Number.isSafeInteger(parsed)) {
28
+ throw new AgentExpectationUsageError(`${optionName} must be a positive integer`, optionName);
29
+ }
30
+ return parsed;
31
+ };
32
+ const readSingleStringOption = (value, optionName) => {
33
+ const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
34
+ if (values.length > 1) {
35
+ throw new AgentExpectationUsageError(`Duplicate ${optionName} is not allowed`, optionName);
36
+ }
37
+ if (!values.length) {
38
+ return undefined;
39
+ }
40
+ const normalized = values[0].trim();
41
+ if (!normalized) {
42
+ throw new AgentExpectationUsageError(`${optionName} value must be non-empty`, optionName);
43
+ }
44
+ return normalized;
45
+ };
46
+ const parseNameValue = (value, optionName, example) => {
47
+ const parts = value.split("=");
48
+ if (parts.length !== 2) {
49
+ throw new AgentExpectationUsageError(`Invalid ${optionName} ${JSON.stringify(value)}. Expected ${example}`, optionName);
50
+ }
51
+ const name = parts[0].trim();
52
+ const filterValue = parts[1].trim();
53
+ if (!name || !filterValue) {
54
+ throw new AgentExpectationUsageError(`Invalid ${optionName} ${JSON.stringify(value)}. Expected ${example}`, optionName);
55
+ }
56
+ return {
57
+ name,
58
+ value: filterValue,
59
+ };
60
+ };
61
+ const addLabelValues = (target, values, optionName) => {
62
+ for (const rawValue of values ?? []) {
63
+ const { name, value } = parseNameValue(rawValue, optionName, "the form name=value, for example module=cli");
64
+ const current = target[name] ?? [];
65
+ if (!current.includes(value)) {
66
+ current.push(value);
67
+ }
68
+ target[name] = current;
69
+ }
70
+ };
71
+ const normalizeStringValues = (values, optionName) => {
72
+ const normalized = [];
73
+ for (const value of values ?? []) {
74
+ const trimmed = value.trim();
75
+ if (!trimmed) {
76
+ throw new AgentExpectationUsageError(`${optionName} value must be non-empty`, optionName);
77
+ }
78
+ normalized.push(trimmed);
79
+ }
80
+ return normalized;
81
+ };
82
+ export const buildAgentInlineExpectations = (options) => {
83
+ const expectedLabels = {};
84
+ const forbiddenLabels = {};
85
+ const expected = {};
86
+ const forbidden = {};
87
+ const evidence = {};
88
+ const attachmentFilters = [];
89
+ addLabelValues(expectedLabels, options.expectLabels, "--expect-label");
90
+ addLabelValues(forbiddenLabels, options.forbidLabels, "--forbid-label");
91
+ const expectTests = readNonNegativeInteger(readSingleStringOption(options.expectTests, "--expect-tests"), "--expect-tests");
92
+ const expectSteps = readPositiveInteger(readSingleStringOption(options.expectSteps, "--expect-steps"), "--expect-steps");
93
+ const expectAttachments = readPositiveInteger(readSingleStringOption(options.expectAttachments, "--expect-attachments"), "--expect-attachments");
94
+ const expectedEnvironments = normalizeStringValues(options.expectEnvironments, "--expect-env");
95
+ const expectedFullNames = normalizeStringValues(options.expectFullNames, "--expect-test");
96
+ const expectedPrefixes = normalizeStringValues(options.expectPrefixes, "--expect-prefix");
97
+ const expectedStepContains = normalizeStringValues(options.expectStepContains, "--expect-step-containing");
98
+ for (const rawValue of options.expectAttachmentFilters ?? []) {
99
+ const parsed = rawValue.includes("=")
100
+ ? parseNameValue(rawValue, "--expect-attachment", "a file name or a filter such as name=trace.zip or content-type=application/json")
101
+ : { name: "name", value: rawValue.trim() };
102
+ const normalizedName = parsed.name.toLowerCase().replace(/_/g, "-");
103
+ if (!parsed.value) {
104
+ throw new AgentExpectationUsageError("Invalid --expect-attachment value. Expected a non-empty file name or filter such as name=trace.zip", "--expect-attachment");
105
+ }
106
+ if (normalizedName === "name") {
107
+ attachmentFilters.push({ name: parsed.value });
108
+ continue;
109
+ }
110
+ if (normalizedName === "content-type" || normalizedName === "type") {
111
+ attachmentFilters.push({ content_type: parsed.value });
112
+ continue;
113
+ }
114
+ throw new AgentExpectationUsageError(`Invalid --expect-attachment key ${JSON.stringify(parsed.name)}. Expected name or content-type`, "--expect-attachment");
115
+ }
116
+ if (expectTests !== undefined) {
117
+ expected.test_count = expectTests;
118
+ }
119
+ if (expectedEnvironments.length) {
120
+ expected.environments = expectedEnvironments;
121
+ }
122
+ if (expectedFullNames.length) {
123
+ expected.full_names = expectedFullNames;
124
+ }
125
+ if (expectedPrefixes.length) {
126
+ expected.full_name_prefixes = expectedPrefixes;
127
+ }
128
+ if (Object.keys(expectedLabels).length) {
129
+ expected.label_values = expectedLabels;
130
+ }
131
+ if (Object.keys(forbiddenLabels).length) {
132
+ forbidden.label_values = forbiddenLabels;
133
+ }
134
+ if (expectSteps !== undefined) {
135
+ evidence.min_steps = expectSteps;
136
+ }
137
+ if (expectAttachments !== undefined) {
138
+ evidence.min_attachments = expectAttachments;
139
+ }
140
+ if (expectedStepContains.length) {
141
+ evidence.step_name_contains = expectedStepContains;
142
+ }
143
+ if (attachmentFilters.length) {
144
+ evidence.attachments = attachmentFilters;
145
+ }
146
+ if (expected.test_count === 0 &&
147
+ (expected.environments?.length ||
148
+ expected.full_names?.length ||
149
+ expected.full_name_prefixes?.length ||
150
+ Object.keys(expected.label_values ?? {}).length ||
151
+ evidence.step_name_contains?.length ||
152
+ evidence.min_steps !== undefined ||
153
+ evidence.min_attachments !== undefined ||
154
+ evidence.attachments?.length)) {
155
+ throw new AgentExpectationUsageError("--expect-tests 0 cannot be combined with positive scope or evidence expectations", "--expect-tests");
156
+ }
157
+ const inlineExpectations = {
158
+ ...(readSingleStringOption(options.goal, "--goal") ? { goal: readSingleStringOption(options.goal, "--goal") } : {}),
159
+ ...(readSingleStringOption(options.taskId, "--task-id")
160
+ ? { task_id: readSingleStringOption(options.taskId, "--task-id") }
161
+ : {}),
162
+ ...(Object.keys(expected).length ? { expected } : {}),
163
+ ...(Object.keys(forbidden).length ? { forbidden } : {}),
164
+ ...(Object.keys(evidence).length ? { evidence } : {}),
165
+ };
166
+ return Object.keys(inlineExpectations).length ? inlineExpectations : undefined;
167
+ };
168
+ export const validateAgentExpectationsFile = async (params) => {
169
+ const { cwd, output, expectations } = params;
170
+ if (!expectations) {
171
+ return;
172
+ }
173
+ const expectationsPath = resolve(cwd, expectations);
174
+ if (output) {
175
+ const outputDir = resolve(cwd, output);
176
+ if (isPathInside(outputDir, expectationsPath)) {
177
+ throw new AgentUsageError(`--expectations path ${JSON.stringify(expectationsPath)} must not be inside the agent output directory ${JSON.stringify(outputDir)}`);
178
+ }
179
+ }
180
+ try {
181
+ parseAgentExpectations(await readFile(expectationsPath, "utf-8"));
182
+ }
183
+ catch (error) {
184
+ throw new AgentExpectationUsageError(`Could not load expectations from ${expectationsPath}: ${error.message}`, "--expectations");
185
+ }
186
+ };
@@ -0,0 +1,58 @@
1
+ import { AgentExpectationUsageError } from "./errors.js";
2
+ export declare const createInvalidExpectationFinding: (params: {
3
+ message: string;
4
+ sourceOption?: string;
5
+ }) => {
6
+ schema_version: string;
7
+ check_id: string;
8
+ instance_id: string;
9
+ severity: string;
10
+ impact: string;
11
+ confidence: number;
12
+ category: string;
13
+ title: string;
14
+ message: string;
15
+ subject: {
16
+ type: string;
17
+ };
18
+ expected: {
19
+ option: string;
20
+ expectations?: undefined;
21
+ } | {
22
+ expectations: string;
23
+ option?: undefined;
24
+ };
25
+ observed: {
26
+ error: string;
27
+ execution_skipped: boolean;
28
+ };
29
+ evidence: {
30
+ paths: string[];
31
+ };
32
+ action: string;
33
+ source: {
34
+ kind: string;
35
+ option: string;
36
+ } | undefined;
37
+ legacy: {
38
+ finding_id: string;
39
+ check_name: string;
40
+ remediation_hint: string;
41
+ };
42
+ finding_id: string;
43
+ subject_ref: string;
44
+ subject_type: string;
45
+ check_name: string;
46
+ explanation: string;
47
+ evidence_paths: string[];
48
+ remediation_hint: string;
49
+ expected_reference: string | undefined;
50
+ };
51
+ export declare const writeInvalidAgentExpectationOutput: (params: {
52
+ outputDir: string;
53
+ command: string;
54
+ error: AgentExpectationUsageError;
55
+ }) => Promise<{
56
+ outputDir: string;
57
+ generatedAt: string;
58
+ }>;
@@ -0,0 +1,238 @@
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ const isFileNotFoundError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
4
+ const emptyAgentStats = () => ({
5
+ total: 0,
6
+ failed: 0,
7
+ broken: 0,
8
+ skipped: 0,
9
+ unknown: 0,
10
+ passed: 0,
11
+ });
12
+ const writeJson = async (path, value) => {
13
+ await mkdir(dirname(path), { recursive: true });
14
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
15
+ };
16
+ const writeText = async (path, value) => {
17
+ await mkdir(dirname(path), { recursive: true });
18
+ await writeFile(path, value, "utf-8");
19
+ };
20
+ const writeJsonl = async (path, values) => {
21
+ await writeText(path, values.map((value) => JSON.stringify(value)).join("\n") + (values.length ? "\n" : ""));
22
+ };
23
+ export const createInvalidExpectationFinding = (params) => {
24
+ const action = "Fix expectation syntax before using the run as validation.";
25
+ return {
26
+ schema_version: "allure-agent-finding/v2",
27
+ check_id: "expectations-invalid",
28
+ instance_id: "F0001",
29
+ severity: "high",
30
+ impact: "reject",
31
+ confidence: 1,
32
+ category: "bootstrap",
33
+ title: "Expectation input is invalid",
34
+ message: params.message,
35
+ subject: {
36
+ type: "run",
37
+ },
38
+ expected: params.sourceOption ? { option: params.sourceOption } : { expectations: "valid M1 expectation input" },
39
+ observed: {
40
+ error: params.message,
41
+ execution_skipped: true,
42
+ },
43
+ evidence: {
44
+ paths: ["manifest/run.json"],
45
+ },
46
+ action,
47
+ source: params.sourceOption
48
+ ? {
49
+ kind: "inline-option",
50
+ option: params.sourceOption,
51
+ }
52
+ : undefined,
53
+ legacy: {
54
+ finding_id: "F0001",
55
+ check_name: "expectations-invalid",
56
+ remediation_hint: action,
57
+ },
58
+ finding_id: "F0001",
59
+ subject_ref: "run",
60
+ subject_type: "run",
61
+ check_name: "expectations-invalid",
62
+ explanation: "The agent expectation controls could not be parsed, so the test command was not executed.",
63
+ evidence_paths: ["manifest/run.json"],
64
+ remediation_hint: action,
65
+ expected_reference: params.sourceOption,
66
+ };
67
+ };
68
+ export const writeInvalidAgentExpectationOutput = async (params) => {
69
+ const { outputDir, command, error } = params;
70
+ const generatedAt = new Date().toISOString();
71
+ const finding = createInvalidExpectationFinding({
72
+ message: error.message,
73
+ sourceOption: error.sourceOption,
74
+ });
75
+ const stats = emptyAgentStats();
76
+ try {
77
+ await rm(outputDir, { recursive: true });
78
+ }
79
+ catch (rmError) {
80
+ if (!isFileNotFoundError(rmError)) {
81
+ console.error("could not clean output directory", rmError);
82
+ }
83
+ }
84
+ const runManifest = {
85
+ schema_version: "allure-agent-output/v1",
86
+ report_uuid: null,
87
+ generated_at: generatedAt,
88
+ phase: "done",
89
+ command,
90
+ actual_exit_code: null,
91
+ original_exit_code: null,
92
+ exit_code: null,
93
+ summary: {
94
+ stats,
95
+ modeled_stats: stats,
96
+ unmodeled_from_stats: stats,
97
+ compact: {
98
+ visible_results: 0,
99
+ logical_tests: 0,
100
+ unmodeled_visible_results: 0,
101
+ runner_failures_outside_logical_tests: 0,
102
+ completeness: "complete",
103
+ findings: 1,
104
+ },
105
+ duration_ms: {
106
+ total: 0,
107
+ average: 0,
108
+ max: 0,
109
+ },
110
+ environments: [],
111
+ },
112
+ modeling: {
113
+ completeness: "complete",
114
+ reasons: ["test command was skipped because agent expectations were invalid"],
115
+ modeledStats: stats,
116
+ unmodeledFromStats: stats,
117
+ runnerFailures: {
118
+ total: 0,
119
+ globalErrors: 0,
120
+ stderrActionable: 0,
121
+ samples: [],
122
+ },
123
+ stderr: {
124
+ actionableCount: 0,
125
+ actionableSamples: [],
126
+ noisyWarningCount: 0,
127
+ noisyWarningSamples: [],
128
+ },
129
+ compact: {
130
+ visible_results: 0,
131
+ logical_tests: 0,
132
+ unmodeled_visible_results: 0,
133
+ runner_failures_outside_logical_tests: 0,
134
+ completeness: "complete",
135
+ },
136
+ },
137
+ paths: {
138
+ index_md: "index.md",
139
+ agents_md: "AGENTS.md",
140
+ tests_manifest: "manifest/tests.jsonl",
141
+ findings_manifest: "manifest/findings.jsonl",
142
+ test_events_manifest: "manifest/test-events.jsonl",
143
+ expected_manifest: null,
144
+ process_logs: {
145
+ stdout: null,
146
+ stderr: null,
147
+ },
148
+ },
149
+ expectations_present: false,
150
+ expectations: null,
151
+ expectation_result: {
152
+ schema_version: "allure-agent-expectation-result/v1",
153
+ status: "unavailable",
154
+ impact: "reject",
155
+ source: {
156
+ kind: "none",
157
+ path: null,
158
+ },
159
+ recognized_control_count: 0,
160
+ unsupported_controls: [],
161
+ degraded_controls: [],
162
+ summary: {
163
+ expected_tests: 0,
164
+ observed_tests: 0,
165
+ missing_expected: 0,
166
+ forbidden_observed: 0,
167
+ unexpected_observed: 0,
168
+ evidence_mismatches: 0,
169
+ },
170
+ finding_ids: ["F0001"],
171
+ },
172
+ check_summary: {
173
+ total: 1,
174
+ countsBySeverity: {
175
+ high: 1,
176
+ warning: 0,
177
+ info: 0,
178
+ },
179
+ countsByCategory: {
180
+ bootstrap: 1,
181
+ scope: 0,
182
+ metadata: 0,
183
+ evidence: 0,
184
+ smells: 0,
185
+ },
186
+ },
187
+ agent_context: {
188
+ agent_name: null,
189
+ loop_id: null,
190
+ task_id: null,
191
+ conversation_id: null,
192
+ },
193
+ };
194
+ const index = [
195
+ "# Allure Agent Run",
196
+ "",
197
+ "- Phase: done",
198
+ `- Command: ${command || "(not executed)"}`,
199
+ "- Exit code: not available",
200
+ "",
201
+ "## Expectation Result",
202
+ "",
203
+ "- Status: unavailable",
204
+ "- Impact: reject",
205
+ "- Recognized controls: 0",
206
+ "- Summary: expectation input was invalid; test execution was skipped",
207
+ "",
208
+ "## Findings",
209
+ "",
210
+ `- [HIGH][reject][bootstrap] ${finding.title}`,
211
+ ` Expected: ${error.sourceOption ?? "valid M1 expectation input"}`,
212
+ ` Observed: ${error.message}`,
213
+ ` Action: ${finding.action}`,
214
+ "",
215
+ "## Machine-Readable Artifacts",
216
+ "",
217
+ "- Run Manifest: [manifest/run.json](manifest/run.json)",
218
+ "- Findings Manifest: [manifest/findings.jsonl](manifest/findings.jsonl)",
219
+ "",
220
+ ].join("\n");
221
+ await Promise.all([
222
+ writeJson(join(outputDir, "manifest", "run.json"), runManifest),
223
+ writeJsonl(join(outputDir, "manifest", "findings.jsonl"), [finding]),
224
+ writeJsonl(join(outputDir, "manifest", "tests.jsonl"), []),
225
+ writeJsonl(join(outputDir, "manifest", "test-events.jsonl"), []),
226
+ writeText(join(outputDir, "index.md"), index),
227
+ writeText(join(outputDir, "AGENTS.md"), [
228
+ "# Allure Agent Output",
229
+ "",
230
+ "Read `index.md`, `manifest/run.json`, and `manifest/findings.jsonl` before using this run.",
231
+ "",
232
+ ].join("\n")),
233
+ ]);
234
+ return {
235
+ outputDir,
236
+ generatedAt,
237
+ };
238
+ };