@xera-ai/core 0.4.4 → 0.5.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/artifact/status.d.ts +12 -0
- package/dist/artifact/status.d.ts.map +1 -1
- package/dist/bin/internal.js +822 -464
- package/dist/bin-internal/auth-setup.d.ts +2 -0
- package/dist/bin-internal/auth-setup.d.ts.map +1 -0
- package/dist/bin-internal/exec.d.ts.map +1 -1
- package/dist/bin-internal/graph-record.d.ts.map +1 -1
- package/dist/bin-internal/index.d.ts.map +1 -1
- package/dist/bin-internal/normalize.d.ts.map +1 -1
- package/dist/bin-internal/report.d.ts.map +1 -1
- package/dist/bin-internal/verify-prompts.d.ts.map +1 -1
- package/dist/classifier/aggregate.d.ts.map +1 -1
- package/dist/classifier/auth-expired.d.ts +12 -0
- package/dist/classifier/auth-expired.d.ts.map +1 -0
- package/dist/classifier/contract-drift.d.ts +35 -0
- package/dist/classifier/contract-drift.d.ts.map +1 -0
- package/dist/classifier/rate-limited.d.ts +15 -0
- package/dist/classifier/rate-limited.d.ts.map +1 -0
- package/dist/config/schema.d.ts +32 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/graph/schema.d.ts +9 -0
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/types.d.ts +1 -1
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/scrub/index.d.ts +2 -0
- package/dist/scrub/index.d.ts.map +1 -0
- package/dist/scrub/rules.d.ts +12 -0
- package/dist/scrub/rules.d.ts.map +1 -0
- package/dist/src/index.js +109 -4
- package/package.json +4 -3
- package/src/artifact/status.ts +3 -0
- package/src/bin-internal/auth-setup.ts +116 -0
- package/src/bin-internal/exec.ts +42 -9
- package/src/bin-internal/graph-record.ts +3 -0
- package/src/bin-internal/index.ts +2 -0
- package/src/bin-internal/normalize.ts +13 -1
- package/src/bin-internal/report.ts +94 -2
- package/src/bin-internal/verify-prompts.ts +2 -1
- package/src/classifier/aggregate.ts +3 -0
- package/src/classifier/auth-expired.ts +44 -0
- package/src/classifier/contract-drift.ts +111 -0
- package/src/classifier/rate-limited.ts +25 -0
- package/src/config/schema.ts +51 -8
- package/src/graph/schema.ts +3 -0
- package/src/graph/types.ts +4 -1
- package/src/index.ts +2 -0
- package/src/scrub/index.ts +1 -0
- package/src/scrub/rules.ts +69 -0
package/dist/bin/internal.js
CHANGED
|
@@ -19,15 +19,15 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
19
19
|
var __require = import.meta.require;
|
|
20
20
|
|
|
21
21
|
// src/graph/paths.ts
|
|
22
|
-
import { join } from "path";
|
|
22
|
+
import { join as join3 } from "path";
|
|
23
23
|
function graphPaths(repoRoot) {
|
|
24
|
-
const eventsDir =
|
|
24
|
+
const eventsDir = join3(repoRoot, ".xera/graph/events");
|
|
25
25
|
return {
|
|
26
26
|
eventsDir,
|
|
27
|
-
snapshotFile:
|
|
28
|
-
costLog:
|
|
29
|
-
eventsMonthDir: (yyyyMm) =>
|
|
30
|
-
eventFile: (ulid, skill, ticketId, yyyyMm) =>
|
|
27
|
+
snapshotFile: join3(repoRoot, ".xera/graph/snapshot.json"),
|
|
28
|
+
costLog: join3(repoRoot, ".xera/cost-log.jsonl"),
|
|
29
|
+
eventsMonthDir: (yyyyMm) => join3(eventsDir, yyyyMm),
|
|
30
|
+
eventFile: (ulid, skill, ticketId, yyyyMm) => join3(eventsDir, yyyyMm, `${ulid}-${skill}-${ticketId}.jsonl`)
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
function currentYyyyMm(now = new Date) {
|
|
@@ -41,7 +41,7 @@ var init_paths = () => {};
|
|
|
41
41
|
var SCHEMA_VERSION = 1;
|
|
42
42
|
|
|
43
43
|
// src/graph/schema.ts
|
|
44
|
-
import { z } from "zod";
|
|
44
|
+
import { z as z2 } from "zod";
|
|
45
45
|
function safeParseEvent(value) {
|
|
46
46
|
const r = EventSchema.safeParse(value);
|
|
47
47
|
if (r.success)
|
|
@@ -50,110 +50,113 @@ function safeParseEvent(value) {
|
|
|
50
50
|
}
|
|
51
51
|
var schemaV, iso, ticketFetched, ticketEnriched, scenarioGenerated, pomGenerated, pomPromoted, runCompleted, classification, runClassified, classificationDisputed, edgeDiscovered, base, EventSchema;
|
|
52
52
|
var init_schema = __esm(() => {
|
|
53
|
-
schemaV =
|
|
54
|
-
iso =
|
|
55
|
-
ticketFetched =
|
|
56
|
-
ticketId:
|
|
57
|
-
summary:
|
|
58
|
-
ac:
|
|
59
|
-
jiraLinks:
|
|
60
|
-
ticketId:
|
|
61
|
-
relation:
|
|
53
|
+
schemaV = z2.literal(SCHEMA_VERSION);
|
|
54
|
+
iso = z2.string().datetime({ offset: false });
|
|
55
|
+
ticketFetched = z2.object({
|
|
56
|
+
ticketId: z2.string().regex(/^[A-Z][A-Z0-9]*-\d+$/),
|
|
57
|
+
summary: z2.string(),
|
|
58
|
+
ac: z2.array(z2.string()),
|
|
59
|
+
jiraLinks: z2.array(z2.object({
|
|
60
|
+
ticketId: z2.string().regex(/^[A-Z][A-Z0-9]*-\d+$/),
|
|
61
|
+
relation: z2.enum(["blocks", "duplicates", "relates", "supersedes"])
|
|
62
62
|
})),
|
|
63
|
-
storyHash:
|
|
64
|
-
modifiesAreas:
|
|
63
|
+
storyHash: z2.string(),
|
|
64
|
+
modifiesAreas: z2.array(z2.string().regex(/^[a-z0-9-]+$/))
|
|
65
65
|
}).passthrough();
|
|
66
|
-
ticketEnriched =
|
|
67
|
-
ticketId:
|
|
66
|
+
ticketEnriched = z2.object({
|
|
67
|
+
ticketId: z2.string(),
|
|
68
68
|
enrichedAt: iso,
|
|
69
|
-
similarCount:
|
|
69
|
+
similarCount: z2.number().int().nonnegative()
|
|
70
70
|
}).passthrough();
|
|
71
|
-
scenarioGenerated =
|
|
72
|
-
scenarioId:
|
|
73
|
-
ticketId:
|
|
74
|
-
name:
|
|
75
|
-
gherkin:
|
|
76
|
-
priority:
|
|
77
|
-
featureHash:
|
|
71
|
+
scenarioGenerated = z2.object({
|
|
72
|
+
scenarioId: z2.string(),
|
|
73
|
+
ticketId: z2.string(),
|
|
74
|
+
name: z2.string(),
|
|
75
|
+
gherkin: z2.string(),
|
|
76
|
+
priority: z2.enum(["p0", "p1", "p2"]),
|
|
77
|
+
featureHash: z2.string(),
|
|
78
78
|
generatedAt: iso
|
|
79
79
|
}).passthrough();
|
|
80
|
-
pomGenerated =
|
|
81
|
-
pomId:
|
|
82
|
-
ticketId:
|
|
83
|
-
filePath:
|
|
84
|
-
route:
|
|
85
|
-
locators:
|
|
86
|
-
scope:
|
|
80
|
+
pomGenerated = z2.object({
|
|
81
|
+
pomId: z2.string(),
|
|
82
|
+
ticketId: z2.string(),
|
|
83
|
+
filePath: z2.string(),
|
|
84
|
+
route: z2.string(),
|
|
85
|
+
locators: z2.array(z2.string()),
|
|
86
|
+
scope: z2.enum(["local", "shared"])
|
|
87
87
|
}).passthrough();
|
|
88
|
-
pomPromoted =
|
|
89
|
-
pomId:
|
|
90
|
-
fromPath:
|
|
91
|
-
toPath:
|
|
88
|
+
pomPromoted = z2.object({
|
|
89
|
+
pomId: z2.string(),
|
|
90
|
+
fromPath: z2.string(),
|
|
91
|
+
toPath: z2.string()
|
|
92
92
|
}).passthrough();
|
|
93
|
-
runCompleted =
|
|
94
|
-
scenarioId:
|
|
95
|
-
ticketId:
|
|
96
|
-
runId:
|
|
97
|
-
status:
|
|
98
|
-
traceId:
|
|
99
|
-
runtime:
|
|
93
|
+
runCompleted = z2.object({
|
|
94
|
+
scenarioId: z2.string(),
|
|
95
|
+
ticketId: z2.string(),
|
|
96
|
+
runId: z2.string(),
|
|
97
|
+
status: z2.enum(["pass", "fail"]),
|
|
98
|
+
traceId: z2.string().optional(),
|
|
99
|
+
runtime: z2.number().nonnegative()
|
|
100
100
|
}).passthrough();
|
|
101
|
-
classification =
|
|
101
|
+
classification = z2.enum([
|
|
102
102
|
"REAL_BUG",
|
|
103
103
|
"TEST_BUG",
|
|
104
104
|
"SELECTOR_DRIFT",
|
|
105
105
|
"FLAKY",
|
|
106
106
|
"PASS",
|
|
107
|
-
"TEST_OUTDATED"
|
|
107
|
+
"TEST_OUTDATED",
|
|
108
|
+
"CONTRACT_DRIFT",
|
|
109
|
+
"RATE_LIMITED",
|
|
110
|
+
"AUTH_EXPIRED"
|
|
108
111
|
]);
|
|
109
|
-
runClassified =
|
|
110
|
-
scenarioId:
|
|
111
|
-
runId:
|
|
112
|
+
runClassified = z2.object({
|
|
113
|
+
scenarioId: z2.string(),
|
|
114
|
+
runId: z2.string(),
|
|
112
115
|
classification,
|
|
113
|
-
confidence:
|
|
116
|
+
confidence: z2.enum(["low", "medium", "high"])
|
|
114
117
|
}).passthrough();
|
|
115
|
-
classificationDisputed =
|
|
116
|
-
runId:
|
|
117
|
-
scenarioId:
|
|
118
|
+
classificationDisputed = z2.object({
|
|
119
|
+
runId: z2.string(),
|
|
120
|
+
scenarioId: z2.string(),
|
|
118
121
|
originalClassification: classification,
|
|
119
122
|
disputedTo: classification,
|
|
120
|
-
qaActor:
|
|
121
|
-
qaReason:
|
|
123
|
+
qaActor: z2.string(),
|
|
124
|
+
qaReason: z2.string().optional()
|
|
122
125
|
}).passthrough();
|
|
123
|
-
edgeDiscovered =
|
|
124
|
-
kind:
|
|
125
|
-
from:
|
|
126
|
-
to:
|
|
127
|
-
confidence:
|
|
128
|
-
source:
|
|
126
|
+
edgeDiscovered = z2.object({
|
|
127
|
+
kind: z2.enum(["tests", "uses", "covers", "modifies", "jira-linked", "similar", "ran"]),
|
|
128
|
+
from: z2.string(),
|
|
129
|
+
to: z2.string(),
|
|
130
|
+
confidence: z2.number().min(0).max(1).optional(),
|
|
131
|
+
source: z2.string()
|
|
129
132
|
}).passthrough();
|
|
130
133
|
base = {
|
|
131
|
-
event_id:
|
|
134
|
+
event_id: z2.string().min(20),
|
|
132
135
|
schema_version: schemaV,
|
|
133
136
|
ts: iso,
|
|
134
|
-
actor:
|
|
137
|
+
actor: z2.string()
|
|
135
138
|
};
|
|
136
|
-
EventSchema =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
EventSchema = z2.discriminatedUnion("type", [
|
|
140
|
+
z2.object({ ...base, type: z2.literal("ticket.fetched"), payload: ticketFetched }),
|
|
141
|
+
z2.object({ ...base, type: z2.literal("ticket.enriched"), payload: ticketEnriched }),
|
|
142
|
+
z2.object({ ...base, type: z2.literal("scenario.generated"), payload: scenarioGenerated }),
|
|
143
|
+
z2.object({ ...base, type: z2.literal("pom.generated"), payload: pomGenerated }),
|
|
144
|
+
z2.object({ ...base, type: z2.literal("pom.promoted"), payload: pomPromoted }),
|
|
145
|
+
z2.object({ ...base, type: z2.literal("run.completed"), payload: runCompleted }),
|
|
146
|
+
z2.object({ ...base, type: z2.literal("run.classified"), payload: runClassified }),
|
|
147
|
+
z2.object({
|
|
145
148
|
...base,
|
|
146
|
-
type:
|
|
149
|
+
type: z2.literal("classification.disputed"),
|
|
147
150
|
payload: classificationDisputed
|
|
148
151
|
}),
|
|
149
|
-
|
|
152
|
+
z2.object({ ...base, type: z2.literal("edge.discovered"), payload: edgeDiscovered })
|
|
150
153
|
]);
|
|
151
154
|
});
|
|
152
155
|
|
|
153
156
|
// src/graph/store.ts
|
|
154
157
|
import { createHash } from "crypto";
|
|
155
158
|
import {
|
|
156
|
-
existsSync,
|
|
159
|
+
existsSync as existsSync3,
|
|
157
160
|
mkdirSync,
|
|
158
161
|
readdirSync,
|
|
159
162
|
readFileSync,
|
|
@@ -180,7 +183,7 @@ function appendEvents(repoRoot, events, opts) {
|
|
|
180
183
|
}
|
|
181
184
|
function loadAllEvents(repoRoot) {
|
|
182
185
|
const paths = graphPaths(repoRoot);
|
|
183
|
-
if (!
|
|
186
|
+
if (!existsSync3(paths.eventsDir))
|
|
184
187
|
return [];
|
|
185
188
|
const files = [];
|
|
186
189
|
for (const monthDir of readdirSync(paths.eventsDir, { withFileTypes: true })) {
|
|
@@ -359,7 +362,7 @@ function writeSnapshot(repoRoot, snap) {
|
|
|
359
362
|
}
|
|
360
363
|
function loadSnapshot(repoRoot) {
|
|
361
364
|
const paths = graphPaths(repoRoot);
|
|
362
|
-
if (!
|
|
365
|
+
if (!existsSync3(paths.snapshotFile))
|
|
363
366
|
return null;
|
|
364
367
|
try {
|
|
365
368
|
return JSON.parse(readFileSync(paths.snapshotFile, "utf8"));
|
|
@@ -437,8 +440,8 @@ __export(exports_graph_record_script, {
|
|
|
437
440
|
recordScriptImpl: () => recordScriptImpl
|
|
438
441
|
});
|
|
439
442
|
import { createHash as createHash2 } from "crypto";
|
|
440
|
-
import { existsSync as
|
|
441
|
-
import { basename, join as
|
|
443
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
444
|
+
import { basename, join as join5 } from "path";
|
|
442
445
|
function inferPriority(name, gherkin) {
|
|
443
446
|
const haystack = `${name} ${gherkin}`.toLowerCase();
|
|
444
447
|
for (const kw of P0_KEYWORDS) {
|
|
@@ -480,9 +483,9 @@ function parseFeature(text) {
|
|
|
480
483
|
return scenarios;
|
|
481
484
|
}
|
|
482
485
|
function listPomFiles(dir) {
|
|
483
|
-
if (!
|
|
486
|
+
if (!existsSync6(dir))
|
|
484
487
|
return [];
|
|
485
|
-
return readdirSync2(dir).filter((f) => f.endsWith(".ts")).map((f) =>
|
|
488
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".ts")).map((f) => join5(dir, f));
|
|
486
489
|
}
|
|
487
490
|
function extractRoute(pomContent) {
|
|
488
491
|
const m = pomContent.match(/goto\s*\(\s*['"]([^'"]+)['"]/);
|
|
@@ -509,11 +512,11 @@ function extractPomUsage(specContent) {
|
|
|
509
512
|
return [...names];
|
|
510
513
|
}
|
|
511
514
|
async function recordScriptImpl(repoRoot, ticket) {
|
|
512
|
-
const ticketDir =
|
|
513
|
-
const featurePath =
|
|
514
|
-
const specPath =
|
|
515
|
-
const pomDir =
|
|
516
|
-
if (!
|
|
515
|
+
const ticketDir = join5(repoRoot, ".xera", ticket);
|
|
516
|
+
const featurePath = join5(ticketDir, "feature", `${ticket}.feature`);
|
|
517
|
+
const specPath = join5(ticketDir, "tests", `${ticket}.spec.ts`);
|
|
518
|
+
const pomDir = join5(ticketDir, "poms");
|
|
519
|
+
if (!existsSync6(featurePath)) {
|
|
517
520
|
console.error(`[graph-record script] feature missing`);
|
|
518
521
|
return 1;
|
|
519
522
|
}
|
|
@@ -551,7 +554,7 @@ async function recordScriptImpl(repoRoot, ticket) {
|
|
|
551
554
|
};
|
|
552
555
|
events.push(mk("xera-script", "pom.generated", pg));
|
|
553
556
|
}
|
|
554
|
-
if (
|
|
557
|
+
if (existsSync6(specPath)) {
|
|
555
558
|
const specContent = readFileSync4(specPath, "utf8");
|
|
556
559
|
const usedPoms = extractPomUsage(specContent);
|
|
557
560
|
for (const scenario of scenarios) {
|
|
@@ -7616,8 +7619,8 @@ __export(exports_graph_record, {
|
|
|
7616
7619
|
graphRecordCmd: () => graphRecordCmd
|
|
7617
7620
|
});
|
|
7618
7621
|
import { createHash as createHash3 } from "crypto";
|
|
7619
|
-
import { existsSync as
|
|
7620
|
-
import { basename as basename2, join as
|
|
7622
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
7623
|
+
import { basename as basename2, join as join6 } from "path";
|
|
7621
7624
|
function nowIso2() {
|
|
7622
7625
|
return new Date().toISOString();
|
|
7623
7626
|
}
|
|
@@ -7641,8 +7644,8 @@ function makeEvent(actor, type, payload) {
|
|
|
7641
7644
|
};
|
|
7642
7645
|
}
|
|
7643
7646
|
function readStoryFrontmatter(repoRoot, ticket) {
|
|
7644
|
-
const path =
|
|
7645
|
-
if (!
|
|
7647
|
+
const path = join6(repoRoot, ".xera", ticket, "story.md");
|
|
7648
|
+
if (!existsSync7(path))
|
|
7646
7649
|
return null;
|
|
7647
7650
|
const raw = readFileSync5(path, "utf8");
|
|
7648
7651
|
const m = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -7651,8 +7654,8 @@ function readStoryFrontmatter(repoRoot, ticket) {
|
|
|
7651
7654
|
return $parse(m[1]);
|
|
7652
7655
|
}
|
|
7653
7656
|
function readGraphInput(repoRoot, ticket) {
|
|
7654
|
-
const path =
|
|
7655
|
-
if (!
|
|
7657
|
+
const path = join6(repoRoot, ".xera", ticket, "graph-input.json");
|
|
7658
|
+
if (!existsSync7(path))
|
|
7656
7659
|
return { modifiesAreas: [] };
|
|
7657
7660
|
try {
|
|
7658
7661
|
return JSON.parse(readFileSync5(path, "utf8"));
|
|
@@ -7703,8 +7706,8 @@ async function recordScript(repoRoot, ticket) {
|
|
|
7703
7706
|
return recordScriptImpl2(repoRoot, ticket);
|
|
7704
7707
|
}
|
|
7705
7708
|
async function recordExec(repoRoot, ticket, runId) {
|
|
7706
|
-
const reporterPath =
|
|
7707
|
-
if (!
|
|
7709
|
+
const reporterPath = join6(repoRoot, ".xera", ticket, "runs", runId, "reporter.json");
|
|
7710
|
+
if (!existsSync7(reporterPath)) {
|
|
7708
7711
|
console.error(`[graph-record exec] reporter.json missing`);
|
|
7709
7712
|
return 1;
|
|
7710
7713
|
}
|
|
@@ -7726,8 +7729,8 @@ async function recordExec(repoRoot, ticket, runId) {
|
|
|
7726
7729
|
return 0;
|
|
7727
7730
|
}
|
|
7728
7731
|
async function recordClassify(repoRoot, ticket, runId) {
|
|
7729
|
-
const classifyPath =
|
|
7730
|
-
if (!
|
|
7732
|
+
const classifyPath = join6(repoRoot, ".xera", ticket, "runs", runId, "classifier-output.json");
|
|
7733
|
+
if (!existsSync7(classifyPath)) {
|
|
7731
7734
|
console.error(`[graph-record classify] classifier-output.json missing`);
|
|
7732
7735
|
return 1;
|
|
7733
7736
|
}
|
|
@@ -7834,7 +7837,10 @@ async function graphRecordCmd(argv) {
|
|
|
7834
7837
|
"SELECTOR_DRIFT",
|
|
7835
7838
|
"FLAKY",
|
|
7836
7839
|
"PASS",
|
|
7837
|
-
"TEST_OUTDATED"
|
|
7840
|
+
"TEST_OUTDATED",
|
|
7841
|
+
"CONTRACT_DRIFT",
|
|
7842
|
+
"RATE_LIMITED",
|
|
7843
|
+
"AUTH_EXPIRED"
|
|
7838
7844
|
];
|
|
7839
7845
|
if (!validClass.includes(from) || !validClass.includes(to)) {
|
|
7840
7846
|
console.error(`[graph-record dispute] --from and --to must be one of: ${validClass.join(", ")}`);
|
|
@@ -7869,11 +7875,11 @@ var exports_graph_backfill = {};
|
|
|
7869
7875
|
__export(exports_graph_backfill, {
|
|
7870
7876
|
graphBackfillCmd: () => graphBackfillCmd
|
|
7871
7877
|
});
|
|
7872
|
-
import { existsSync as
|
|
7873
|
-
import { join as
|
|
7878
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
7879
|
+
import { join as join7 } from "path";
|
|
7874
7880
|
async function backfillTicket(repoRoot, ticket, dryRun) {
|
|
7875
|
-
const storyPath =
|
|
7876
|
-
if (!
|
|
7881
|
+
const storyPath = join7(repoRoot, ".xera", ticket, "story.md");
|
|
7882
|
+
if (!existsSync8(storyPath))
|
|
7877
7883
|
return 0;
|
|
7878
7884
|
const { recordFetch: recordFetch2 } = await Promise.resolve().then(() => (init_graph_record(), exports_graph_record));
|
|
7879
7885
|
if (dryRun) {
|
|
@@ -7887,8 +7893,8 @@ async function backfillTicket(repoRoot, ticket, dryRun) {
|
|
|
7887
7893
|
async function graphBackfillCmd(argv) {
|
|
7888
7894
|
const dryRun = argv.includes("--dry-run");
|
|
7889
7895
|
const repoRoot = process.cwd();
|
|
7890
|
-
const xeraDir =
|
|
7891
|
-
if (!
|
|
7896
|
+
const xeraDir = join7(repoRoot, ".xera");
|
|
7897
|
+
if (!existsSync8(xeraDir)) {
|
|
7892
7898
|
console.log("[backfill] no .xera/ directory");
|
|
7893
7899
|
return 0;
|
|
7894
7900
|
}
|
|
@@ -7914,6 +7920,212 @@ var init_graph_backfill = __esm(() => {
|
|
|
7914
7920
|
init_graph_record_script();
|
|
7915
7921
|
});
|
|
7916
7922
|
|
|
7923
|
+
// src/bin-internal/auth-setup.ts
|
|
7924
|
+
import { existsSync as existsSync2 } from "fs";
|
|
7925
|
+
import { join as join2 } from "path";
|
|
7926
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
7927
|
+
|
|
7928
|
+
// src/config/load.ts
|
|
7929
|
+
import { existsSync } from "fs";
|
|
7930
|
+
import { join } from "path";
|
|
7931
|
+
import { pathToFileURL } from "url";
|
|
7932
|
+
|
|
7933
|
+
// src/config/schema.ts
|
|
7934
|
+
import { z } from "zod";
|
|
7935
|
+
var AuthRoleSchema = z.object({
|
|
7936
|
+
envEmail: z.string().min(1),
|
|
7937
|
+
envPassword: z.string().min(1)
|
|
7938
|
+
});
|
|
7939
|
+
var AuthSchema = z.object({
|
|
7940
|
+
strategy: z.enum(["storageState", "apiToken", "none"]).default("none"),
|
|
7941
|
+
ttl: z.string().default("8h"),
|
|
7942
|
+
refreshBuffer: z.string().default("30m"),
|
|
7943
|
+
setupScript: z.string().optional(),
|
|
7944
|
+
roles: z.record(z.string(), AuthRoleSchema).default({})
|
|
7945
|
+
});
|
|
7946
|
+
var WebSchema = z.object({
|
|
7947
|
+
baseUrl: z.record(z.string(), z.string().url()).refine((m) => Object.keys(m).length > 0, {
|
|
7948
|
+
message: "baseUrl must have at least one environment"
|
|
7949
|
+
}),
|
|
7950
|
+
defaultEnv: z.string(),
|
|
7951
|
+
auth: AuthSchema.prefault({}),
|
|
7952
|
+
testData: z.object({
|
|
7953
|
+
users: z.record(z.string(), z.object({ fromAuth: z.string() })).default({})
|
|
7954
|
+
}).prefault({})
|
|
7955
|
+
}).refine((w) => w.baseUrl[w.defaultEnv] !== undefined, {
|
|
7956
|
+
message: "defaultEnv must exist in baseUrl map",
|
|
7957
|
+
path: ["defaultEnv"]
|
|
7958
|
+
});
|
|
7959
|
+
var HttpAuthRoleSchema = z.object({
|
|
7960
|
+
tokenEnv: z.string().optional(),
|
|
7961
|
+
userEnv: z.string().optional(),
|
|
7962
|
+
passEnv: z.string().optional(),
|
|
7963
|
+
tokenUrl: z.string().url().optional(),
|
|
7964
|
+
clientIdEnv: z.string().optional(),
|
|
7965
|
+
clientSecretEnv: z.string().optional(),
|
|
7966
|
+
scope: z.string().optional()
|
|
7967
|
+
});
|
|
7968
|
+
var HttpAuthSchema = z.object({
|
|
7969
|
+
strategy: z.enum(["bearer", "apiKey", "basic", "oauth-cc", "custom", "none"]).default("none"),
|
|
7970
|
+
ttl: z.string().default("8h"),
|
|
7971
|
+
refreshBuffer: z.string().default("30m"),
|
|
7972
|
+
roles: z.record(z.string(), HttpAuthRoleSchema).default({})
|
|
7973
|
+
});
|
|
7974
|
+
var HttpSchema = z.object({
|
|
7975
|
+
baseUrl: z.record(z.string(), z.string().url()).refine((m) => Object.keys(m).length > 0, {
|
|
7976
|
+
message: "baseUrl must have at least one environment"
|
|
7977
|
+
}),
|
|
7978
|
+
defaultEnv: z.string(),
|
|
7979
|
+
spec: z.string().optional(),
|
|
7980
|
+
auth: HttpAuthSchema.prefault({})
|
|
7981
|
+
}).refine((h) => h.baseUrl[h.defaultEnv] !== undefined, {
|
|
7982
|
+
message: "defaultEnv must exist in baseUrl map",
|
|
7983
|
+
path: ["defaultEnv"]
|
|
7984
|
+
});
|
|
7985
|
+
var JiraSchema = z.object({
|
|
7986
|
+
baseUrl: z.string().url(),
|
|
7987
|
+
projectKeys: z.array(z.string().min(1)).min(1),
|
|
7988
|
+
fields: z.object({
|
|
7989
|
+
story: z.string().min(1),
|
|
7990
|
+
acceptanceCriteria: z.string().optional(),
|
|
7991
|
+
attachments: z.string().default("attachment")
|
|
7992
|
+
})
|
|
7993
|
+
});
|
|
7994
|
+
var AISchema = z.object({
|
|
7995
|
+
livePageSnapshot: z.boolean().default(true),
|
|
7996
|
+
confidenceThreshold: z.enum(["low", "medium", "high"]).default("medium"),
|
|
7997
|
+
maxRetries: z.object({
|
|
7998
|
+
typecheck: z.number().int().min(0).max(5).default(2),
|
|
7999
|
+
lint: z.number().int().min(0).max(5).default(2),
|
|
8000
|
+
validateFeature: z.number().int().min(0).max(5).default(2)
|
|
8001
|
+
}).prefault({})
|
|
8002
|
+
}).prefault({});
|
|
8003
|
+
var ReportingSchema = z.object({
|
|
8004
|
+
language: z.enum(["en", "vi"]).default("en"),
|
|
8005
|
+
postToJira: z.boolean().default(true),
|
|
8006
|
+
transition: z.object({
|
|
8007
|
+
onPass: z.string().nullable().default(null),
|
|
8008
|
+
onFail: z.string().nullable().default(null)
|
|
8009
|
+
}).prefault({}),
|
|
8010
|
+
artifactLinks: z.enum(["git", "local"]).default("git")
|
|
8011
|
+
}).prefault({});
|
|
8012
|
+
var RunSchema = z.object({
|
|
8013
|
+
autoImpact: z.object({
|
|
8014
|
+
enabled: z.boolean().default(true),
|
|
8015
|
+
threshold: z.number().nonnegative().default(8)
|
|
8016
|
+
}).prefault({})
|
|
8017
|
+
}).prefault({});
|
|
8018
|
+
var XeraConfigSchema = z.object({
|
|
8019
|
+
jira: JiraSchema,
|
|
8020
|
+
web: WebSchema.optional(),
|
|
8021
|
+
http: HttpSchema.optional(),
|
|
8022
|
+
ai: AISchema,
|
|
8023
|
+
reporting: ReportingSchema,
|
|
8024
|
+
run: RunSchema.prefault({}),
|
|
8025
|
+
adapters: z.array(z.enum(["web", "http"])).min(1).default(["web"])
|
|
8026
|
+
}).refine((c) => c.web !== undefined || c.http !== undefined, {
|
|
8027
|
+
message: "At least one of `web` or `http` must be configured"
|
|
8028
|
+
}).refine((c) => c.adapters.every((a) => (a === "web" ? c.web : c.http) !== undefined), {
|
|
8029
|
+
message: "Every adapter in `adapters` must have a corresponding config block",
|
|
8030
|
+
path: ["adapters"]
|
|
8031
|
+
});
|
|
8032
|
+
|
|
8033
|
+
// src/config/load.ts
|
|
8034
|
+
async function loadConfig(cwd) {
|
|
8035
|
+
const path = join(cwd, "xera.config.ts");
|
|
8036
|
+
if (!existsSync(path)) {
|
|
8037
|
+
throw new Error(`xera.config.ts not found in ${cwd}`);
|
|
8038
|
+
}
|
|
8039
|
+
const mod = await import(pathToFileURL(path).href);
|
|
8040
|
+
const raw = mod.default ?? mod;
|
|
8041
|
+
return XeraConfigSchema.parse(raw);
|
|
8042
|
+
}
|
|
8043
|
+
|
|
8044
|
+
// src/bin-internal/auth-setup.ts
|
|
8045
|
+
function parseOpts(argv) {
|
|
8046
|
+
const opts = { shape: "all" };
|
|
8047
|
+
for (let i = 0;i < argv.length; i++) {
|
|
8048
|
+
const a = argv[i];
|
|
8049
|
+
const next = argv[i + 1];
|
|
8050
|
+
if (a === "--role" && next) {
|
|
8051
|
+
opts.role = next;
|
|
8052
|
+
i++;
|
|
8053
|
+
} else if (a === "--shape" && next) {
|
|
8054
|
+
if (next === "web" || next === "http" || next === "all")
|
|
8055
|
+
opts.shape = next;
|
|
8056
|
+
i++;
|
|
8057
|
+
}
|
|
8058
|
+
}
|
|
8059
|
+
return opts;
|
|
8060
|
+
}
|
|
8061
|
+
async function authSetupCmd(argv) {
|
|
8062
|
+
const opts = parseOpts(argv);
|
|
8063
|
+
const cwd = process.cwd();
|
|
8064
|
+
const config = await loadConfig(cwd);
|
|
8065
|
+
const authSetupScript = join2(cwd, "shared", "auth-setup.ts");
|
|
8066
|
+
if (!existsSync2(authSetupScript)) {
|
|
8067
|
+
console.error(`[xera:auth-setup] auth-setup.ts not found at ${authSetupScript}. Run 'bunx @xera-ai/cli init' first.`);
|
|
8068
|
+
return 1;
|
|
8069
|
+
}
|
|
8070
|
+
const mod = await import(pathToFileURL2(authSetupScript).href);
|
|
8071
|
+
let exitCode = 0;
|
|
8072
|
+
if ((opts.shape === "all" || opts.shape === "web") && config.web && typeof mod.web === "function") {
|
|
8073
|
+
const { runAuthSetup } = await import("@xera-ai/web");
|
|
8074
|
+
const { chromium } = await import("@playwright/test");
|
|
8075
|
+
const browser = await chromium.launch();
|
|
8076
|
+
try {
|
|
8077
|
+
for (const [roleName, roleCreds] of Object.entries(config.web.auth.roles)) {
|
|
8078
|
+
if (opts.role && roleName !== opts.role)
|
|
8079
|
+
continue;
|
|
8080
|
+
const email = process.env[roleCreds.envEmail];
|
|
8081
|
+
const password = process.env[roleCreds.envPassword];
|
|
8082
|
+
if (!email || !password) {
|
|
8083
|
+
console.error(`[xera:auth-setup] missing env vars ${roleCreds.envEmail} / ${roleCreds.envPassword} for role '${roleName}'`);
|
|
8084
|
+
exitCode = 1;
|
|
8085
|
+
continue;
|
|
8086
|
+
}
|
|
8087
|
+
try {
|
|
8088
|
+
await runAuthSetup({
|
|
8089
|
+
role: roleName,
|
|
8090
|
+
creds: { email, password },
|
|
8091
|
+
setupScriptPath: authSetupScript,
|
|
8092
|
+
authDir: join2(cwd, ".xera", ".auth"),
|
|
8093
|
+
browser
|
|
8094
|
+
});
|
|
8095
|
+
console.log(`[xera:auth-setup] \u2713 ${roleName}.json (web)`);
|
|
8096
|
+
} catch (e) {
|
|
8097
|
+
console.error(`[xera:auth-setup] \u2717 web/${roleName}: ${e.message}`);
|
|
8098
|
+
exitCode = 1;
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8101
|
+
} finally {
|
|
8102
|
+
await browser.close();
|
|
8103
|
+
}
|
|
8104
|
+
}
|
|
8105
|
+
if ((opts.shape === "all" || opts.shape === "http") && config.http && typeof mod.http === "function") {
|
|
8106
|
+
globalThis.__XERA_HTTP_CONFIG__ = config.http;
|
|
8107
|
+
const { runHttpAuthSetup } = await import("@xera-ai/http");
|
|
8108
|
+
for (const roleName of Object.keys(config.http.auth.roles)) {
|
|
8109
|
+
if (opts.role && roleName !== opts.role)
|
|
8110
|
+
continue;
|
|
8111
|
+
try {
|
|
8112
|
+
await runHttpAuthSetup({
|
|
8113
|
+
authDir: join2(cwd, ".xera", ".auth"),
|
|
8114
|
+
role: roleName,
|
|
8115
|
+
config: config.http,
|
|
8116
|
+
setupFn: mod.http,
|
|
8117
|
+
creds: { email: "", password: "" }
|
|
8118
|
+
});
|
|
8119
|
+
console.log(`[xera:auth-setup] \u2713 http/${roleName}.json`);
|
|
8120
|
+
} catch (e) {
|
|
8121
|
+
console.error(`[xera:auth-setup] \u2717 http/${roleName}: ${e.message}`);
|
|
8122
|
+
exitCode = 1;
|
|
8123
|
+
}
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
return exitCode;
|
|
8127
|
+
}
|
|
8128
|
+
|
|
7917
8129
|
// src/bin-internal/disputes.ts
|
|
7918
8130
|
init_store();
|
|
7919
8131
|
function parseDuration(s) {
|
|
@@ -7990,16 +8202,16 @@ async function disputesCmd(argv) {
|
|
|
7990
8202
|
}
|
|
7991
8203
|
|
|
7992
8204
|
// src/bin-internal/doctor.ts
|
|
7993
|
-
import { existsSync as
|
|
7994
|
-
import { join as
|
|
8205
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "fs";
|
|
8206
|
+
import { join as join8 } from "path";
|
|
7995
8207
|
|
|
7996
8208
|
// src/graph/cost.ts
|
|
7997
|
-
import { appendFileSync, existsSync as
|
|
8209
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
7998
8210
|
init_paths();
|
|
7999
8211
|
function summarizeCost(repoRoot, daysBack) {
|
|
8000
8212
|
const paths = graphPaths(repoRoot);
|
|
8001
8213
|
const result = { totalCalls: 0, totalUsd: 0, bySkill: {}, windowDays: daysBack };
|
|
8002
|
-
if (!
|
|
8214
|
+
if (!existsSync4(paths.costLog))
|
|
8003
8215
|
return result;
|
|
8004
8216
|
const cutoff = Date.now() - daysBack * 86400 * 1000;
|
|
8005
8217
|
for (const line of readFileSync2(paths.costLog, "utf8").split(`
|
|
@@ -8029,11 +8241,12 @@ function summarizeCost(repoRoot, daysBack) {
|
|
|
8029
8241
|
init_store();
|
|
8030
8242
|
|
|
8031
8243
|
// src/bin-internal/verify-prompts.ts
|
|
8032
|
-
import { existsSync as
|
|
8033
|
-
import { join as
|
|
8244
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
8245
|
+
import { join as join4 } from "path";
|
|
8034
8246
|
var IN_SCOPE_PROMPTS = [
|
|
8035
8247
|
"feature-from-story.md",
|
|
8036
|
-
"script-from-feature.md",
|
|
8248
|
+
"script-from-feature-web.md",
|
|
8249
|
+
"script-from-feature-http.md",
|
|
8037
8250
|
"heal-locator.md",
|
|
8038
8251
|
"extract-areas.md",
|
|
8039
8252
|
"similarity-match.md",
|
|
@@ -8042,11 +8255,11 @@ var IN_SCOPE_PROMPTS = [
|
|
|
8042
8255
|
var REQUIRED_SECTION_HEADING = "## Handling untrusted input";
|
|
8043
8256
|
var REQUIRED_KEYWORDS = ["UNTRUSTED", "injection-follow", "<XR_"];
|
|
8044
8257
|
function verifyPrompts(repoRoot) {
|
|
8045
|
-
const promptsDir =
|
|
8258
|
+
const promptsDir = join4(repoRoot, "packages/prompts");
|
|
8046
8259
|
const results = [];
|
|
8047
8260
|
for (const filename of IN_SCOPE_PROMPTS) {
|
|
8048
|
-
const path =
|
|
8049
|
-
if (!
|
|
8261
|
+
const path = join4(promptsDir, filename);
|
|
8262
|
+
if (!existsSync5(path)) {
|
|
8050
8263
|
results.push({
|
|
8051
8264
|
ok: false,
|
|
8052
8265
|
message: `${filename}: file missing at packages/prompts/${filename}`
|
|
@@ -8101,8 +8314,8 @@ function frontmatterField(content, field) {
|
|
|
8101
8314
|
return m?.[1] ?? null;
|
|
8102
8315
|
}
|
|
8103
8316
|
function checkGoldenEvalDir(repoRoot) {
|
|
8104
|
-
const root =
|
|
8105
|
-
if (!
|
|
8317
|
+
const root = join8(repoRoot, "fixtures/golden-eval");
|
|
8318
|
+
if (!existsSync9(root))
|
|
8106
8319
|
return [{ ok: false, message: "fixtures/golden-eval/ does not exist" }];
|
|
8107
8320
|
const dirs = readdirSync4(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("."));
|
|
8108
8321
|
const results = [];
|
|
@@ -8113,9 +8326,9 @@ function checkGoldenEvalDir(repoRoot) {
|
|
|
8113
8326
|
});
|
|
8114
8327
|
}
|
|
8115
8328
|
for (const entry of dirs) {
|
|
8116
|
-
const dir =
|
|
8117
|
-
const metaPath =
|
|
8118
|
-
if (!
|
|
8329
|
+
const dir = join8(root, entry.name);
|
|
8330
|
+
const metaPath = join8(dir, "meta.json");
|
|
8331
|
+
if (!existsSync9(metaPath)) {
|
|
8119
8332
|
results.push({ ok: false, message: `${entry.name}: meta.json missing` });
|
|
8120
8333
|
continue;
|
|
8121
8334
|
}
|
|
@@ -8132,12 +8345,12 @@ function checkGoldenEvalDir(repoRoot) {
|
|
|
8132
8345
|
const stages = Array.isArray(meta.stages) ? meta.stages : [];
|
|
8133
8346
|
if (stages.length === 0)
|
|
8134
8347
|
results.push({ ok: false, message: `${entry.name}: meta.stages is empty` });
|
|
8135
|
-
if (!
|
|
8348
|
+
if (!existsSync9(join8(dir, "story.md")))
|
|
8136
8349
|
results.push({ ok: false, message: `${entry.name}: story.md missing` });
|
|
8137
8350
|
for (const stage of stages) {
|
|
8138
8351
|
const required = REQUIRED_FILES_PER_STAGE[stage] ?? [];
|
|
8139
8352
|
for (const rel of required) {
|
|
8140
|
-
if (!
|
|
8353
|
+
if (!existsSync9(join8(dir, rel))) {
|
|
8141
8354
|
results.push({
|
|
8142
8355
|
ok: false,
|
|
8143
8356
|
message: `${meta.id ?? entry.name}: stage "${stage}" declared but ${rel} missing`
|
|
@@ -8149,8 +8362,8 @@ function checkGoldenEvalDir(repoRoot) {
|
|
|
8149
8362
|
return results;
|
|
8150
8363
|
}
|
|
8151
8364
|
function checkRubricPrompt(repoRoot) {
|
|
8152
|
-
const path =
|
|
8153
|
-
if (!
|
|
8365
|
+
const path = join8(repoRoot, "packages/prompts/eval-rubric.md");
|
|
8366
|
+
if (!existsSync9(path))
|
|
8154
8367
|
return [{ ok: false, message: "packages/prompts/eval-rubric.md missing" }];
|
|
8155
8368
|
const text = readFileSync6(path, "utf8");
|
|
8156
8369
|
const id = frontmatterField(text, "id");
|
|
@@ -8162,8 +8375,8 @@ function checkRubricPrompt(repoRoot) {
|
|
|
8162
8375
|
return [];
|
|
8163
8376
|
}
|
|
8164
8377
|
function checkEvalSkill(repoRoot) {
|
|
8165
|
-
const path =
|
|
8166
|
-
if (!
|
|
8378
|
+
const path = join8(repoRoot, "packages/skills/xera-eval.md");
|
|
8379
|
+
if (!existsSync9(path))
|
|
8167
8380
|
return [{ ok: false, message: "packages/skills/xera-eval.md missing" }];
|
|
8168
8381
|
const text = readFileSync6(path, "utf8");
|
|
8169
8382
|
if (!frontmatterField(text, "name"))
|
|
@@ -8174,8 +8387,8 @@ function checkPromptInjectionPreamble(repoRoot) {
|
|
|
8174
8387
|
return verifyPrompts(repoRoot);
|
|
8175
8388
|
}
|
|
8176
8389
|
function checkRootScripts(repoRoot) {
|
|
8177
|
-
const path =
|
|
8178
|
-
if (!
|
|
8390
|
+
const path = join8(repoRoot, "package.json");
|
|
8391
|
+
if (!existsSync9(path))
|
|
8179
8392
|
return [{ ok: false, message: "root package.json missing" }];
|
|
8180
8393
|
const pkg = JSON.parse(readFileSync6(path, "utf8"));
|
|
8181
8394
|
const scripts = pkg.scripts ?? {};
|
|
@@ -8202,8 +8415,8 @@ async function doctorCmd(argv, opts = {}) {
|
|
|
8202
8415
|
if (top)
|
|
8203
8416
|
console.log(` Top skill: ${top[0]} (${top[1].calls} calls, $${top[1].usd.toFixed(2)})`);
|
|
8204
8417
|
}
|
|
8205
|
-
const xeraDir =
|
|
8206
|
-
if (
|
|
8418
|
+
const xeraDir = join8(repoRoot, ".xera");
|
|
8419
|
+
if (existsSync9(xeraDir)) {
|
|
8207
8420
|
const ticketDirs = readdirSync4(xeraDir, { withFileTypes: true }).filter((e) => e.isDirectory() && /^[A-Z]+-\d+$/.test(e.name));
|
|
8208
8421
|
if (ticketDirs.length > 0) {
|
|
8209
8422
|
const events = loadAllEvents(repoRoot);
|
|
@@ -8238,111 +8451,111 @@ async function doctorCmd(argv, opts = {}) {
|
|
|
8238
8451
|
}
|
|
8239
8452
|
|
|
8240
8453
|
// src/bin-internal/eval-deterministic.ts
|
|
8241
|
-
import { existsSync as
|
|
8242
|
-
import { join as
|
|
8454
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
8455
|
+
import { join as join10 } from "path";
|
|
8243
8456
|
import { validateGherkin } from "@xera-ai/web";
|
|
8244
8457
|
|
|
8245
8458
|
// src/eval/paths.ts
|
|
8246
|
-
import { join as
|
|
8459
|
+
import { join as join9 } from "path";
|
|
8247
8460
|
function resolveEvalPaths(cwd, runId) {
|
|
8248
|
-
const root =
|
|
8461
|
+
const root = join9(cwd, ".xera", "eval", runId);
|
|
8249
8462
|
return {
|
|
8250
8463
|
root,
|
|
8251
|
-
manifest:
|
|
8252
|
-
lock:
|
|
8253
|
-
deterministicScores:
|
|
8254
|
-
judgeScores:
|
|
8255
|
-
report:
|
|
8256
|
-
summary:
|
|
8257
|
-
inputsDir:
|
|
8258
|
-
actualDir:
|
|
8259
|
-
ticketInputsDir: (ticket) =>
|
|
8260
|
-
ticketActualDir: (ticket) =>
|
|
8464
|
+
manifest: join9(root, "manifest.json"),
|
|
8465
|
+
lock: join9(root, ".lock"),
|
|
8466
|
+
deterministicScores: join9(root, "deterministic-scores.json"),
|
|
8467
|
+
judgeScores: join9(root, "judge-scores.json"),
|
|
8468
|
+
report: join9(root, "report.md"),
|
|
8469
|
+
summary: join9(root, "summary.json"),
|
|
8470
|
+
inputsDir: join9(root, "inputs"),
|
|
8471
|
+
actualDir: join9(root, "actual"),
|
|
8472
|
+
ticketInputsDir: (ticket) => join9(root, "inputs", ticket),
|
|
8473
|
+
ticketActualDir: (ticket) => join9(root, "actual", ticket)
|
|
8261
8474
|
};
|
|
8262
8475
|
}
|
|
8263
8476
|
|
|
8264
8477
|
// src/eval/types.ts
|
|
8265
|
-
import { z as
|
|
8478
|
+
import { z as z3 } from "zod";
|
|
8266
8479
|
var STAGES = ["feature-from-story", "script-from-feature", "diagnose-failure"];
|
|
8267
|
-
var StageSchema =
|
|
8268
|
-
var VerdictSchema =
|
|
8269
|
-
var PromptVersionsSchema =
|
|
8270
|
-
"feature-from-story":
|
|
8271
|
-
"script-from-feature":
|
|
8272
|
-
"diagnose-failure":
|
|
8273
|
-
"eval-rubric":
|
|
8480
|
+
var StageSchema = z3.enum(STAGES);
|
|
8481
|
+
var VerdictSchema = z3.enum(["PASS", "FAIL", "NA"]);
|
|
8482
|
+
var PromptVersionsSchema = z3.object({
|
|
8483
|
+
"feature-from-story": z3.string(),
|
|
8484
|
+
"script-from-feature": z3.string(),
|
|
8485
|
+
"diagnose-failure": z3.string(),
|
|
8486
|
+
"eval-rubric": z3.string()
|
|
8274
8487
|
});
|
|
8275
|
-
var ManifestSchema =
|
|
8276
|
-
run_id:
|
|
8277
|
-
started_at:
|
|
8278
|
-
git_sha:
|
|
8279
|
-
tickets:
|
|
8280
|
-
stages:
|
|
8281
|
-
ticket_stages:
|
|
8488
|
+
var ManifestSchema = z3.object({
|
|
8489
|
+
run_id: z3.string(),
|
|
8490
|
+
started_at: z3.string(),
|
|
8491
|
+
git_sha: z3.string(),
|
|
8492
|
+
tickets: z3.array(z3.string()).min(1),
|
|
8493
|
+
stages: z3.array(StageSchema).min(1),
|
|
8494
|
+
ticket_stages: z3.record(z3.string(), z3.array(StageSchema).min(1)),
|
|
8282
8495
|
prompt_versions: PromptVersionsSchema,
|
|
8283
|
-
flags:
|
|
8284
|
-
force:
|
|
8496
|
+
flags: z3.object({
|
|
8497
|
+
force: z3.boolean(),
|
|
8285
8498
|
only_prompt: StageSchema.nullable(),
|
|
8286
|
-
only_ticket:
|
|
8287
|
-
judge_only:
|
|
8499
|
+
only_ticket: z3.string().nullable(),
|
|
8500
|
+
judge_only: z3.boolean()
|
|
8288
8501
|
})
|
|
8289
8502
|
});
|
|
8290
|
-
var DimensionSchema =
|
|
8291
|
-
name:
|
|
8503
|
+
var DimensionSchema = z3.object({
|
|
8504
|
+
name: z3.string(),
|
|
8292
8505
|
verdict: VerdictSchema,
|
|
8293
|
-
notes:
|
|
8506
|
+
notes: z3.string()
|
|
8294
8507
|
});
|
|
8295
|
-
var JudgmentSchema =
|
|
8508
|
+
var JudgmentSchema = z3.object({
|
|
8296
8509
|
stage: StageSchema,
|
|
8297
|
-
ticket:
|
|
8298
|
-
dimensions:
|
|
8510
|
+
ticket: z3.string(),
|
|
8511
|
+
dimensions: z3.array(DimensionSchema).min(1)
|
|
8299
8512
|
});
|
|
8300
|
-
var JudgeScoresSchema =
|
|
8301
|
-
run_id:
|
|
8302
|
-
judgments:
|
|
8513
|
+
var JudgeScoresSchema = z3.object({
|
|
8514
|
+
run_id: z3.string(),
|
|
8515
|
+
judgments: z3.array(JudgmentSchema)
|
|
8303
8516
|
});
|
|
8304
|
-
var DeterministicEntrySchema =
|
|
8305
|
-
ticket:
|
|
8517
|
+
var DeterministicEntrySchema = z3.object({
|
|
8518
|
+
ticket: z3.string(),
|
|
8306
8519
|
stage: StageSchema,
|
|
8307
|
-
passed:
|
|
8308
|
-
checks:
|
|
8309
|
-
error:
|
|
8520
|
+
passed: z3.boolean(),
|
|
8521
|
+
checks: z3.array(z3.string()),
|
|
8522
|
+
error: z3.string().optional()
|
|
8310
8523
|
});
|
|
8311
|
-
var DeterministicScoresSchema =
|
|
8312
|
-
run_id:
|
|
8313
|
-
entries:
|
|
8524
|
+
var DeterministicScoresSchema = z3.object({
|
|
8525
|
+
run_id: z3.string(),
|
|
8526
|
+
entries: z3.array(DeterministicEntrySchema)
|
|
8314
8527
|
});
|
|
8315
|
-
var ResultSchema =
|
|
8316
|
-
ticket:
|
|
8528
|
+
var ResultSchema = z3.object({
|
|
8529
|
+
ticket: z3.string(),
|
|
8317
8530
|
stage: StageSchema,
|
|
8318
|
-
deterministic:
|
|
8319
|
-
passed:
|
|
8320
|
-
checks:
|
|
8321
|
-
error:
|
|
8531
|
+
deterministic: z3.object({
|
|
8532
|
+
passed: z3.boolean(),
|
|
8533
|
+
checks: z3.array(z3.string()),
|
|
8534
|
+
error: z3.string().optional()
|
|
8322
8535
|
}),
|
|
8323
|
-
judge:
|
|
8324
|
-
passed:
|
|
8325
|
-
dimensions:
|
|
8326
|
-
score:
|
|
8536
|
+
judge: z3.object({
|
|
8537
|
+
passed: z3.boolean(),
|
|
8538
|
+
dimensions: z3.array(DimensionSchema),
|
|
8539
|
+
score: z3.number().min(0).max(1)
|
|
8327
8540
|
}).nullable(),
|
|
8328
|
-
skipped:
|
|
8541
|
+
skipped: z3.boolean().optional()
|
|
8329
8542
|
});
|
|
8330
|
-
var SummarySchema =
|
|
8331
|
-
run_id:
|
|
8332
|
-
git_sha:
|
|
8543
|
+
var SummarySchema = z3.object({
|
|
8544
|
+
run_id: z3.string(),
|
|
8545
|
+
git_sha: z3.string(),
|
|
8333
8546
|
prompt_versions: PromptVersionsSchema,
|
|
8334
|
-
results:
|
|
8335
|
-
overall:
|
|
8336
|
-
passed:
|
|
8337
|
-
failed:
|
|
8338
|
-
total:
|
|
8339
|
-
score:
|
|
8547
|
+
results: z3.array(ResultSchema),
|
|
8548
|
+
overall: z3.object({
|
|
8549
|
+
passed: z3.number().int().nonnegative(),
|
|
8550
|
+
failed: z3.number().int().nonnegative(),
|
|
8551
|
+
total: z3.number().int().nonnegative(),
|
|
8552
|
+
score: z3.number().min(0).max(1)
|
|
8340
8553
|
})
|
|
8341
8554
|
});
|
|
8342
8555
|
|
|
8343
8556
|
// src/bin-internal/eval-deterministic.ts
|
|
8344
8557
|
function checkFeatureFromStory(actualFeaturePath) {
|
|
8345
|
-
if (!
|
|
8558
|
+
if (!existsSync10(actualFeaturePath)) {
|
|
8346
8559
|
return { passed: false, checks: ["validate-feature"], error: "actual missing: test.feature" };
|
|
8347
8560
|
}
|
|
8348
8561
|
try {
|
|
@@ -8359,23 +8572,23 @@ function checkFeatureFromStory(actualFeaturePath) {
|
|
|
8359
8572
|
}
|
|
8360
8573
|
}
|
|
8361
8574
|
function checkScriptFromFeature(actualTicketDir) {
|
|
8362
|
-
const specPath =
|
|
8363
|
-
if (!
|
|
8575
|
+
const specPath = join10(actualTicketDir, "spec.ts");
|
|
8576
|
+
if (!existsSync10(specPath)) {
|
|
8364
8577
|
return { passed: false, checks: ["file-presence"], error: "actual missing: spec.ts" };
|
|
8365
8578
|
}
|
|
8366
8579
|
return { passed: true, checks: ["file-presence"] };
|
|
8367
8580
|
}
|
|
8368
8581
|
function checkDiagnoseFailure(inputsTicketDir, actualTicketDir) {
|
|
8369
|
-
const inputPath =
|
|
8370
|
-
const actualPath =
|
|
8371
|
-
if (!
|
|
8582
|
+
const inputPath = join10(inputsTicketDir, "classifier-input.json");
|
|
8583
|
+
const actualPath = join10(actualTicketDir, "classification.json");
|
|
8584
|
+
if (!existsSync10(actualPath)) {
|
|
8372
8585
|
return {
|
|
8373
8586
|
passed: false,
|
|
8374
8587
|
checks: ["bucket-match"],
|
|
8375
8588
|
error: "actual missing: classification.json"
|
|
8376
8589
|
};
|
|
8377
8590
|
}
|
|
8378
|
-
if (!
|
|
8591
|
+
if (!existsSync10(inputPath)) {
|
|
8379
8592
|
return {
|
|
8380
8593
|
passed: false,
|
|
8381
8594
|
checks: ["bucket-match"],
|
|
@@ -8413,7 +8626,7 @@ async function evalDeterministicCmd(argv, opts = {}) {
|
|
|
8413
8626
|
return 1;
|
|
8414
8627
|
}
|
|
8415
8628
|
const paths = resolveEvalPaths(cwd, runId);
|
|
8416
|
-
if (!
|
|
8629
|
+
if (!existsSync10(paths.manifest)) {
|
|
8417
8630
|
console.error(`[xera:eval-deterministic] missing manifest.json at ${paths.manifest}`);
|
|
8418
8631
|
return 1;
|
|
8419
8632
|
}
|
|
@@ -8425,7 +8638,7 @@ async function evalDeterministicCmd(argv, opts = {}) {
|
|
|
8425
8638
|
const actualDir = paths.ticketActualDir(ticket);
|
|
8426
8639
|
let result;
|
|
8427
8640
|
if (stage === "feature-from-story") {
|
|
8428
|
-
result = checkFeatureFromStory(
|
|
8641
|
+
result = checkFeatureFromStory(join10(actualDir, "test.feature"));
|
|
8429
8642
|
} else if (stage === "script-from-feature") {
|
|
8430
8643
|
result = checkScriptFromFeature(actualDir);
|
|
8431
8644
|
} else {
|
|
@@ -8452,13 +8665,13 @@ async function evalDeterministicCmd(argv, opts = {}) {
|
|
|
8452
8665
|
// src/bin-internal/eval-prepare.ts
|
|
8453
8666
|
import {
|
|
8454
8667
|
copyFileSync,
|
|
8455
|
-
existsSync as
|
|
8668
|
+
existsSync as existsSync12,
|
|
8456
8669
|
mkdirSync as mkdirSync4,
|
|
8457
8670
|
readdirSync as readdirSync5,
|
|
8458
8671
|
readFileSync as readFileSync9,
|
|
8459
8672
|
writeFileSync as writeFileSync4
|
|
8460
8673
|
} from "fs";
|
|
8461
|
-
import { join as
|
|
8674
|
+
import { join as join11 } from "path";
|
|
8462
8675
|
|
|
8463
8676
|
// src/eval/run-id.ts
|
|
8464
8677
|
import { execSync } from "child_process";
|
|
@@ -8483,11 +8696,11 @@ function generateRunId(opts = {}) {
|
|
|
8483
8696
|
}
|
|
8484
8697
|
|
|
8485
8698
|
// src/lock/file-lock.ts
|
|
8486
|
-
import { existsSync as
|
|
8699
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8487
8700
|
import { hostname } from "os";
|
|
8488
8701
|
import { dirname as dirname2 } from "path";
|
|
8489
8702
|
function acquireLock(path, runId) {
|
|
8490
|
-
if (
|
|
8703
|
+
if (existsSync11(path))
|
|
8491
8704
|
return false;
|
|
8492
8705
|
mkdirSync3(dirname2(path), { recursive: true });
|
|
8493
8706
|
const data = {
|
|
@@ -8504,11 +8717,11 @@ function acquireLock(path, runId) {
|
|
|
8504
8717
|
}
|
|
8505
8718
|
}
|
|
8506
8719
|
function releaseLock(path) {
|
|
8507
|
-
if (
|
|
8720
|
+
if (existsSync11(path))
|
|
8508
8721
|
unlinkSync(path);
|
|
8509
8722
|
}
|
|
8510
8723
|
function readLock(path) {
|
|
8511
|
-
if (!
|
|
8724
|
+
if (!existsSync11(path))
|
|
8512
8725
|
return null;
|
|
8513
8726
|
return JSON.parse(readFileSync8(path, "utf8"));
|
|
8514
8727
|
}
|
|
@@ -8551,16 +8764,16 @@ function parseFlags2(argv) {
|
|
|
8551
8764
|
return flags;
|
|
8552
8765
|
}
|
|
8553
8766
|
function readPromptVersion(repoRoot, name) {
|
|
8554
|
-
const path =
|
|
8555
|
-
if (!
|
|
8767
|
+
const path = join11(repoRoot, "packages/prompts", `${name}.md`);
|
|
8768
|
+
if (!existsSync12(path))
|
|
8556
8769
|
return "0.0.0";
|
|
8557
8770
|
const text = readFileSync9(path, "utf8");
|
|
8558
8771
|
const m = /^version:\s*(\S+)\s*$/m.exec(text);
|
|
8559
8772
|
return m?.[1] ?? "0.0.0";
|
|
8560
8773
|
}
|
|
8561
8774
|
function discoverEvalTickets(repoRoot) {
|
|
8562
|
-
const root =
|
|
8563
|
-
if (!
|
|
8775
|
+
const root = join11(repoRoot, "fixtures/golden-eval");
|
|
8776
|
+
if (!existsSync12(root))
|
|
8564
8777
|
return [];
|
|
8565
8778
|
const out = [];
|
|
8566
8779
|
for (const entry of readdirSync5(root, { withFileTypes: true })) {
|
|
@@ -8568,9 +8781,9 @@ function discoverEvalTickets(repoRoot) {
|
|
|
8568
8781
|
continue;
|
|
8569
8782
|
if (entry.name === "README.md" || entry.name.startsWith("."))
|
|
8570
8783
|
continue;
|
|
8571
|
-
const dir =
|
|
8572
|
-
const metaPath =
|
|
8573
|
-
if (!
|
|
8784
|
+
const dir = join11(root, entry.name);
|
|
8785
|
+
const metaPath = join11(dir, "meta.json");
|
|
8786
|
+
if (!existsSync12(metaPath))
|
|
8574
8787
|
continue;
|
|
8575
8788
|
const meta = JSON.parse(readFileSync9(metaPath, "utf8"));
|
|
8576
8789
|
out.push({ id: meta.id, dir, stages: meta.stages });
|
|
@@ -8578,14 +8791,14 @@ function discoverEvalTickets(repoRoot) {
|
|
|
8578
8791
|
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
8579
8792
|
}
|
|
8580
8793
|
function discoverClassifierTickets(repoRoot) {
|
|
8581
|
-
const root =
|
|
8582
|
-
if (!
|
|
8794
|
+
const root = join11(repoRoot, "fixtures/golden-tickets");
|
|
8795
|
+
if (!existsSync12(root))
|
|
8583
8796
|
return [];
|
|
8584
8797
|
const out = [];
|
|
8585
8798
|
for (const entry of readdirSync5(root, { withFileTypes: true })) {
|
|
8586
8799
|
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
8587
8800
|
continue;
|
|
8588
|
-
const path =
|
|
8801
|
+
const path = join11(root, entry.name);
|
|
8589
8802
|
const data = JSON.parse(readFileSync9(path, "utf8"));
|
|
8590
8803
|
if (typeof data.ticket === "string")
|
|
8591
8804
|
out.push({ id: data.ticket, path });
|
|
@@ -8645,7 +8858,7 @@ async function evalPrepareCmd(argv, opts = {}) {
|
|
|
8645
8858
|
...opts.getGitSha ? { getGitSha: opts.getGitSha } : {}
|
|
8646
8859
|
});
|
|
8647
8860
|
const paths = resolveEvalPaths(repoRoot, runId);
|
|
8648
|
-
if (
|
|
8861
|
+
if (existsSync12(paths.root) && !flags.force) {
|
|
8649
8862
|
console.error(`[xera:eval-prepare] run dir already exists: ${paths.root}. Pass --force to re-run.`);
|
|
8650
8863
|
return 1;
|
|
8651
8864
|
}
|
|
@@ -8657,13 +8870,13 @@ async function evalPrepareCmd(argv, opts = {}) {
|
|
|
8657
8870
|
const evalT = evalTickets.find((t) => t.id === ticket);
|
|
8658
8871
|
const classT = classifierTickets.find((t) => t.id === ticket);
|
|
8659
8872
|
if (evalT) {
|
|
8660
|
-
copyFileSync(
|
|
8661
|
-
const featurePath =
|
|
8662
|
-
if (
|
|
8663
|
-
copyFileSync(featurePath,
|
|
8873
|
+
copyFileSync(join11(evalT.dir, "story.md"), join11(ticketInputs, "story.md"));
|
|
8874
|
+
const featurePath = join11(evalT.dir, "golden/test.feature");
|
|
8875
|
+
if (existsSync12(featurePath))
|
|
8876
|
+
copyFileSync(featurePath, join11(ticketInputs, "test.feature"));
|
|
8664
8877
|
}
|
|
8665
8878
|
if (classT) {
|
|
8666
|
-
copyFileSync(classT.path,
|
|
8879
|
+
copyFileSync(classT.path, join11(ticketInputs, "classifier-input.json"));
|
|
8667
8880
|
}
|
|
8668
8881
|
}
|
|
8669
8882
|
const now = (opts.now ?? (() => new Date))();
|
|
@@ -8699,7 +8912,7 @@ async function evalPrepareCmd(argv, opts = {}) {
|
|
|
8699
8912
|
}
|
|
8700
8913
|
|
|
8701
8914
|
// src/bin-internal/eval-report.ts
|
|
8702
|
-
import { existsSync as
|
|
8915
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
8703
8916
|
function scoreJudgment(j) {
|
|
8704
8917
|
const nonNa = j.dimensions.filter((d) => d.verdict !== "NA");
|
|
8705
8918
|
if (nonNa.length === 0)
|
|
@@ -8754,7 +8967,7 @@ async function evalReportCmd(argv, opts = {}) {
|
|
|
8754
8967
|
return 1;
|
|
8755
8968
|
}
|
|
8756
8969
|
const paths = resolveEvalPaths(cwd, runId);
|
|
8757
|
-
if (!
|
|
8970
|
+
if (!existsSync13(paths.manifest)) {
|
|
8758
8971
|
console.error(`[xera:eval-report] missing manifest.json at ${paths.manifest}`);
|
|
8759
8972
|
return 1;
|
|
8760
8973
|
}
|
|
@@ -8841,40 +9054,77 @@ async function evalReportCmd(argv, opts = {}) {
|
|
|
8841
9054
|
}
|
|
8842
9055
|
|
|
8843
9056
|
// src/bin-internal/exec.ts
|
|
8844
|
-
import { existsSync as
|
|
8845
|
-
import { join as
|
|
9057
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "fs";
|
|
9058
|
+
import { join as join14 } from "path";
|
|
8846
9059
|
import { chromium } from "@playwright/test";
|
|
8847
9060
|
import { runAuthSetup, runPlaywright, stagePlaywrightState } from "@xera-ai/web";
|
|
8848
9061
|
|
|
9062
|
+
// src/artifact/meta.ts
|
|
9063
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
9064
|
+
import { dirname as dirname3 } from "path";
|
|
9065
|
+
import { z as z4 } from "zod";
|
|
9066
|
+
var MetaJsonSchema = z4.object({
|
|
9067
|
+
ticket: z4.string(),
|
|
9068
|
+
adapter: z4.string(),
|
|
9069
|
+
xera_version: z4.string(),
|
|
9070
|
+
prompts_version: z4.string(),
|
|
9071
|
+
fetched_at: z4.string().optional(),
|
|
9072
|
+
story_hash: z4.string().optional(),
|
|
9073
|
+
feature_generated_at: z4.string().optional(),
|
|
9074
|
+
feature_generated_from_story_hash: z4.string().optional(),
|
|
9075
|
+
feature_hash: z4.string().optional(),
|
|
9076
|
+
script_generated_at: z4.string().optional(),
|
|
9077
|
+
script_generated_from_feature_hash: z4.string().optional(),
|
|
9078
|
+
script_warnings: z4.array(z4.string()).optional()
|
|
9079
|
+
});
|
|
9080
|
+
function readMeta(path) {
|
|
9081
|
+
if (!existsSync14(path))
|
|
9082
|
+
return null;
|
|
9083
|
+
return MetaJsonSchema.parse(JSON.parse(readFileSync11(path, "utf8")));
|
|
9084
|
+
}
|
|
9085
|
+
function writeMeta(path, meta) {
|
|
9086
|
+
mkdirSync5(dirname3(path), { recursive: true });
|
|
9087
|
+
writeFileSync6(path, JSON.stringify(meta, null, 2));
|
|
9088
|
+
}
|
|
9089
|
+
function updateMeta(path, patch) {
|
|
9090
|
+
const existing = readMeta(path);
|
|
9091
|
+
if (!existing) {
|
|
9092
|
+
throw new Error(`meta.json not found at ${path}; cannot update`);
|
|
9093
|
+
}
|
|
9094
|
+
const next = { ...existing, ...patch };
|
|
9095
|
+
writeMeta(path, next);
|
|
9096
|
+
return next;
|
|
9097
|
+
}
|
|
9098
|
+
|
|
8849
9099
|
// src/artifact/paths.ts
|
|
8850
|
-
import { join as
|
|
9100
|
+
import { join as join12 } from "path";
|
|
8851
9101
|
var TICKET_RE = /^[A-Z][A-Z0-9_]*-\d+$|^SAMPLE-\d+$/;
|
|
8852
9102
|
function resolveArtifactPaths(repoRoot, ticket) {
|
|
8853
9103
|
if (!TICKET_RE.test(ticket)) {
|
|
8854
9104
|
throw new Error(`Invalid ticket key: "${ticket}" (expected e.g. JIRA-123 or SAMPLE-001)`);
|
|
8855
9105
|
}
|
|
8856
|
-
const ticketDir =
|
|
9106
|
+
const ticketDir = join12(repoRoot, ".xera", ticket);
|
|
8857
9107
|
return {
|
|
8858
9108
|
ticketDir,
|
|
8859
|
-
storyPath:
|
|
8860
|
-
featurePath:
|
|
8861
|
-
specPath:
|
|
8862
|
-
pageObjectsDir:
|
|
8863
|
-
runsDir:
|
|
8864
|
-
metaPath:
|
|
8865
|
-
statusPath:
|
|
8866
|
-
logPath:
|
|
8867
|
-
lockPath:
|
|
8868
|
-
authDir:
|
|
9109
|
+
storyPath: join12(ticketDir, "story.md"),
|
|
9110
|
+
featurePath: join12(ticketDir, "test.feature"),
|
|
9111
|
+
specPath: join12(ticketDir, "spec.ts"),
|
|
9112
|
+
pageObjectsDir: join12(ticketDir, "page-objects"),
|
|
9113
|
+
runsDir: join12(ticketDir, "runs"),
|
|
9114
|
+
metaPath: join12(ticketDir, "meta.json"),
|
|
9115
|
+
statusPath: join12(ticketDir, "status.json"),
|
|
9116
|
+
logPath: join12(ticketDir, "xera.log"),
|
|
9117
|
+
lockPath: join12(ticketDir, ".lock"),
|
|
9118
|
+
authDir: join12(repoRoot, ".xera", ".auth"),
|
|
8869
9119
|
runPath: (runId) => {
|
|
8870
|
-
const runDir =
|
|
9120
|
+
const runDir = join12(ticketDir, "runs", runId);
|
|
8871
9121
|
return {
|
|
8872
9122
|
runDir,
|
|
8873
|
-
reportJsonPath:
|
|
8874
|
-
tracePath:
|
|
8875
|
-
normalizedPath:
|
|
8876
|
-
screenshotsDir:
|
|
8877
|
-
videoDir:
|
|
9123
|
+
reportJsonPath: join12(runDir, "report.json"),
|
|
9124
|
+
tracePath: join12(runDir, "trace.zip"),
|
|
9125
|
+
normalizedPath: join12(runDir, "normalized.json"),
|
|
9126
|
+
screenshotsDir: join12(runDir, "screenshots"),
|
|
9127
|
+
videoDir: join12(runDir, "videos")
|
|
8878
9128
|
};
|
|
8879
9129
|
}
|
|
8880
9130
|
};
|
|
@@ -8912,9 +9162,9 @@ function needsRefresh(entry, policy, now = new Date) {
|
|
|
8912
9162
|
}
|
|
8913
9163
|
|
|
8914
9164
|
// src/auth/state.ts
|
|
8915
|
-
import { existsSync as
|
|
8916
|
-
import { join as
|
|
8917
|
-
import { z as
|
|
9165
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
9166
|
+
import { join as join13 } from "path";
|
|
9167
|
+
import { z as z5 } from "zod";
|
|
8918
9168
|
|
|
8919
9169
|
// src/auth/encrypt.ts
|
|
8920
9170
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
@@ -8971,123 +9221,39 @@ function resolveAuthKey() {
|
|
|
8971
9221
|
}
|
|
8972
9222
|
|
|
8973
9223
|
// src/auth/state.ts
|
|
8974
|
-
var AuthStateEntrySchema =
|
|
8975
|
-
role:
|
|
8976
|
-
strategy:
|
|
8977
|
-
created_at:
|
|
8978
|
-
expires_at:
|
|
8979
|
-
payload:
|
|
9224
|
+
var AuthStateEntrySchema = z5.object({
|
|
9225
|
+
role: z5.string(),
|
|
9226
|
+
strategy: z5.enum(["storageState", "apiToken"]),
|
|
9227
|
+
created_at: z5.string(),
|
|
9228
|
+
expires_at: z5.string(),
|
|
9229
|
+
payload: z5.record(z5.string(), z5.unknown())
|
|
8980
9230
|
});
|
|
8981
9231
|
function pathFor(authDir, role) {
|
|
8982
|
-
return
|
|
9232
|
+
return join13(authDir, `${role}.json`);
|
|
8983
9233
|
}
|
|
8984
9234
|
function writeAuthState(authDir, entry) {
|
|
8985
|
-
|
|
9235
|
+
mkdirSync6(authDir, { recursive: true });
|
|
8986
9236
|
const ct = encrypt(JSON.stringify(entry), resolveAuthKey());
|
|
8987
|
-
|
|
9237
|
+
writeFileSync7(pathFor(authDir, entry.role), ct);
|
|
8988
9238
|
}
|
|
8989
9239
|
function readAuthState(authDir, role) {
|
|
8990
9240
|
const p = pathFor(authDir, role);
|
|
8991
|
-
if (!
|
|
9241
|
+
if (!existsSync15(p))
|
|
8992
9242
|
return null;
|
|
8993
|
-
const txt =
|
|
9243
|
+
const txt = readFileSync12(p, "utf8");
|
|
8994
9244
|
const plain = decrypt(txt, resolveAuthKey());
|
|
8995
9245
|
return AuthStateEntrySchema.parse(JSON.parse(plain));
|
|
8996
9246
|
}
|
|
8997
9247
|
|
|
8998
|
-
// src/config/load.ts
|
|
8999
|
-
import { existsSync as existsSync13 } from "fs";
|
|
9000
|
-
import { join as join12 } from "path";
|
|
9001
|
-
import { pathToFileURL } from "url";
|
|
9002
|
-
|
|
9003
|
-
// src/config/schema.ts
|
|
9004
|
-
import { z as z4 } from "zod";
|
|
9005
|
-
var AuthRoleSchema = z4.object({
|
|
9006
|
-
envEmail: z4.string().min(1),
|
|
9007
|
-
envPassword: z4.string().min(1)
|
|
9008
|
-
});
|
|
9009
|
-
var AuthSchema = z4.object({
|
|
9010
|
-
strategy: z4.enum(["storageState", "apiToken", "none"]).default("none"),
|
|
9011
|
-
ttl: z4.string().default("8h"),
|
|
9012
|
-
refreshBuffer: z4.string().default("30m"),
|
|
9013
|
-
setupScript: z4.string().optional(),
|
|
9014
|
-
roles: z4.record(z4.string(), AuthRoleSchema).default({})
|
|
9015
|
-
});
|
|
9016
|
-
var WebSchema = z4.object({
|
|
9017
|
-
baseUrl: z4.record(z4.string(), z4.string().url()).refine((m) => Object.keys(m).length > 0, {
|
|
9018
|
-
message: "baseUrl must have at least one environment"
|
|
9019
|
-
}),
|
|
9020
|
-
defaultEnv: z4.string(),
|
|
9021
|
-
auth: AuthSchema.prefault({}),
|
|
9022
|
-
testData: z4.object({
|
|
9023
|
-
users: z4.record(z4.string(), z4.object({ fromAuth: z4.string() })).default({})
|
|
9024
|
-
}).prefault({})
|
|
9025
|
-
}).refine((w) => w.baseUrl[w.defaultEnv] !== undefined, {
|
|
9026
|
-
message: "defaultEnv must exist in baseUrl map",
|
|
9027
|
-
path: ["defaultEnv"]
|
|
9028
|
-
});
|
|
9029
|
-
var JiraSchema = z4.object({
|
|
9030
|
-
baseUrl: z4.string().url(),
|
|
9031
|
-
projectKeys: z4.array(z4.string().min(1)).min(1),
|
|
9032
|
-
fields: z4.object({
|
|
9033
|
-
story: z4.string().min(1),
|
|
9034
|
-
acceptanceCriteria: z4.string().optional(),
|
|
9035
|
-
attachments: z4.string().default("attachment")
|
|
9036
|
-
})
|
|
9037
|
-
});
|
|
9038
|
-
var AISchema = z4.object({
|
|
9039
|
-
livePageSnapshot: z4.boolean().default(true),
|
|
9040
|
-
confidenceThreshold: z4.enum(["low", "medium", "high"]).default("medium"),
|
|
9041
|
-
maxRetries: z4.object({
|
|
9042
|
-
typecheck: z4.number().int().min(0).max(5).default(2),
|
|
9043
|
-
lint: z4.number().int().min(0).max(5).default(2),
|
|
9044
|
-
validateFeature: z4.number().int().min(0).max(5).default(2)
|
|
9045
|
-
}).prefault({})
|
|
9046
|
-
}).prefault({});
|
|
9047
|
-
var ReportingSchema = z4.object({
|
|
9048
|
-
language: z4.enum(["en", "vi"]).default("en"),
|
|
9049
|
-
postToJira: z4.boolean().default(true),
|
|
9050
|
-
transition: z4.object({
|
|
9051
|
-
onPass: z4.string().nullable().default(null),
|
|
9052
|
-
onFail: z4.string().nullable().default(null)
|
|
9053
|
-
}).prefault({}),
|
|
9054
|
-
artifactLinks: z4.enum(["git", "local"]).default("git")
|
|
9055
|
-
}).prefault({});
|
|
9056
|
-
var RunSchema = z4.object({
|
|
9057
|
-
autoImpact: z4.object({
|
|
9058
|
-
enabled: z4.boolean().default(true),
|
|
9059
|
-
threshold: z4.number().nonnegative().default(8)
|
|
9060
|
-
}).prefault({})
|
|
9061
|
-
}).prefault({});
|
|
9062
|
-
var XeraConfigSchema = z4.object({
|
|
9063
|
-
jira: JiraSchema,
|
|
9064
|
-
web: WebSchema,
|
|
9065
|
-
ai: AISchema,
|
|
9066
|
-
reporting: ReportingSchema,
|
|
9067
|
-
run: RunSchema.prefault({}),
|
|
9068
|
-
adapters: z4.array(z4.string().min(1)).min(1).default(["web"])
|
|
9069
|
-
});
|
|
9070
|
-
|
|
9071
|
-
// src/config/load.ts
|
|
9072
|
-
async function loadConfig(cwd) {
|
|
9073
|
-
const path = join12(cwd, "xera.config.ts");
|
|
9074
|
-
if (!existsSync13(path)) {
|
|
9075
|
-
throw new Error(`xera.config.ts not found in ${cwd}`);
|
|
9076
|
-
}
|
|
9077
|
-
const mod = await import(pathToFileURL(path).href);
|
|
9078
|
-
const raw = mod.default ?? mod;
|
|
9079
|
-
return XeraConfigSchema.parse(raw);
|
|
9080
|
-
}
|
|
9081
|
-
|
|
9082
9248
|
// src/logging/ndjson-logger.ts
|
|
9083
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
9084
|
-
import { dirname as
|
|
9249
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync13 } from "fs";
|
|
9250
|
+
import { dirname as dirname4 } from "path";
|
|
9085
9251
|
|
|
9086
9252
|
class NdjsonLogger {
|
|
9087
9253
|
path;
|
|
9088
9254
|
constructor(path) {
|
|
9089
9255
|
this.path = path;
|
|
9090
|
-
|
|
9256
|
+
mkdirSync7(dirname4(path), { recursive: true });
|
|
9091
9257
|
}
|
|
9092
9258
|
log(payload) {
|
|
9093
9259
|
const entry = { ts: new Date().toISOString(), ...payload };
|
|
@@ -9095,9 +9261,9 @@ class NdjsonLogger {
|
|
|
9095
9261
|
`);
|
|
9096
9262
|
}
|
|
9097
9263
|
static readAll(path) {
|
|
9098
|
-
if (!
|
|
9264
|
+
if (!existsSync16(path))
|
|
9099
9265
|
return [];
|
|
9100
|
-
const txt =
|
|
9266
|
+
const txt = readFileSync13(path, "utf8").trim();
|
|
9101
9267
|
if (!txt)
|
|
9102
9268
|
return [];
|
|
9103
9269
|
return txt.split(`
|
|
@@ -9132,14 +9298,41 @@ async function execCmd(argv) {
|
|
|
9132
9298
|
}
|
|
9133
9299
|
const t0 = Date.now();
|
|
9134
9300
|
try {
|
|
9135
|
-
|
|
9301
|
+
const meta = readMeta(paths.metaPath);
|
|
9302
|
+
const adapter = meta?.adapter ?? "web";
|
|
9303
|
+
if (adapter === "http") {
|
|
9304
|
+
if (!config.http) {
|
|
9305
|
+
throw new Error("http adapter requires http config block");
|
|
9306
|
+
}
|
|
9307
|
+
const env = process.env["XERA_ENV"] ?? config.http.defaultEnv;
|
|
9308
|
+
const { HttpAdapter } = await import("@xera-ai/http");
|
|
9309
|
+
const result = await HttpAdapter.execute({
|
|
9310
|
+
ticketDir: paths.ticketDir,
|
|
9311
|
+
config,
|
|
9312
|
+
runId,
|
|
9313
|
+
env
|
|
9314
|
+
});
|
|
9315
|
+
log.log({
|
|
9316
|
+
step: "exec.complete",
|
|
9317
|
+
runId,
|
|
9318
|
+
outcome: result.outcome,
|
|
9319
|
+
elapsedMs: Date.now() - t0
|
|
9320
|
+
});
|
|
9321
|
+
console.log(`[xera:exec] runId=${runId} outcome=${result.outcome}`);
|
|
9322
|
+
return result.outcome === "PASS" ? 0 : 3;
|
|
9323
|
+
}
|
|
9324
|
+
if (!config.web) {
|
|
9325
|
+
throw new Error("web adapter requires web config block");
|
|
9326
|
+
}
|
|
9327
|
+
const webConfig = config.web;
|
|
9328
|
+
if (webConfig.auth.strategy === "storageState" && webConfig.auth.setupScript) {
|
|
9136
9329
|
const browser = await chromium.launch();
|
|
9137
9330
|
try {
|
|
9138
|
-
for (const [roleName, roleCreds] of Object.entries(
|
|
9331
|
+
for (const [roleName, roleCreds] of Object.entries(webConfig.auth.roles)) {
|
|
9139
9332
|
const entry = readAuthState(paths.authDir, roleName);
|
|
9140
9333
|
if (needsRefresh(entry, {
|
|
9141
|
-
ttl:
|
|
9142
|
-
refreshBuffer:
|
|
9334
|
+
ttl: webConfig.auth.ttl,
|
|
9335
|
+
refreshBuffer: webConfig.auth.refreshBuffer
|
|
9143
9336
|
})) {
|
|
9144
9337
|
const email = process.env[roleCreds.envEmail];
|
|
9145
9338
|
const password = process.env[roleCreds.envPassword];
|
|
@@ -9150,7 +9343,7 @@ async function execCmd(argv) {
|
|
|
9150
9343
|
await runAuthSetup({
|
|
9151
9344
|
role: roleName,
|
|
9152
9345
|
creds: { email, password },
|
|
9153
|
-
setupScriptPath:
|
|
9346
|
+
setupScriptPath: join14(cwd, webConfig.auth.setupScript),
|
|
9154
9347
|
authDir: paths.authDir,
|
|
9155
9348
|
browser
|
|
9156
9349
|
});
|
|
@@ -9161,23 +9354,23 @@ async function execCmd(argv) {
|
|
|
9161
9354
|
await browser.close();
|
|
9162
9355
|
}
|
|
9163
9356
|
}
|
|
9164
|
-
if (
|
|
9165
|
-
for (const roleName of Object.keys(
|
|
9357
|
+
if (webConfig.auth.strategy === "storageState") {
|
|
9358
|
+
for (const roleName of Object.keys(webConfig.auth.roles)) {
|
|
9166
9359
|
if (readAuthState(paths.authDir, roleName)) {
|
|
9167
9360
|
stagePlaywrightState(paths.authDir, roleName);
|
|
9168
9361
|
}
|
|
9169
9362
|
}
|
|
9170
9363
|
}
|
|
9171
|
-
const cfgPath =
|
|
9172
|
-
if (!
|
|
9364
|
+
const cfgPath = join14(cwd, "playwright.config.ts");
|
|
9365
|
+
if (!existsSync17(cfgPath)) {
|
|
9173
9366
|
console.error(`[xera:exec] missing ${cfgPath}. Run \`xera init\` to scaffold it, then re-run.`);
|
|
9174
9367
|
return 1;
|
|
9175
9368
|
}
|
|
9176
9369
|
const runDir = paths.runPath(runId).runDir;
|
|
9177
|
-
|
|
9178
|
-
const envName = process.env.XERA_ENV ??
|
|
9179
|
-
const baseURL =
|
|
9180
|
-
const reportJsonPath =
|
|
9370
|
+
mkdirSync8(runDir, { recursive: true });
|
|
9371
|
+
const envName = process.env.XERA_ENV ?? webConfig.defaultEnv;
|
|
9372
|
+
const baseURL = webConfig.baseUrl[envName] ?? webConfig.baseUrl[webConfig.defaultEnv];
|
|
9373
|
+
const reportJsonPath = join14(runDir, "report.json");
|
|
9181
9374
|
log.log({ step: "exec.start", runId, env: envName, baseURL });
|
|
9182
9375
|
const r = await runPlaywright({
|
|
9183
9376
|
specPath: paths.specPath,
|
|
@@ -9204,83 +9397,46 @@ import { dirname as dirname5 } from "path";
|
|
|
9204
9397
|
|
|
9205
9398
|
// src/artifact/hash.ts
|
|
9206
9399
|
import { createHash as createHash4 } from "crypto";
|
|
9207
|
-
import { existsSync as
|
|
9400
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
|
|
9208
9401
|
function hashString(s) {
|
|
9209
9402
|
return `sha256:${createHash4("sha256").update(s).digest("hex")}`;
|
|
9210
9403
|
}
|
|
9211
9404
|
function hashFile(path) {
|
|
9212
|
-
return hashString(
|
|
9405
|
+
return hashString(readFileSync14(path, "utf8"));
|
|
9213
9406
|
}
|
|
9214
9407
|
function hashFileIfExists(path) {
|
|
9215
|
-
if (!
|
|
9408
|
+
if (!existsSync18(path))
|
|
9216
9409
|
return null;
|
|
9217
9410
|
return hashFile(path);
|
|
9218
9411
|
}
|
|
9219
9412
|
|
|
9220
|
-
// src/artifact/meta.ts
|
|
9221
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
9222
|
-
import { dirname as dirname4 } from "path";
|
|
9223
|
-
import { z as z5 } from "zod";
|
|
9224
|
-
var MetaJsonSchema = z5.object({
|
|
9225
|
-
ticket: z5.string(),
|
|
9226
|
-
adapter: z5.string(),
|
|
9227
|
-
xera_version: z5.string(),
|
|
9228
|
-
prompts_version: z5.string(),
|
|
9229
|
-
fetched_at: z5.string().optional(),
|
|
9230
|
-
story_hash: z5.string().optional(),
|
|
9231
|
-
feature_generated_at: z5.string().optional(),
|
|
9232
|
-
feature_generated_from_story_hash: z5.string().optional(),
|
|
9233
|
-
feature_hash: z5.string().optional(),
|
|
9234
|
-
script_generated_at: z5.string().optional(),
|
|
9235
|
-
script_generated_from_feature_hash: z5.string().optional(),
|
|
9236
|
-
script_warnings: z5.array(z5.string()).optional()
|
|
9237
|
-
});
|
|
9238
|
-
function readMeta(path) {
|
|
9239
|
-
if (!existsSync17(path))
|
|
9240
|
-
return null;
|
|
9241
|
-
return MetaJsonSchema.parse(JSON.parse(readFileSync14(path, "utf8")));
|
|
9242
|
-
}
|
|
9243
|
-
function writeMeta(path, meta) {
|
|
9244
|
-
mkdirSync8(dirname4(path), { recursive: true });
|
|
9245
|
-
writeFileSync7(path, JSON.stringify(meta, null, 2));
|
|
9246
|
-
}
|
|
9247
|
-
function updateMeta(path, patch) {
|
|
9248
|
-
const existing = readMeta(path);
|
|
9249
|
-
if (!existing) {
|
|
9250
|
-
throw new Error(`meta.json not found at ${path}; cannot update`);
|
|
9251
|
-
}
|
|
9252
|
-
const next = { ...existing, ...patch };
|
|
9253
|
-
writeMeta(path, next);
|
|
9254
|
-
return next;
|
|
9255
|
-
}
|
|
9256
|
-
|
|
9257
9413
|
// src/jira/mcp-backend.ts
|
|
9258
|
-
import { existsSync as
|
|
9414
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
9259
9415
|
import { tmpdir } from "os";
|
|
9260
|
-
import { join as
|
|
9416
|
+
import { join as join15 } from "path";
|
|
9261
9417
|
var MCP_ENV = "XERA_MCP_JIRA";
|
|
9262
9418
|
async function createMcpBackend(_baseUrl) {
|
|
9263
9419
|
if (process.env[MCP_ENV] !== "1")
|
|
9264
9420
|
return null;
|
|
9265
|
-
const tmpDir =
|
|
9421
|
+
const tmpDir = join15(tmpdir(), "xera-mcp");
|
|
9266
9422
|
mkdirSync9(tmpDir, { recursive: true });
|
|
9267
9423
|
return {
|
|
9268
9424
|
backend: "mcp",
|
|
9269
9425
|
async fetchTicket(key, _fields) {
|
|
9270
|
-
const cachePath =
|
|
9271
|
-
if (!
|
|
9426
|
+
const cachePath = join15(tmpDir, `${key}.json`);
|
|
9427
|
+
if (!existsSync19(cachePath)) {
|
|
9272
9428
|
throw new Error(`MCP-mode fetch requires the skill to first call mcp__atlassian__getJiraIssue and write ${cachePath}. ` + `If you are running this directly, unset ${MCP_ENV} to use REST.`);
|
|
9273
9429
|
}
|
|
9274
9430
|
const parsed = JSON.parse(readFileSync15(cachePath, "utf8"));
|
|
9275
9431
|
return parsed;
|
|
9276
9432
|
},
|
|
9277
9433
|
async postComment(key, body) {
|
|
9278
|
-
const outPath =
|
|
9434
|
+
const outPath = join15(tmpDir, `${key}.comment.json`);
|
|
9279
9435
|
writeFileSync8(outPath, JSON.stringify({ key, body }));
|
|
9280
9436
|
return { id: "mcp-pending" };
|
|
9281
9437
|
},
|
|
9282
9438
|
async transitionStatus(key, statusName) {
|
|
9283
|
-
const outPath =
|
|
9439
|
+
const outPath = join15(tmpDir, `${key}.transition.json`);
|
|
9284
9440
|
writeFileSync8(outPath, JSON.stringify({ key, statusName }));
|
|
9285
9441
|
},
|
|
9286
9442
|
async listFields(_sampleKey) {
|
|
@@ -9455,8 +9611,8 @@ init_graph_backfill();
|
|
|
9455
9611
|
// src/graph/enrich.ts
|
|
9456
9612
|
init_store();
|
|
9457
9613
|
init_ulid();
|
|
9458
|
-
import { existsSync as
|
|
9459
|
-
import { join as
|
|
9614
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16 } from "fs";
|
|
9615
|
+
import { join as join16 } from "path";
|
|
9460
9616
|
import { z as z6 } from "zod";
|
|
9461
9617
|
var MAX_SIMILAR_EDGES = 10;
|
|
9462
9618
|
var MIN_CONFIDENCE = 0.7;
|
|
@@ -9478,8 +9634,8 @@ var mk2 = (actor, type, payload) => ({
|
|
|
9478
9634
|
payload
|
|
9479
9635
|
});
|
|
9480
9636
|
async function enrichTicket(repoRoot, ticketId, opts) {
|
|
9481
|
-
const inputPath =
|
|
9482
|
-
if (!
|
|
9637
|
+
const inputPath = join16(repoRoot, ".xera", ticketId, "enrichment-input.json");
|
|
9638
|
+
if (!existsSync20(inputPath)) {
|
|
9483
9639
|
throw new Error(`enrichment-input.json not found at ${inputPath}`);
|
|
9484
9640
|
}
|
|
9485
9641
|
const raw = JSON.parse(readFileSync16(inputPath, "utf8"));
|
|
@@ -9592,11 +9748,11 @@ init_graph_record();
|
|
|
9592
9748
|
|
|
9593
9749
|
// src/bin-internal/graph-render.ts
|
|
9594
9750
|
import { mkdirSync as mkdirSync11, renameSync as renameSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
9595
|
-
import { dirname as dirname7, join as
|
|
9751
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
9596
9752
|
|
|
9597
9753
|
// src/graph/render.ts
|
|
9598
9754
|
import { readFileSync as readFileSync17 } from "fs";
|
|
9599
|
-
import { dirname as dirname6, join as
|
|
9755
|
+
import { dirname as dirname6, join as join17 } from "path";
|
|
9600
9756
|
import { fileURLToPath } from "url";
|
|
9601
9757
|
var COLORS = {
|
|
9602
9758
|
ticket: "#3B82F6",
|
|
@@ -9815,9 +9971,9 @@ function transformForVisNetwork(snap, opts) {
|
|
|
9815
9971
|
}
|
|
9816
9972
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
9817
9973
|
var __dirname2 = dirname6(__filename2);
|
|
9818
|
-
var TEMPLATES_DIR =
|
|
9974
|
+
var TEMPLATES_DIR = join17(__dirname2, "templates");
|
|
9819
9975
|
function loadTemplate(name) {
|
|
9820
|
-
return readFileSync17(
|
|
9976
|
+
return readFileSync17(join17(TEMPLATES_DIR, name), "utf8");
|
|
9821
9977
|
}
|
|
9822
9978
|
function statsToHuman(s) {
|
|
9823
9979
|
return `${s.tickets} tickets \xB7 ${s.scenarios} scenarios \xB7 ${s.poms} POMs \xB7 ${s.edges} edges`;
|
|
@@ -9863,7 +10019,7 @@ async function graphRenderCmd(argv) {
|
|
|
9863
10019
|
depth = parseDepth(argv[++i]);
|
|
9864
10020
|
}
|
|
9865
10021
|
const repoRoot = process.cwd();
|
|
9866
|
-
const finalPath = outPath ??
|
|
10022
|
+
const finalPath = outPath ?? join18(repoRoot, ".xera/graph.html");
|
|
9867
10023
|
const snap = deriveSnapshot(loadAllEvents(repoRoot));
|
|
9868
10024
|
const totalNodeCount = Object.keys(snap.tickets).length + Object.keys(snap.scenarios).length + Object.keys(snap.poms).length + Object.keys(snap.areas).length;
|
|
9869
10025
|
const performanceMode = decidePerformanceMode(totalNodeCount);
|
|
@@ -9915,8 +10071,8 @@ async function graphSnapshotCmd(argv) {
|
|
|
9915
10071
|
}
|
|
9916
10072
|
|
|
9917
10073
|
// src/bin-internal/heal-prepare.ts
|
|
9918
|
-
import { existsSync as
|
|
9919
|
-
import { join as
|
|
10074
|
+
import { existsSync as existsSync21, readdirSync as readdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
|
|
10075
|
+
import { join as join19 } from "path";
|
|
9920
10076
|
import { scrubFreeText } from "@xera-ai/web";
|
|
9921
10077
|
|
|
9922
10078
|
// ../../node_modules/.bun/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
@@ -10344,7 +10500,7 @@ function classifyKind(raw) {
|
|
|
10344
10500
|
return "other";
|
|
10345
10501
|
}
|
|
10346
10502
|
function extractDomSnapshot(tracePath) {
|
|
10347
|
-
if (!
|
|
10503
|
+
if (!existsSync21(tracePath))
|
|
10348
10504
|
return "";
|
|
10349
10505
|
const buf = readFileSync18(tracePath);
|
|
10350
10506
|
const entries = unzipSync(buf);
|
|
@@ -10394,12 +10550,12 @@ function extractDomSnapshot(tracePath) {
|
|
|
10394
10550
|
return scrubFreeText(html);
|
|
10395
10551
|
}
|
|
10396
10552
|
function findPomLine(ticketDir, rawLocator) {
|
|
10397
|
-
const pomDir =
|
|
10553
|
+
const pomDir = join19(ticketDir, "page-objects");
|
|
10398
10554
|
const candidates = [];
|
|
10399
|
-
if (
|
|
10555
|
+
if (existsSync21(pomDir)) {
|
|
10400
10556
|
for (const name of readdirSync6(pomDir)) {
|
|
10401
10557
|
if (name.endsWith(".ts"))
|
|
10402
|
-
candidates.push(
|
|
10558
|
+
candidates.push(join19(pomDir, name));
|
|
10403
10559
|
}
|
|
10404
10560
|
}
|
|
10405
10561
|
for (const file of candidates) {
|
|
@@ -10441,13 +10597,13 @@ function findGherkinStep(featureText, rawLocator) {
|
|
|
10441
10597
|
}
|
|
10442
10598
|
function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
10443
10599
|
const paths = resolveArtifactPaths(repoRoot, ticket);
|
|
10444
|
-
const classifierPath =
|
|
10600
|
+
const classifierPath = join19(paths.ticketDir, "classifier-input.json");
|
|
10445
10601
|
const classifier = JSON.parse(readFileSync18(classifierPath, "utf8"));
|
|
10446
10602
|
const cls = classifier.scenarios.find((s) => s.name === scenarioName);
|
|
10447
10603
|
if (!cls)
|
|
10448
10604
|
throw new Error(`scenario not found in classifier-input: "${scenarioName}"`);
|
|
10449
|
-
const runDir =
|
|
10450
|
-
const normalized = JSON.parse(readFileSync18(
|
|
10605
|
+
const runDir = join19(paths.runsDir, runId);
|
|
10606
|
+
const normalized = JSON.parse(readFileSync18(join19(runDir, "normalized.json"), "utf8"));
|
|
10451
10607
|
const normSc = normalized.scenarios.find((s) => s.name === scenarioName);
|
|
10452
10608
|
if (!normSc?.failure)
|
|
10453
10609
|
throw new Error(`no failure recorded for scenario "${scenarioName}"`);
|
|
@@ -10460,7 +10616,7 @@ function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
|
10460
10616
|
const pomLoc = findPomLine(paths.ticketDir, raw);
|
|
10461
10617
|
const featureText = readFileSync18(paths.featurePath, "utf8");
|
|
10462
10618
|
const gherkinStep = findGherkinStep(featureText, raw);
|
|
10463
|
-
const domSnapshotAtFailure = extractDomSnapshot(
|
|
10619
|
+
const domSnapshotAtFailure = extractDomSnapshot(join19(runDir, "trace.zip"));
|
|
10464
10620
|
return {
|
|
10465
10621
|
ticket,
|
|
10466
10622
|
runId,
|
|
@@ -10480,7 +10636,7 @@ async function healPrepareCmd(argv) {
|
|
|
10480
10636
|
try {
|
|
10481
10637
|
const result = healPrepare(process.cwd(), ticket, runId, scenarioName);
|
|
10482
10638
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
10483
|
-
const outPath =
|
|
10639
|
+
const outPath = join19(paths.runsDir, runId, "heal-input.json");
|
|
10484
10640
|
writeFileSync11(outPath, JSON.stringify(result, null, 2));
|
|
10485
10641
|
console.log(`[xera:heal-prepare] wrote ${outPath}`);
|
|
10486
10642
|
return 0;
|
|
@@ -10492,7 +10648,7 @@ async function healPrepareCmd(argv) {
|
|
|
10492
10648
|
|
|
10493
10649
|
// src/bin-internal/impact-prepare.ts
|
|
10494
10650
|
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
10495
|
-
import { join as
|
|
10651
|
+
import { join as join20 } from "path";
|
|
10496
10652
|
|
|
10497
10653
|
// src/graph/impact.ts
|
|
10498
10654
|
var PRIORITY_WEIGHT = { p0: 3, p1: 2, p2: 1 };
|
|
@@ -10742,11 +10898,11 @@ async function impactPrepareCmd(argv) {
|
|
|
10742
10898
|
scenarios,
|
|
10743
10899
|
generatedAt: new Date().toISOString()
|
|
10744
10900
|
};
|
|
10745
|
-
const impactDir =
|
|
10901
|
+
const impactDir = join20(repoRoot, ".xera/impact");
|
|
10746
10902
|
mkdirSync12(impactDir, { recursive: true });
|
|
10747
|
-
writeFileSync12(
|
|
10903
|
+
writeFileSync12(join20(impactDir, `${ticket}.json`), JSON.stringify(report, null, 2));
|
|
10748
10904
|
if (!quiet) {
|
|
10749
|
-
writeFileSync12(
|
|
10905
|
+
writeFileSync12(join20(impactDir, `${ticket}.md`), renderImpactMarkdown(report));
|
|
10750
10906
|
}
|
|
10751
10907
|
return 0;
|
|
10752
10908
|
}
|
|
@@ -10771,9 +10927,8 @@ async function lintCmd(argv) {
|
|
|
10771
10927
|
}
|
|
10772
10928
|
|
|
10773
10929
|
// src/bin-internal/normalize.ts
|
|
10774
|
-
import { existsSync as
|
|
10775
|
-
import { join as
|
|
10776
|
-
import { normalizeRun } from "@xera-ai/web";
|
|
10930
|
+
import { existsSync as existsSync22, readdirSync as readdirSync7 } from "fs";
|
|
10931
|
+
import { join as join21 } from "path";
|
|
10777
10932
|
async function normalizeCmd(argv) {
|
|
10778
10933
|
const ticket = argv[0];
|
|
10779
10934
|
if (!ticket) {
|
|
@@ -10787,22 +10942,31 @@ async function normalizeCmd(argv) {
|
|
|
10787
10942
|
console.error("[xera:normalize] no run found");
|
|
10788
10943
|
return 1;
|
|
10789
10944
|
}
|
|
10790
|
-
const runDir =
|
|
10791
|
-
if (!
|
|
10945
|
+
const runDir = join21(paths.runsDir, runId);
|
|
10946
|
+
if (!existsSync22(runDir)) {
|
|
10792
10947
|
console.error(`[xera:normalize] runs/${runId} missing`);
|
|
10793
10948
|
return 1;
|
|
10794
10949
|
}
|
|
10950
|
+
const meta = readMeta(paths.metaPath);
|
|
10951
|
+
const adapter = meta?.adapter ?? "web";
|
|
10952
|
+
if (adapter === "http") {
|
|
10953
|
+
const { normalizeHttpRun } = await import("@xera-ai/http");
|
|
10954
|
+
await normalizeHttpRun({ runId, runDir });
|
|
10955
|
+
console.log(`[xera:normalize] wrote normalized.json (http)`);
|
|
10956
|
+
return 0;
|
|
10957
|
+
}
|
|
10958
|
+
const { normalizeRun } = await import("@xera-ai/web");
|
|
10795
10959
|
const r = await normalizeRun({ runId, runDir });
|
|
10796
10960
|
console.log(`[xera:normalize] wrote normalized.json (scrubbed_fields_count=${r.scrubbed_fields_count})`);
|
|
10797
10961
|
return 0;
|
|
10798
10962
|
}
|
|
10799
10963
|
|
|
10800
10964
|
// src/bin-internal/post.ts
|
|
10801
|
-
import { existsSync as
|
|
10802
|
-
import { join as
|
|
10965
|
+
import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
|
|
10966
|
+
import { join as join22 } from "path";
|
|
10803
10967
|
|
|
10804
10968
|
// src/artifact/status.ts
|
|
10805
|
-
import { existsSync as
|
|
10969
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
10806
10970
|
import { dirname as dirname8 } from "path";
|
|
10807
10971
|
import { z as z7 } from "zod";
|
|
10808
10972
|
var ClassificationEnum = z7.enum([
|
|
@@ -10811,7 +10975,10 @@ var ClassificationEnum = z7.enum([
|
|
|
10811
10975
|
"SELECTOR_DRIFT",
|
|
10812
10976
|
"FLAKY",
|
|
10813
10977
|
"TEST_BUG",
|
|
10814
|
-
"TEST_OUTDATED"
|
|
10978
|
+
"TEST_OUTDATED",
|
|
10979
|
+
"CONTRACT_DRIFT",
|
|
10980
|
+
"RATE_LIMITED",
|
|
10981
|
+
"AUTH_EXPIRED"
|
|
10815
10982
|
]);
|
|
10816
10983
|
var ResultEnum = z7.enum(["PASS", "FAIL"]);
|
|
10817
10984
|
var ConfidenceEnum = z7.enum(["low", "medium", "high"]);
|
|
@@ -10837,7 +11004,7 @@ var StatusJsonSchema = z7.object({
|
|
|
10837
11004
|
});
|
|
10838
11005
|
var HISTORY_CAP = 20;
|
|
10839
11006
|
function readStatus(path) {
|
|
10840
|
-
if (!
|
|
11007
|
+
if (!existsSync23(path))
|
|
10841
11008
|
return null;
|
|
10842
11009
|
return StatusJsonSchema.parse(JSON.parse(readFileSync19(path, "utf8")));
|
|
10843
11010
|
}
|
|
@@ -10869,8 +11036,8 @@ async function postCmd(argv) {
|
|
|
10869
11036
|
return 0;
|
|
10870
11037
|
}
|
|
10871
11038
|
const paths = resolveArtifactPaths(cwd, ticket);
|
|
10872
|
-
const draftPath =
|
|
10873
|
-
if (!
|
|
11039
|
+
const draftPath = join22(paths.ticketDir, "jira-comment.draft.md");
|
|
11040
|
+
if (!existsSync24(draftPath)) {
|
|
10874
11041
|
console.error(`[xera:post] no draft at ${draftPath}; run \`xera-internal report\` first.`);
|
|
10875
11042
|
return 1;
|
|
10876
11043
|
}
|
|
@@ -10902,12 +11069,15 @@ async function promoteCmd(argv) {
|
|
|
10902
11069
|
}
|
|
10903
11070
|
|
|
10904
11071
|
// src/bin-internal/report.ts
|
|
10905
|
-
import { existsSync as
|
|
10906
|
-
import { join as
|
|
11072
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "fs";
|
|
11073
|
+
import { join as join23 } from "path";
|
|
10907
11074
|
|
|
10908
11075
|
// src/classifier/aggregate.ts
|
|
10909
11076
|
var CLASS_PRIORITY = [
|
|
10910
11077
|
"REAL_BUG",
|
|
11078
|
+
"CONTRACT_DRIFT",
|
|
11079
|
+
"AUTH_EXPIRED",
|
|
11080
|
+
"RATE_LIMITED",
|
|
10911
11081
|
"TEST_OUTDATED",
|
|
10912
11082
|
"TEST_BUG",
|
|
10913
11083
|
"SELECTOR_DRIFT",
|
|
@@ -10934,6 +11104,134 @@ function aggregateScenarios(scenarios) {
|
|
|
10934
11104
|
return { overall: chosen, overallConfidence: minConf, scenarios };
|
|
10935
11105
|
}
|
|
10936
11106
|
|
|
11107
|
+
// src/classifier/auth-expired.ts
|
|
11108
|
+
function jwtExpPast(jwt, now) {
|
|
11109
|
+
const parts = jwt.split(".");
|
|
11110
|
+
if (parts.length !== 3)
|
|
11111
|
+
return false;
|
|
11112
|
+
try {
|
|
11113
|
+
const payloadB64 = parts[1];
|
|
11114
|
+
if (!payloadB64)
|
|
11115
|
+
return false;
|
|
11116
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
|
|
11117
|
+
return typeof payload.exp === "number" && payload.exp * 1000 < now;
|
|
11118
|
+
} catch {
|
|
11119
|
+
return false;
|
|
11120
|
+
}
|
|
11121
|
+
}
|
|
11122
|
+
function classifyAuthExpired(input) {
|
|
11123
|
+
const has401 = input.calls.some((c) => c.status === 401);
|
|
11124
|
+
if (!has401)
|
|
11125
|
+
return null;
|
|
11126
|
+
const now = Date.now();
|
|
11127
|
+
for (const [role, entry] of Object.entries(input.authFiles)) {
|
|
11128
|
+
const fileExpired = new Date(entry.expires_at).getTime() < now;
|
|
11129
|
+
const jwtExpired = entry.type === "bearer" && jwtExpPast(entry.token, now);
|
|
11130
|
+
if (fileExpired || jwtExpired) {
|
|
11131
|
+
return {
|
|
11132
|
+
class: "AUTH_EXPIRED",
|
|
11133
|
+
rationale: `HTTP 401 captured; auth file for role '${role}' is past expiry. Run: bun run xera:auth-setup --role ${role}`
|
|
11134
|
+
};
|
|
11135
|
+
}
|
|
11136
|
+
}
|
|
11137
|
+
return null;
|
|
11138
|
+
}
|
|
11139
|
+
|
|
11140
|
+
// src/classifier/contract-drift.ts
|
|
11141
|
+
function matchPath(specPaths, actualUrl) {
|
|
11142
|
+
const path = actualUrl.split("?")[0] ?? actualUrl;
|
|
11143
|
+
for (const tmpl of specPaths) {
|
|
11144
|
+
const re = new RegExp(`^${tmpl.replace(/\{[^}]+\}/g, "[^/]+")}$`);
|
|
11145
|
+
if (re.test(path))
|
|
11146
|
+
return tmpl;
|
|
11147
|
+
}
|
|
11148
|
+
return null;
|
|
11149
|
+
}
|
|
11150
|
+
function matchesSchema(body, schema) {
|
|
11151
|
+
if (!schema)
|
|
11152
|
+
return true;
|
|
11153
|
+
if (schema.type === "object") {
|
|
11154
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
11155
|
+
return false;
|
|
11156
|
+
const obj = body;
|
|
11157
|
+
for (const req of schema.required ?? []) {
|
|
11158
|
+
if (!(req in obj))
|
|
11159
|
+
return false;
|
|
11160
|
+
}
|
|
11161
|
+
return true;
|
|
11162
|
+
}
|
|
11163
|
+
if (schema.type === "array")
|
|
11164
|
+
return Array.isArray(body);
|
|
11165
|
+
if (schema.type === "string")
|
|
11166
|
+
return typeof body === "string";
|
|
11167
|
+
if (schema.type === "integer" || schema.type === "number")
|
|
11168
|
+
return typeof body === "number";
|
|
11169
|
+
if (schema.type === "boolean")
|
|
11170
|
+
return typeof body === "boolean";
|
|
11171
|
+
if (schema.type === "null")
|
|
11172
|
+
return body === null;
|
|
11173
|
+
return true;
|
|
11174
|
+
}
|
|
11175
|
+
var VERBS = ["get", "post", "put", "patch", "delete"];
|
|
11176
|
+
function isVerb(s) {
|
|
11177
|
+
return VERBS.includes(s);
|
|
11178
|
+
}
|
|
11179
|
+
function classifyContractDrift(input) {
|
|
11180
|
+
if (input.openapi === null)
|
|
11181
|
+
return null;
|
|
11182
|
+
const specPaths = Object.keys(input.openapi.paths);
|
|
11183
|
+
for (const call of input.calls) {
|
|
11184
|
+
const tmpl = matchPath(specPaths, call.url);
|
|
11185
|
+
if (!tmpl) {
|
|
11186
|
+
return {
|
|
11187
|
+
class: "CONTRACT_DRIFT",
|
|
11188
|
+
rationale: `Endpoint ${call.method} ${call.url} not found in OpenAPI`
|
|
11189
|
+
};
|
|
11190
|
+
}
|
|
11191
|
+
const methodLower = call.method.toLowerCase();
|
|
11192
|
+
if (!isVerb(methodLower)) {
|
|
11193
|
+
return {
|
|
11194
|
+
class: "CONTRACT_DRIFT",
|
|
11195
|
+
rationale: `Method ${call.method} not supported by classifier for ${tmpl}`
|
|
11196
|
+
};
|
|
11197
|
+
}
|
|
11198
|
+
const pathItem = input.openapi.paths[tmpl];
|
|
11199
|
+
const op = pathItem?.[methodLower];
|
|
11200
|
+
if (!op) {
|
|
11201
|
+
return {
|
|
11202
|
+
class: "CONTRACT_DRIFT",
|
|
11203
|
+
rationale: `${call.method} not defined for ${tmpl} in OpenAPI`
|
|
11204
|
+
};
|
|
11205
|
+
}
|
|
11206
|
+
const respDef = op.responses?.[String(call.status)];
|
|
11207
|
+
if (!respDef) {
|
|
11208
|
+
return {
|
|
11209
|
+
class: "CONTRACT_DRIFT",
|
|
11210
|
+
rationale: `Status ${call.status} not enumerated for ${call.method} ${tmpl} in OpenAPI`
|
|
11211
|
+
};
|
|
11212
|
+
}
|
|
11213
|
+
const schema = respDef.content?.["application/json"]?.schema;
|
|
11214
|
+
if (!matchesSchema(call.respBody, schema)) {
|
|
11215
|
+
return {
|
|
11216
|
+
class: "CONTRACT_DRIFT",
|
|
11217
|
+
rationale: `Response body for ${call.method} ${tmpl} (${call.status}) does not match OpenAPI schema`
|
|
11218
|
+
};
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
return null;
|
|
11222
|
+
}
|
|
11223
|
+
|
|
11224
|
+
// src/classifier/rate-limited.ts
|
|
11225
|
+
function classifyRateLimited(input) {
|
|
11226
|
+
const hit = input.calls.find((c) => c.status === 429);
|
|
11227
|
+
if (!hit)
|
|
11228
|
+
return null;
|
|
11229
|
+
return {
|
|
11230
|
+
class: "RATE_LIMITED",
|
|
11231
|
+
rationale: `Captured HTTP 429 on ${hit.method} ${hit.url}`
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
|
|
10937
11235
|
// src/graph/classify.ts
|
|
10938
11236
|
var DEFAULT_THRESHOLD = 0.7;
|
|
10939
11237
|
var SHORT_CIRCUIT = ["FLAKY", "PASS"];
|
|
@@ -11038,11 +11336,11 @@ xera v${input.xeraVersion} \u2022 prompts v${input.promptsVersion}`;
|
|
|
11038
11336
|
}
|
|
11039
11337
|
|
|
11040
11338
|
// src/reporter/status-writer.ts
|
|
11041
|
-
import { existsSync as
|
|
11339
|
+
import { existsSync as existsSync25 } from "fs";
|
|
11042
11340
|
function writeStatusFromClassification(path, input) {
|
|
11043
11341
|
const result = input.classification.overall === "PASS" ? "PASS" : "FAIL";
|
|
11044
11342
|
const entry = { ts: input.runTs, result, class: input.classification.overall };
|
|
11045
|
-
if (!
|
|
11343
|
+
if (!existsSync25(path)) {
|
|
11046
11344
|
writeStatus(path, {
|
|
11047
11345
|
ticket: input.ticket,
|
|
11048
11346
|
lastRun: input.runTs,
|
|
@@ -11074,11 +11372,70 @@ async function reportCmd(argv) {
|
|
|
11074
11372
|
console.error("[xera:report] usage: report <TICKET> --input=<classifier-output.json>");
|
|
11075
11373
|
return 1;
|
|
11076
11374
|
}
|
|
11077
|
-
const
|
|
11375
|
+
const cwd = process.cwd();
|
|
11376
|
+
const paths = resolveArtifactPaths(cwd, ticket);
|
|
11078
11377
|
const input = JSON.parse(readFileSync21(inputArg.slice("--input=".length), "utf8"));
|
|
11079
|
-
|
|
11080
|
-
const
|
|
11081
|
-
|
|
11378
|
+
let httpRuleOverride = null;
|
|
11379
|
+
const meta = readMeta(paths.metaPath);
|
|
11380
|
+
if (meta?.adapter === "http") {
|
|
11381
|
+
const config = await loadConfig(cwd);
|
|
11382
|
+
if (config.http) {
|
|
11383
|
+
const normalizedPath = join23(paths.ticketDir, "runs", input.runId, "normalized.json");
|
|
11384
|
+
if (existsSync26(normalizedPath)) {
|
|
11385
|
+
const norm = JSON.parse(readFileSync21(normalizedPath, "utf8"));
|
|
11386
|
+
const calls = norm.http?.calls ?? [];
|
|
11387
|
+
const rate = classifyRateLimited({ calls });
|
|
11388
|
+
if (rate)
|
|
11389
|
+
httpRuleOverride = rate;
|
|
11390
|
+
if (!httpRuleOverride) {
|
|
11391
|
+
const authFiles = {};
|
|
11392
|
+
const httpAuthDir = join23(cwd, ".xera", ".auth", "http");
|
|
11393
|
+
for (const role of Object.keys(config.http.auth.roles)) {
|
|
11394
|
+
const entry = readAuthState(httpAuthDir, role);
|
|
11395
|
+
if (entry) {
|
|
11396
|
+
const p = entry.payload;
|
|
11397
|
+
if (typeof p.token === "string" && typeof p.type === "string") {
|
|
11398
|
+
authFiles[role] = {
|
|
11399
|
+
token: p.token,
|
|
11400
|
+
type: p.type,
|
|
11401
|
+
expires_at: entry.expires_at
|
|
11402
|
+
};
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
}
|
|
11406
|
+
const authExp = classifyAuthExpired({ calls, authFiles });
|
|
11407
|
+
if (authExp)
|
|
11408
|
+
httpRuleOverride = authExp;
|
|
11409
|
+
}
|
|
11410
|
+
if (!httpRuleOverride && config.http.spec) {
|
|
11411
|
+
const { loadOpenApi } = await import("@xera-ai/http");
|
|
11412
|
+
const openapi = await loadOpenApi(config.http.spec);
|
|
11413
|
+
if (openapi) {
|
|
11414
|
+
const drift = classifyContractDrift({
|
|
11415
|
+
calls: calls.map((c) => ({
|
|
11416
|
+
method: c.method,
|
|
11417
|
+
url: c.url,
|
|
11418
|
+
status: c.status,
|
|
11419
|
+
respBody: c.respBody
|
|
11420
|
+
})),
|
|
11421
|
+
openapi
|
|
11422
|
+
});
|
|
11423
|
+
if (drift)
|
|
11424
|
+
httpRuleOverride = drift;
|
|
11425
|
+
}
|
|
11426
|
+
}
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
}
|
|
11430
|
+
const scenariosForAggregation = httpRuleOverride ? input.scenarios.map((s) => s.outcome === "FAIL" ? {
|
|
11431
|
+
...s,
|
|
11432
|
+
class: httpRuleOverride.class,
|
|
11433
|
+
rationale: httpRuleOverride.rationale,
|
|
11434
|
+
confidence: "high"
|
|
11435
|
+
} : s) : input.scenarios;
|
|
11436
|
+
const aggregated = aggregateScenarios(scenariosForAggregation);
|
|
11437
|
+
const decisionsPath = join23(paths.ticketDir, "runs", input.runId, "outdated-decisions.json");
|
|
11438
|
+
const decisions = existsSync26(decisionsPath) ? JSON.parse(readFileSync21(decisionsPath, "utf8")) : {};
|
|
11082
11439
|
const graph = deriveSnapshot(loadAllEvents(process.cwd()));
|
|
11083
11440
|
const normalizeScenarioName = (name) => name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
11084
11441
|
const scenarioIdByName = {};
|
|
@@ -11126,7 +11483,7 @@ async function reportCmd(argv) {
|
|
|
11126
11483
|
xeraVersion: "0.1.0",
|
|
11127
11484
|
promptsVersion: "1.0.0"
|
|
11128
11485
|
});
|
|
11129
|
-
const draftPath =
|
|
11486
|
+
const draftPath = join23(paths.ticketDir, "jira-comment.draft.md");
|
|
11130
11487
|
writeFileSync14(draftPath, md);
|
|
11131
11488
|
console.log(`[xera:report] wrote status.json and ${draftPath}`);
|
|
11132
11489
|
return 0;
|
|
@@ -11194,7 +11551,7 @@ async function unlockCmd(argv) {
|
|
|
11194
11551
|
}
|
|
11195
11552
|
|
|
11196
11553
|
// src/bin-internal/validate-feature.ts
|
|
11197
|
-
import { existsSync as
|
|
11554
|
+
import { existsSync as existsSync27, readFileSync as readFileSync22 } from "fs";
|
|
11198
11555
|
import { validateGherkin as validateGherkin2 } from "@xera-ai/web";
|
|
11199
11556
|
async function validateFeatureCmd(argv) {
|
|
11200
11557
|
const ticket = argv[0];
|
|
@@ -11203,7 +11560,7 @@ async function validateFeatureCmd(argv) {
|
|
|
11203
11560
|
return 1;
|
|
11204
11561
|
}
|
|
11205
11562
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
11206
|
-
if (!
|
|
11563
|
+
if (!existsSync27(paths.featurePath)) {
|
|
11207
11564
|
console.error(`[xera:validate-feature] missing ${paths.featurePath}`);
|
|
11208
11565
|
return 1;
|
|
11209
11566
|
}
|
|
@@ -11219,6 +11576,7 @@ async function validateFeatureCmd(argv) {
|
|
|
11219
11576
|
|
|
11220
11577
|
// src/bin-internal/index.ts
|
|
11221
11578
|
var COMMANDS = {
|
|
11579
|
+
"auth-setup": authSetupCmd,
|
|
11222
11580
|
disputes: disputesCmd,
|
|
11223
11581
|
doctor: doctorCmd,
|
|
11224
11582
|
"eval-deterministic": evalDeterministicCmd,
|