@xera-ai/core 0.4.3 → 0.4.4
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/bin/internal.js +382 -248
- package/dist/bin-internal/disputes.d.ts +2 -0
- package/dist/bin-internal/disputes.d.ts.map +1 -0
- package/dist/bin-internal/doctor.d.ts +1 -1
- package/dist/bin-internal/doctor.d.ts.map +1 -1
- package/dist/bin-internal/exec.d.ts.map +1 -1
- package/dist/bin-internal/graph-record-script.d.ts.map +1 -1
- package/dist/bin-internal/index.d.ts.map +1 -1
- package/dist/graph/store.d.ts.map +1 -1
- package/dist/graph/types.d.ts +1 -0
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/package.json +1 -1
- package/src/bin-internal/disputes.ts +88 -0
- package/src/bin-internal/doctor.ts +13 -1
- package/src/bin-internal/exec.ts +3 -0
- package/src/bin-internal/graph-record-script.ts +37 -8
- package/src/bin-internal/index.ts +2 -0
- package/src/config/schema.ts +1 -1
- package/src/graph/store.ts +8 -1
- package/src/graph/types.ts +1 -0
package/dist/bin/internal.js
CHANGED
|
@@ -153,10 +153,10 @@ var init_schema = __esm(() => {
|
|
|
153
153
|
// src/graph/store.ts
|
|
154
154
|
import { createHash } from "crypto";
|
|
155
155
|
import {
|
|
156
|
-
existsSync
|
|
157
|
-
mkdirSync
|
|
156
|
+
existsSync,
|
|
157
|
+
mkdirSync,
|
|
158
158
|
readdirSync,
|
|
159
|
-
readFileSync
|
|
159
|
+
readFileSync,
|
|
160
160
|
renameSync,
|
|
161
161
|
writeFileSync
|
|
162
162
|
} from "fs";
|
|
@@ -167,7 +167,7 @@ function appendEvents(repoRoot, events, opts) {
|
|
|
167
167
|
const paths = graphPaths(repoRoot);
|
|
168
168
|
const yyyyMm = currentYyyyMm(opts.now);
|
|
169
169
|
const monthDir = paths.eventsMonthDir(yyyyMm);
|
|
170
|
-
|
|
170
|
+
mkdirSync(monthDir, { recursive: true });
|
|
171
171
|
const ulid = events[0].event_id;
|
|
172
172
|
const finalPath = paths.eventFile(ulid, opts.skill, opts.ticketId, yyyyMm);
|
|
173
173
|
const tmpPath = `${finalPath}.tmp`;
|
|
@@ -180,7 +180,7 @@ function appendEvents(repoRoot, events, opts) {
|
|
|
180
180
|
}
|
|
181
181
|
function loadAllEvents(repoRoot) {
|
|
182
182
|
const paths = graphPaths(repoRoot);
|
|
183
|
-
if (!
|
|
183
|
+
if (!existsSync(paths.eventsDir))
|
|
184
184
|
return [];
|
|
185
185
|
const files = [];
|
|
186
186
|
for (const monthDir of readdirSync(paths.eventsDir, { withFileTypes: true })) {
|
|
@@ -200,7 +200,7 @@ function loadAllEvents(repoRoot) {
|
|
|
200
200
|
const events = [];
|
|
201
201
|
for (const file of files) {
|
|
202
202
|
try {
|
|
203
|
-
const lines =
|
|
203
|
+
const lines = readFileSync(file, "utf8").split(`
|
|
204
204
|
`).filter(Boolean);
|
|
205
205
|
for (const line of lines) {
|
|
206
206
|
let parsed;
|
|
@@ -326,6 +326,13 @@ function deriveSnapshot(events) {
|
|
|
326
326
|
edges.push(ed);
|
|
327
327
|
break;
|
|
328
328
|
}
|
|
329
|
+
case "classification.disputed": {
|
|
330
|
+
const existing = latestFailures[e.payload.scenarioId];
|
|
331
|
+
if (existing && existing.runId === e.payload.runId) {
|
|
332
|
+
existing.disputed = true;
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
329
336
|
default:
|
|
330
337
|
break;
|
|
331
338
|
}
|
|
@@ -345,17 +352,17 @@ function deriveSnapshot(events) {
|
|
|
345
352
|
}
|
|
346
353
|
function writeSnapshot(repoRoot, snap) {
|
|
347
354
|
const paths = graphPaths(repoRoot);
|
|
348
|
-
|
|
355
|
+
mkdirSync(dirname(paths.snapshotFile), { recursive: true });
|
|
349
356
|
const tmp = `${paths.snapshotFile}.tmp`;
|
|
350
357
|
writeFileSync(tmp, JSON.stringify(snap, null, 2));
|
|
351
358
|
renameSync(tmp, paths.snapshotFile);
|
|
352
359
|
}
|
|
353
360
|
function loadSnapshot(repoRoot) {
|
|
354
361
|
const paths = graphPaths(repoRoot);
|
|
355
|
-
if (!
|
|
362
|
+
if (!existsSync(paths.snapshotFile))
|
|
356
363
|
return null;
|
|
357
364
|
try {
|
|
358
|
-
return JSON.parse(
|
|
365
|
+
return JSON.parse(readFileSync(paths.snapshotFile, "utf8"));
|
|
359
366
|
} catch {
|
|
360
367
|
return null;
|
|
361
368
|
}
|
|
@@ -429,21 +436,29 @@ var exports_graph_record_script = {};
|
|
|
429
436
|
__export(exports_graph_record_script, {
|
|
430
437
|
recordScriptImpl: () => recordScriptImpl
|
|
431
438
|
});
|
|
432
|
-
import { createHash as
|
|
433
|
-
import { existsSync as
|
|
434
|
-
import { basename, join as
|
|
439
|
+
import { createHash as createHash2 } from "crypto";
|
|
440
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
441
|
+
import { basename, join as join3 } from "path";
|
|
442
|
+
function inferPriority(name, gherkin) {
|
|
443
|
+
const haystack = `${name} ${gherkin}`.toLowerCase();
|
|
444
|
+
for (const kw of P0_KEYWORDS) {
|
|
445
|
+
if (haystack.includes(kw))
|
|
446
|
+
return "p0";
|
|
447
|
+
}
|
|
448
|
+
return "p1";
|
|
449
|
+
}
|
|
435
450
|
function parseFeature(text) {
|
|
436
451
|
const scenarios = [];
|
|
437
452
|
const lines = text.split(`
|
|
438
453
|
`);
|
|
439
|
-
let
|
|
454
|
+
let explicitTag = null;
|
|
440
455
|
let i = 0;
|
|
441
456
|
while (i < lines.length) {
|
|
442
457
|
const line = lines[i].trim();
|
|
443
458
|
if (line.startsWith("@")) {
|
|
444
459
|
const tag = line.slice(1).split(/\s+/)[0].toLowerCase();
|
|
445
460
|
if (tag === "p0" || tag === "p1" || tag === "p2")
|
|
446
|
-
|
|
461
|
+
explicitTag = tag;
|
|
447
462
|
i++;
|
|
448
463
|
continue;
|
|
449
464
|
}
|
|
@@ -453,13 +468,11 @@ function parseFeature(text) {
|
|
|
453
468
|
i++;
|
|
454
469
|
while (i < lines.length && !lines[i].trim().startsWith("Scenario") && !lines[i].trim().startsWith("@"))
|
|
455
470
|
i++;
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
});
|
|
462
|
-
currentTagPriority = "p1";
|
|
471
|
+
const gherkin = lines.slice(start, i).join(`
|
|
472
|
+
`);
|
|
473
|
+
const priority = explicitTag !== null ? explicitTag : inferPriority(name, gherkin);
|
|
474
|
+
scenarios.push({ name, priority, gherkin });
|
|
475
|
+
explicitTag = null;
|
|
463
476
|
continue;
|
|
464
477
|
}
|
|
465
478
|
i++;
|
|
@@ -467,9 +480,9 @@ function parseFeature(text) {
|
|
|
467
480
|
return scenarios;
|
|
468
481
|
}
|
|
469
482
|
function listPomFiles(dir) {
|
|
470
|
-
if (!
|
|
483
|
+
if (!existsSync4(dir))
|
|
471
484
|
return [];
|
|
472
|
-
return
|
|
485
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".ts")).map((f) => join3(dir, f));
|
|
473
486
|
}
|
|
474
487
|
function extractRoute(pomContent) {
|
|
475
488
|
const m = pomContent.match(/goto\s*\(\s*['"]([^'"]+)['"]/);
|
|
@@ -496,15 +509,15 @@ function extractPomUsage(specContent) {
|
|
|
496
509
|
return [...names];
|
|
497
510
|
}
|
|
498
511
|
async function recordScriptImpl(repoRoot, ticket) {
|
|
499
|
-
const ticketDir =
|
|
500
|
-
const featurePath =
|
|
501
|
-
const specPath =
|
|
502
|
-
const pomDir =
|
|
503
|
-
if (!
|
|
512
|
+
const ticketDir = join3(repoRoot, ".xera", ticket);
|
|
513
|
+
const featurePath = join3(ticketDir, "feature", `${ticket}.feature`);
|
|
514
|
+
const specPath = join3(ticketDir, "tests", `${ticket}.spec.ts`);
|
|
515
|
+
const pomDir = join3(ticketDir, "poms");
|
|
516
|
+
if (!existsSync4(featurePath)) {
|
|
504
517
|
console.error(`[graph-record script] feature missing`);
|
|
505
518
|
return 1;
|
|
506
519
|
}
|
|
507
|
-
const featureText =
|
|
520
|
+
const featureText = readFileSync4(featurePath, "utf8");
|
|
508
521
|
const featureHash = sha1(featureText);
|
|
509
522
|
const scenarios = parseFeature(featureText);
|
|
510
523
|
const events = [];
|
|
@@ -524,7 +537,7 @@ async function recordScriptImpl(repoRoot, ticket) {
|
|
|
524
537
|
const pomFiles = listPomFiles(pomDir);
|
|
525
538
|
const pomNameToId = new Map;
|
|
526
539
|
for (const pomFile of pomFiles) {
|
|
527
|
-
const content =
|
|
540
|
+
const content = readFileSync4(pomFile, "utf8");
|
|
528
541
|
const id = pId(pomFile);
|
|
529
542
|
const className = content.match(/export\s+class\s+([A-Z][A-Za-z0-9]*Page)/)?.[1] ?? "";
|
|
530
543
|
pomNameToId.set(className, id);
|
|
@@ -538,8 +551,8 @@ async function recordScriptImpl(repoRoot, ticket) {
|
|
|
538
551
|
};
|
|
539
552
|
events.push(mk("xera-script", "pom.generated", pg));
|
|
540
553
|
}
|
|
541
|
-
if (
|
|
542
|
-
const specContent =
|
|
554
|
+
if (existsSync4(specPath)) {
|
|
555
|
+
const specContent = readFileSync4(specPath, "utf8");
|
|
543
556
|
const usedPoms = extractPomUsage(specContent);
|
|
544
557
|
for (const scenario of scenarios) {
|
|
545
558
|
const scId = sId(ticket, scenario.name);
|
|
@@ -571,17 +584,39 @@ async function recordScriptImpl(repoRoot, ticket) {
|
|
|
571
584
|
appendEvents(repoRoot, events, { skill: "xera-script", ticketId: ticket });
|
|
572
585
|
return 0;
|
|
573
586
|
}
|
|
574
|
-
var sha1 = (s) =>
|
|
587
|
+
var sha1 = (s) => createHash2("sha1").update(s).digest("hex"), sId = (ticket, name) => sha1(`${ticket}:${name.trim().toLowerCase().replace(/\s+/g, " ")}`), pId = (file) => sha1(basename(file)), nowIso = () => new Date().toISOString(), mk = (actor, type, payload) => ({
|
|
575
588
|
event_id: ulid(),
|
|
576
589
|
schema_version: SCHEMA_VERSION,
|
|
577
590
|
ts: nowIso(),
|
|
578
591
|
actor,
|
|
579
592
|
type,
|
|
580
593
|
payload
|
|
581
|
-
});
|
|
594
|
+
}), P0_KEYWORDS;
|
|
582
595
|
var init_graph_record_script = __esm(() => {
|
|
583
596
|
init_store();
|
|
584
597
|
init_ulid();
|
|
598
|
+
P0_KEYWORDS = [
|
|
599
|
+
"log in",
|
|
600
|
+
"login",
|
|
601
|
+
"sign in",
|
|
602
|
+
"signin",
|
|
603
|
+
"sign up",
|
|
604
|
+
"signup",
|
|
605
|
+
"auth",
|
|
606
|
+
"authentic",
|
|
607
|
+
"payment",
|
|
608
|
+
"pay ",
|
|
609
|
+
"checkout",
|
|
610
|
+
"purchase",
|
|
611
|
+
"charge",
|
|
612
|
+
"password",
|
|
613
|
+
"credential",
|
|
614
|
+
"admin",
|
|
615
|
+
"permission",
|
|
616
|
+
"role",
|
|
617
|
+
"must ",
|
|
618
|
+
"critical"
|
|
619
|
+
];
|
|
585
620
|
});
|
|
586
621
|
|
|
587
622
|
// ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
|
|
@@ -7580,14 +7615,14 @@ __export(exports_graph_record, {
|
|
|
7580
7615
|
recordFetch: () => recordFetch,
|
|
7581
7616
|
graphRecordCmd: () => graphRecordCmd
|
|
7582
7617
|
});
|
|
7583
|
-
import { createHash as
|
|
7584
|
-
import { existsSync as
|
|
7585
|
-
import { basename as basename2, join as
|
|
7618
|
+
import { createHash as createHash3 } from "crypto";
|
|
7619
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
7620
|
+
import { basename as basename2, join as join4 } from "path";
|
|
7586
7621
|
function nowIso2() {
|
|
7587
7622
|
return new Date().toISOString();
|
|
7588
7623
|
}
|
|
7589
7624
|
function sha12(s) {
|
|
7590
|
-
return
|
|
7625
|
+
return createHash3("sha1").update(s).digest("hex");
|
|
7591
7626
|
}
|
|
7592
7627
|
function scenarioId(ticket, name) {
|
|
7593
7628
|
return sha12(`${ticket}:${name.trim().toLowerCase().replace(/\s+/g, " ")}`);
|
|
@@ -7606,21 +7641,21 @@ function makeEvent(actor, type, payload) {
|
|
|
7606
7641
|
};
|
|
7607
7642
|
}
|
|
7608
7643
|
function readStoryFrontmatter(repoRoot, ticket) {
|
|
7609
|
-
const path =
|
|
7610
|
-
if (!
|
|
7644
|
+
const path = join4(repoRoot, ".xera", ticket, "story.md");
|
|
7645
|
+
if (!existsSync5(path))
|
|
7611
7646
|
return null;
|
|
7612
|
-
const raw =
|
|
7647
|
+
const raw = readFileSync5(path, "utf8");
|
|
7613
7648
|
const m = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
7614
7649
|
if (!m)
|
|
7615
7650
|
return null;
|
|
7616
7651
|
return $parse(m[1]);
|
|
7617
7652
|
}
|
|
7618
7653
|
function readGraphInput(repoRoot, ticket) {
|
|
7619
|
-
const path =
|
|
7620
|
-
if (!
|
|
7654
|
+
const path = join4(repoRoot, ".xera", ticket, "graph-input.json");
|
|
7655
|
+
if (!existsSync5(path))
|
|
7621
7656
|
return { modifiesAreas: [] };
|
|
7622
7657
|
try {
|
|
7623
|
-
return JSON.parse(
|
|
7658
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
7624
7659
|
} catch {
|
|
7625
7660
|
return { modifiesAreas: [] };
|
|
7626
7661
|
}
|
|
@@ -7668,12 +7703,12 @@ async function recordScript(repoRoot, ticket) {
|
|
|
7668
7703
|
return recordScriptImpl2(repoRoot, ticket);
|
|
7669
7704
|
}
|
|
7670
7705
|
async function recordExec(repoRoot, ticket, runId) {
|
|
7671
|
-
const reporterPath =
|
|
7672
|
-
if (!
|
|
7706
|
+
const reporterPath = join4(repoRoot, ".xera", ticket, "runs", runId, "reporter.json");
|
|
7707
|
+
if (!existsSync5(reporterPath)) {
|
|
7673
7708
|
console.error(`[graph-record exec] reporter.json missing`);
|
|
7674
7709
|
return 1;
|
|
7675
7710
|
}
|
|
7676
|
-
const data = JSON.parse(
|
|
7711
|
+
const data = JSON.parse(readFileSync5(reporterPath, "utf8"));
|
|
7677
7712
|
const events = [];
|
|
7678
7713
|
for (const s of data.scenarios) {
|
|
7679
7714
|
const p = {
|
|
@@ -7691,12 +7726,12 @@ async function recordExec(repoRoot, ticket, runId) {
|
|
|
7691
7726
|
return 0;
|
|
7692
7727
|
}
|
|
7693
7728
|
async function recordClassify(repoRoot, ticket, runId) {
|
|
7694
|
-
const classifyPath =
|
|
7695
|
-
if (!
|
|
7729
|
+
const classifyPath = join4(repoRoot, ".xera", ticket, "runs", runId, "classifier-output.json");
|
|
7730
|
+
if (!existsSync5(classifyPath)) {
|
|
7696
7731
|
console.error(`[graph-record classify] classifier-output.json missing`);
|
|
7697
7732
|
return 1;
|
|
7698
7733
|
}
|
|
7699
|
-
const data = JSON.parse(
|
|
7734
|
+
const data = JSON.parse(readFileSync5(classifyPath, "utf8"));
|
|
7700
7735
|
const events = [];
|
|
7701
7736
|
for (const s of data.scenarios) {
|
|
7702
7737
|
const p = {
|
|
@@ -7724,7 +7759,7 @@ async function recordPromote(repoRoot, args) {
|
|
|
7724
7759
|
appendEvents(repoRoot, [e], { skill: "xera-promote", ticketId: "shared" });
|
|
7725
7760
|
return 0;
|
|
7726
7761
|
}
|
|
7727
|
-
function
|
|
7762
|
+
function parseFlags(args) {
|
|
7728
7763
|
const m = new Map;
|
|
7729
7764
|
for (let i = 0;i < args.length; i++) {
|
|
7730
7765
|
if (args[i].startsWith("--")) {
|
|
@@ -7760,7 +7795,7 @@ async function graphRecordCmd(argv) {
|
|
|
7760
7795
|
}
|
|
7761
7796
|
case "exec": {
|
|
7762
7797
|
const ticket = rest[0];
|
|
7763
|
-
const flags =
|
|
7798
|
+
const flags = parseFlags(rest);
|
|
7764
7799
|
const runId = flags.get("--run-id");
|
|
7765
7800
|
if (!ticket || !runId) {
|
|
7766
7801
|
console.error("ticket + --run-id required");
|
|
@@ -7770,7 +7805,7 @@ async function graphRecordCmd(argv) {
|
|
|
7770
7805
|
}
|
|
7771
7806
|
case "classify": {
|
|
7772
7807
|
const ticket = rest[0];
|
|
7773
|
-
const flags =
|
|
7808
|
+
const flags = parseFlags(rest);
|
|
7774
7809
|
const runId = flags.get("--run-id");
|
|
7775
7810
|
if (!ticket || !runId) {
|
|
7776
7811
|
console.error("ticket + --run-id required");
|
|
@@ -7779,10 +7814,10 @@ async function graphRecordCmd(argv) {
|
|
|
7779
7814
|
return recordClassify(repoRoot, ticket, runId);
|
|
7780
7815
|
}
|
|
7781
7816
|
case "promote": {
|
|
7782
|
-
return recordPromote(repoRoot,
|
|
7817
|
+
return recordPromote(repoRoot, parseFlags(rest));
|
|
7783
7818
|
}
|
|
7784
7819
|
case "dispute": {
|
|
7785
|
-
const flags =
|
|
7820
|
+
const flags = parseFlags(rest);
|
|
7786
7821
|
const runId = flags.get("--run-id");
|
|
7787
7822
|
const scenarioIdArg = flags.get("--scenario-id");
|
|
7788
7823
|
const from = flags.get("--from");
|
|
@@ -7829,20 +7864,145 @@ var init_graph_record = __esm(() => {
|
|
|
7829
7864
|
init_ulid();
|
|
7830
7865
|
});
|
|
7831
7866
|
|
|
7867
|
+
// src/bin-internal/graph-backfill.ts
|
|
7868
|
+
var exports_graph_backfill = {};
|
|
7869
|
+
__export(exports_graph_backfill, {
|
|
7870
|
+
graphBackfillCmd: () => graphBackfillCmd
|
|
7871
|
+
});
|
|
7872
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
7873
|
+
import { join as join5 } from "path";
|
|
7874
|
+
async function backfillTicket(repoRoot, ticket, dryRun) {
|
|
7875
|
+
const storyPath = join5(repoRoot, ".xera", ticket, "story.md");
|
|
7876
|
+
if (!existsSync6(storyPath))
|
|
7877
|
+
return 0;
|
|
7878
|
+
const { recordFetch: recordFetch2 } = await Promise.resolve().then(() => (init_graph_record(), exports_graph_record));
|
|
7879
|
+
if (dryRun) {
|
|
7880
|
+
console.log(`[backfill dry-run] would backfill ${ticket}`);
|
|
7881
|
+
return 0;
|
|
7882
|
+
}
|
|
7883
|
+
await recordScriptImpl(repoRoot, ticket);
|
|
7884
|
+
await recordFetch2(repoRoot, ticket);
|
|
7885
|
+
return 0;
|
|
7886
|
+
}
|
|
7887
|
+
async function graphBackfillCmd(argv) {
|
|
7888
|
+
const dryRun = argv.includes("--dry-run");
|
|
7889
|
+
const repoRoot = process.cwd();
|
|
7890
|
+
const xeraDir = join5(repoRoot, ".xera");
|
|
7891
|
+
if (!existsSync6(xeraDir)) {
|
|
7892
|
+
console.log("[backfill] no .xera/ directory");
|
|
7893
|
+
return 0;
|
|
7894
|
+
}
|
|
7895
|
+
const tickets = [];
|
|
7896
|
+
for (const entry of readdirSync3(xeraDir, { withFileTypes: true })) {
|
|
7897
|
+
if (!entry.isDirectory())
|
|
7898
|
+
continue;
|
|
7899
|
+
if (entry.name === "graph")
|
|
7900
|
+
continue;
|
|
7901
|
+
if (entry.name.startsWith("."))
|
|
7902
|
+
continue;
|
|
7903
|
+
if (!/^[A-Z]+-\d+$/.test(entry.name))
|
|
7904
|
+
continue;
|
|
7905
|
+
tickets.push(entry.name);
|
|
7906
|
+
}
|
|
7907
|
+
console.log(`[backfill] found ${tickets.length} tickets`);
|
|
7908
|
+
for (const t of tickets)
|
|
7909
|
+
await backfillTicket(repoRoot, t, dryRun);
|
|
7910
|
+
console.log(`[backfill] done`);
|
|
7911
|
+
return 0;
|
|
7912
|
+
}
|
|
7913
|
+
var init_graph_backfill = __esm(() => {
|
|
7914
|
+
init_graph_record_script();
|
|
7915
|
+
});
|
|
7916
|
+
|
|
7917
|
+
// src/bin-internal/disputes.ts
|
|
7918
|
+
init_store();
|
|
7919
|
+
function parseDuration(s) {
|
|
7920
|
+
const match = s.match(/^(\d+)([dhm])$/);
|
|
7921
|
+
if (!match)
|
|
7922
|
+
return 0;
|
|
7923
|
+
const n = Number.parseInt(match[1], 10);
|
|
7924
|
+
const unit = match[2];
|
|
7925
|
+
if (unit === "d")
|
|
7926
|
+
return n * 86400 * 1000;
|
|
7927
|
+
if (unit === "h")
|
|
7928
|
+
return n * 3600 * 1000;
|
|
7929
|
+
if (unit === "m")
|
|
7930
|
+
return n * 60 * 1000;
|
|
7931
|
+
return 0;
|
|
7932
|
+
}
|
|
7933
|
+
function eventToRow(e) {
|
|
7934
|
+
const p = e.payload;
|
|
7935
|
+
const row = {
|
|
7936
|
+
ts: e.ts,
|
|
7937
|
+
runId: p.runId,
|
|
7938
|
+
scenarioId: p.scenarioId,
|
|
7939
|
+
originalClassification: p.originalClassification,
|
|
7940
|
+
disputedTo: p.disputedTo,
|
|
7941
|
+
qaActor: p.qaActor
|
|
7942
|
+
};
|
|
7943
|
+
if (p.qaReason)
|
|
7944
|
+
row.qaReason = p.qaReason;
|
|
7945
|
+
return row;
|
|
7946
|
+
}
|
|
7947
|
+
function renderText(rows) {
|
|
7948
|
+
if (rows.length === 0)
|
|
7949
|
+
return `No disputes recorded.
|
|
7950
|
+
`;
|
|
7951
|
+
const lines = [];
|
|
7952
|
+
lines.push(`${rows.length} dispute(s):`);
|
|
7953
|
+
for (const r of rows) {
|
|
7954
|
+
lines.push(` ${r.ts} | ${r.scenarioId} | ${r.originalClassification} \u2192 ${r.disputedTo} | ${r.qaActor}`);
|
|
7955
|
+
if (r.qaReason)
|
|
7956
|
+
lines.push(` reason: ${r.qaReason}`);
|
|
7957
|
+
}
|
|
7958
|
+
return `${lines.join(`
|
|
7959
|
+
`)}
|
|
7960
|
+
`;
|
|
7961
|
+
}
|
|
7962
|
+
async function disputesCmd(argv) {
|
|
7963
|
+
let since;
|
|
7964
|
+
let format = "text";
|
|
7965
|
+
for (let i = 0;i < argv.length; i++) {
|
|
7966
|
+
if (argv[i] === "--since") {
|
|
7967
|
+
since = argv[++i];
|
|
7968
|
+
} else if (argv[i] === "--format") {
|
|
7969
|
+
const v = argv[++i];
|
|
7970
|
+
if (v === "json" || v === "text")
|
|
7971
|
+
format = v;
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
const repoRoot = process.cwd();
|
|
7975
|
+
const events = loadAllEvents(repoRoot);
|
|
7976
|
+
const disputes = events.filter((e) => e.type === "classification.disputed");
|
|
7977
|
+
let cutoffMs;
|
|
7978
|
+
if (since) {
|
|
7979
|
+
const sinceMs = parseDuration(since);
|
|
7980
|
+
if (sinceMs > 0)
|
|
7981
|
+
cutoffMs = Date.now() - sinceMs;
|
|
7982
|
+
}
|
|
7983
|
+
const rows = disputes.filter((e) => cutoffMs === undefined || Date.parse(e.ts) >= cutoffMs).map(eventToRow).sort((a, b) => a.ts < b.ts ? 1 : -1);
|
|
7984
|
+
if (format === "json") {
|
|
7985
|
+
process.stdout.write(JSON.stringify(rows, null, 2));
|
|
7986
|
+
} else {
|
|
7987
|
+
process.stdout.write(renderText(rows));
|
|
7988
|
+
}
|
|
7989
|
+
return 0;
|
|
7990
|
+
}
|
|
7991
|
+
|
|
7832
7992
|
// src/bin-internal/doctor.ts
|
|
7833
|
-
import { existsSync as
|
|
7834
|
-
import { join as
|
|
7993
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "fs";
|
|
7994
|
+
import { join as join6 } from "path";
|
|
7835
7995
|
|
|
7836
7996
|
// src/graph/cost.ts
|
|
7837
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
7997
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
7838
7998
|
init_paths();
|
|
7839
7999
|
function summarizeCost(repoRoot, daysBack) {
|
|
7840
8000
|
const paths = graphPaths(repoRoot);
|
|
7841
8001
|
const result = { totalCalls: 0, totalUsd: 0, bySkill: {}, windowDays: daysBack };
|
|
7842
|
-
if (!
|
|
8002
|
+
if (!existsSync2(paths.costLog))
|
|
7843
8003
|
return result;
|
|
7844
8004
|
const cutoff = Date.now() - daysBack * 86400 * 1000;
|
|
7845
|
-
for (const line of
|
|
8005
|
+
for (const line of readFileSync2(paths.costLog, "utf8").split(`
|
|
7846
8006
|
`)) {
|
|
7847
8007
|
if (!line.trim())
|
|
7848
8008
|
continue;
|
|
@@ -7941,10 +8101,10 @@ function frontmatterField(content, field) {
|
|
|
7941
8101
|
return m?.[1] ?? null;
|
|
7942
8102
|
}
|
|
7943
8103
|
function checkGoldenEvalDir(repoRoot) {
|
|
7944
|
-
const root =
|
|
7945
|
-
if (!
|
|
8104
|
+
const root = join6(repoRoot, "fixtures/golden-eval");
|
|
8105
|
+
if (!existsSync7(root))
|
|
7946
8106
|
return [{ ok: false, message: "fixtures/golden-eval/ does not exist" }];
|
|
7947
|
-
const dirs =
|
|
8107
|
+
const dirs = readdirSync4(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("."));
|
|
7948
8108
|
const results = [];
|
|
7949
8109
|
if (dirs.length < 3) {
|
|
7950
8110
|
results.push({
|
|
@@ -7953,15 +8113,15 @@ function checkGoldenEvalDir(repoRoot) {
|
|
|
7953
8113
|
});
|
|
7954
8114
|
}
|
|
7955
8115
|
for (const entry of dirs) {
|
|
7956
|
-
const dir =
|
|
7957
|
-
const metaPath =
|
|
7958
|
-
if (!
|
|
8116
|
+
const dir = join6(root, entry.name);
|
|
8117
|
+
const metaPath = join6(dir, "meta.json");
|
|
8118
|
+
if (!existsSync7(metaPath)) {
|
|
7959
8119
|
results.push({ ok: false, message: `${entry.name}: meta.json missing` });
|
|
7960
8120
|
continue;
|
|
7961
8121
|
}
|
|
7962
8122
|
let meta;
|
|
7963
8123
|
try {
|
|
7964
|
-
meta = JSON.parse(
|
|
8124
|
+
meta = JSON.parse(readFileSync6(metaPath, "utf8"));
|
|
7965
8125
|
} catch (err) {
|
|
7966
8126
|
results.push({
|
|
7967
8127
|
ok: false,
|
|
@@ -7972,12 +8132,12 @@ function checkGoldenEvalDir(repoRoot) {
|
|
|
7972
8132
|
const stages = Array.isArray(meta.stages) ? meta.stages : [];
|
|
7973
8133
|
if (stages.length === 0)
|
|
7974
8134
|
results.push({ ok: false, message: `${entry.name}: meta.stages is empty` });
|
|
7975
|
-
if (!
|
|
8135
|
+
if (!existsSync7(join6(dir, "story.md")))
|
|
7976
8136
|
results.push({ ok: false, message: `${entry.name}: story.md missing` });
|
|
7977
8137
|
for (const stage of stages) {
|
|
7978
8138
|
const required = REQUIRED_FILES_PER_STAGE[stage] ?? [];
|
|
7979
8139
|
for (const rel of required) {
|
|
7980
|
-
if (!
|
|
8140
|
+
if (!existsSync7(join6(dir, rel))) {
|
|
7981
8141
|
results.push({
|
|
7982
8142
|
ok: false,
|
|
7983
8143
|
message: `${meta.id ?? entry.name}: stage "${stage}" declared but ${rel} missing`
|
|
@@ -7989,10 +8149,10 @@ function checkGoldenEvalDir(repoRoot) {
|
|
|
7989
8149
|
return results;
|
|
7990
8150
|
}
|
|
7991
8151
|
function checkRubricPrompt(repoRoot) {
|
|
7992
|
-
const path =
|
|
7993
|
-
if (!
|
|
8152
|
+
const path = join6(repoRoot, "packages/prompts/eval-rubric.md");
|
|
8153
|
+
if (!existsSync7(path))
|
|
7994
8154
|
return [{ ok: false, message: "packages/prompts/eval-rubric.md missing" }];
|
|
7995
|
-
const text =
|
|
8155
|
+
const text = readFileSync6(path, "utf8");
|
|
7996
8156
|
const id = frontmatterField(text, "id");
|
|
7997
8157
|
const version = frontmatterField(text, "version");
|
|
7998
8158
|
if (id !== "eval-rubric")
|
|
@@ -8002,10 +8162,10 @@ function checkRubricPrompt(repoRoot) {
|
|
|
8002
8162
|
return [];
|
|
8003
8163
|
}
|
|
8004
8164
|
function checkEvalSkill(repoRoot) {
|
|
8005
|
-
const path =
|
|
8006
|
-
if (!
|
|
8165
|
+
const path = join6(repoRoot, "packages/skills/xera-eval.md");
|
|
8166
|
+
if (!existsSync7(path))
|
|
8007
8167
|
return [{ ok: false, message: "packages/skills/xera-eval.md missing" }];
|
|
8008
|
-
const text =
|
|
8168
|
+
const text = readFileSync6(path, "utf8");
|
|
8009
8169
|
if (!frontmatterField(text, "name"))
|
|
8010
8170
|
return [{ ok: false, message: 'xera-eval.md frontmatter "name" missing' }];
|
|
8011
8171
|
return [];
|
|
@@ -8014,16 +8174,17 @@ function checkPromptInjectionPreamble(repoRoot) {
|
|
|
8014
8174
|
return verifyPrompts(repoRoot);
|
|
8015
8175
|
}
|
|
8016
8176
|
function checkRootScripts(repoRoot) {
|
|
8017
|
-
const path =
|
|
8018
|
-
if (!
|
|
8177
|
+
const path = join6(repoRoot, "package.json");
|
|
8178
|
+
if (!existsSync7(path))
|
|
8019
8179
|
return [{ ok: false, message: "root package.json missing" }];
|
|
8020
|
-
const pkg = JSON.parse(
|
|
8180
|
+
const pkg = JSON.parse(readFileSync6(path, "utf8"));
|
|
8021
8181
|
const scripts = pkg.scripts ?? {};
|
|
8022
8182
|
const missing = REQUIRED_SCRIPTS.filter((s) => typeof scripts[s] !== "string");
|
|
8023
8183
|
return missing.map((s) => ({ ok: false, message: `root package.json missing script: ${s}` }));
|
|
8024
8184
|
}
|
|
8025
|
-
async function doctorCmd(
|
|
8185
|
+
async function doctorCmd(argv, opts = {}) {
|
|
8026
8186
|
const repoRoot = opts.cwd ?? process.cwd();
|
|
8187
|
+
const autoEnrich = argv.includes("--auto-enrich");
|
|
8027
8188
|
const results = [
|
|
8028
8189
|
...checkGoldenEvalDir(repoRoot),
|
|
8029
8190
|
...checkRubricPrompt(repoRoot),
|
|
@@ -8041,9 +8202,9 @@ async function doctorCmd(_argv, opts = {}) {
|
|
|
8041
8202
|
if (top)
|
|
8042
8203
|
console.log(` Top skill: ${top[0]} (${top[1].calls} calls, $${top[1].usd.toFixed(2)})`);
|
|
8043
8204
|
}
|
|
8044
|
-
const xeraDir =
|
|
8045
|
-
if (
|
|
8046
|
-
const ticketDirs =
|
|
8205
|
+
const xeraDir = join6(repoRoot, ".xera");
|
|
8206
|
+
if (existsSync7(xeraDir)) {
|
|
8207
|
+
const ticketDirs = readdirSync4(xeraDir, { withFileTypes: true }).filter((e) => e.isDirectory() && /^[A-Z]+-\d+$/.test(e.name));
|
|
8047
8208
|
if (ticketDirs.length > 0) {
|
|
8048
8209
|
const events = loadAllEvents(repoRoot);
|
|
8049
8210
|
const fetchedTickets = new Set(events.filter((e) => e.type === "ticket.fetched").map((e) => e.payload.ticketId));
|
|
@@ -8054,6 +8215,16 @@ async function doctorCmd(_argv, opts = {}) {
|
|
|
8054
8215
|
console.log(` These won't participate in v0.6.1+ features (TEST_OUTDATED, /xera-impact).`);
|
|
8055
8216
|
console.log(` Run: bun run xera:graph-backfill`);
|
|
8056
8217
|
console.log(` (Use --dry-run to preview.)`);
|
|
8218
|
+
if (autoEnrich) {
|
|
8219
|
+
console.log("[doctor] --auto-enrich: running backfill for unbackfilled tickets...");
|
|
8220
|
+
const { graphBackfillCmd: graphBackfillCmd2 } = await Promise.resolve().then(() => (init_graph_backfill(), exports_graph_backfill));
|
|
8221
|
+
const exitCode = await graphBackfillCmd2([]);
|
|
8222
|
+
if (exitCode === 0) {
|
|
8223
|
+
console.log(`[doctor] auto-enrich: backfilled ${unbackfilled.length} tickets`);
|
|
8224
|
+
} else {
|
|
8225
|
+
console.error("[doctor] auto-enrich: backfill failed");
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8057
8228
|
}
|
|
8058
8229
|
}
|
|
8059
8230
|
}
|
|
@@ -8067,26 +8238,26 @@ async function doctorCmd(_argv, opts = {}) {
|
|
|
8067
8238
|
}
|
|
8068
8239
|
|
|
8069
8240
|
// src/bin-internal/eval-deterministic.ts
|
|
8070
|
-
import { existsSync as
|
|
8071
|
-
import { join as
|
|
8241
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
8242
|
+
import { join as join8 } from "path";
|
|
8072
8243
|
import { validateGherkin } from "@xera-ai/web";
|
|
8073
8244
|
|
|
8074
8245
|
// src/eval/paths.ts
|
|
8075
|
-
import { join as
|
|
8246
|
+
import { join as join7 } from "path";
|
|
8076
8247
|
function resolveEvalPaths(cwd, runId) {
|
|
8077
|
-
const root =
|
|
8248
|
+
const root = join7(cwd, ".xera", "eval", runId);
|
|
8078
8249
|
return {
|
|
8079
8250
|
root,
|
|
8080
|
-
manifest:
|
|
8081
|
-
lock:
|
|
8082
|
-
deterministicScores:
|
|
8083
|
-
judgeScores:
|
|
8084
|
-
report:
|
|
8085
|
-
summary:
|
|
8086
|
-
inputsDir:
|
|
8087
|
-
actualDir:
|
|
8088
|
-
ticketInputsDir: (ticket) =>
|
|
8089
|
-
ticketActualDir: (ticket) =>
|
|
8251
|
+
manifest: join7(root, "manifest.json"),
|
|
8252
|
+
lock: join7(root, ".lock"),
|
|
8253
|
+
deterministicScores: join7(root, "deterministic-scores.json"),
|
|
8254
|
+
judgeScores: join7(root, "judge-scores.json"),
|
|
8255
|
+
report: join7(root, "report.md"),
|
|
8256
|
+
summary: join7(root, "summary.json"),
|
|
8257
|
+
inputsDir: join7(root, "inputs"),
|
|
8258
|
+
actualDir: join7(root, "actual"),
|
|
8259
|
+
ticketInputsDir: (ticket) => join7(root, "inputs", ticket),
|
|
8260
|
+
ticketActualDir: (ticket) => join7(root, "actual", ticket)
|
|
8090
8261
|
};
|
|
8091
8262
|
}
|
|
8092
8263
|
|
|
@@ -8171,11 +8342,11 @@ var SummarySchema = z2.object({
|
|
|
8171
8342
|
|
|
8172
8343
|
// src/bin-internal/eval-deterministic.ts
|
|
8173
8344
|
function checkFeatureFromStory(actualFeaturePath) {
|
|
8174
|
-
if (!
|
|
8345
|
+
if (!existsSync8(actualFeaturePath)) {
|
|
8175
8346
|
return { passed: false, checks: ["validate-feature"], error: "actual missing: test.feature" };
|
|
8176
8347
|
}
|
|
8177
8348
|
try {
|
|
8178
|
-
const r = validateGherkin(
|
|
8349
|
+
const r = validateGherkin(readFileSync7(actualFeaturePath, "utf8"));
|
|
8179
8350
|
if (r.ok)
|
|
8180
8351
|
return { passed: true, checks: ["validate-feature"] };
|
|
8181
8352
|
return {
|
|
@@ -8188,31 +8359,31 @@ function checkFeatureFromStory(actualFeaturePath) {
|
|
|
8188
8359
|
}
|
|
8189
8360
|
}
|
|
8190
8361
|
function checkScriptFromFeature(actualTicketDir) {
|
|
8191
|
-
const specPath =
|
|
8192
|
-
if (!
|
|
8362
|
+
const specPath = join8(actualTicketDir, "spec.ts");
|
|
8363
|
+
if (!existsSync8(specPath)) {
|
|
8193
8364
|
return { passed: false, checks: ["file-presence"], error: "actual missing: spec.ts" };
|
|
8194
8365
|
}
|
|
8195
8366
|
return { passed: true, checks: ["file-presence"] };
|
|
8196
8367
|
}
|
|
8197
8368
|
function checkDiagnoseFailure(inputsTicketDir, actualTicketDir) {
|
|
8198
|
-
const inputPath =
|
|
8199
|
-
const actualPath =
|
|
8200
|
-
if (!
|
|
8369
|
+
const inputPath = join8(inputsTicketDir, "classifier-input.json");
|
|
8370
|
+
const actualPath = join8(actualTicketDir, "classification.json");
|
|
8371
|
+
if (!existsSync8(actualPath)) {
|
|
8201
8372
|
return {
|
|
8202
8373
|
passed: false,
|
|
8203
8374
|
checks: ["bucket-match"],
|
|
8204
8375
|
error: "actual missing: classification.json"
|
|
8205
8376
|
};
|
|
8206
8377
|
}
|
|
8207
|
-
if (!
|
|
8378
|
+
if (!existsSync8(inputPath)) {
|
|
8208
8379
|
return {
|
|
8209
8380
|
passed: false,
|
|
8210
8381
|
checks: ["bucket-match"],
|
|
8211
8382
|
error: "inputs missing: classifier-input.json"
|
|
8212
8383
|
};
|
|
8213
8384
|
}
|
|
8214
|
-
const golden = JSON.parse(
|
|
8215
|
-
const actual = JSON.parse(
|
|
8385
|
+
const golden = JSON.parse(readFileSync7(inputPath, "utf8"));
|
|
8386
|
+
const actual = JSON.parse(readFileSync7(actualPath, "utf8"));
|
|
8216
8387
|
const goldScens = golden.scenarios ?? [];
|
|
8217
8388
|
const actScens = actual.scenarios ?? [];
|
|
8218
8389
|
const mismatches = [];
|
|
@@ -8242,11 +8413,11 @@ async function evalDeterministicCmd(argv, opts = {}) {
|
|
|
8242
8413
|
return 1;
|
|
8243
8414
|
}
|
|
8244
8415
|
const paths = resolveEvalPaths(cwd, runId);
|
|
8245
|
-
if (!
|
|
8416
|
+
if (!existsSync8(paths.manifest)) {
|
|
8246
8417
|
console.error(`[xera:eval-deterministic] missing manifest.json at ${paths.manifest}`);
|
|
8247
8418
|
return 1;
|
|
8248
8419
|
}
|
|
8249
|
-
const manifest = ManifestSchema.parse(JSON.parse(
|
|
8420
|
+
const manifest = ManifestSchema.parse(JSON.parse(readFileSync7(paths.manifest, "utf8")));
|
|
8250
8421
|
const entries = [];
|
|
8251
8422
|
for (const [ticket, ticketStages] of Object.entries(manifest.ticket_stages)) {
|
|
8252
8423
|
for (const stage of ticketStages) {
|
|
@@ -8254,7 +8425,7 @@ async function evalDeterministicCmd(argv, opts = {}) {
|
|
|
8254
8425
|
const actualDir = paths.ticketActualDir(ticket);
|
|
8255
8426
|
let result;
|
|
8256
8427
|
if (stage === "feature-from-story") {
|
|
8257
|
-
result = checkFeatureFromStory(
|
|
8428
|
+
result = checkFeatureFromStory(join8(actualDir, "test.feature"));
|
|
8258
8429
|
} else if (stage === "script-from-feature") {
|
|
8259
8430
|
result = checkScriptFromFeature(actualDir);
|
|
8260
8431
|
} else {
|
|
@@ -8281,13 +8452,13 @@ async function evalDeterministicCmd(argv, opts = {}) {
|
|
|
8281
8452
|
// src/bin-internal/eval-prepare.ts
|
|
8282
8453
|
import {
|
|
8283
8454
|
copyFileSync,
|
|
8284
|
-
existsSync as
|
|
8455
|
+
existsSync as existsSync10,
|
|
8285
8456
|
mkdirSync as mkdirSync4,
|
|
8286
|
-
readdirSync as
|
|
8287
|
-
readFileSync as
|
|
8457
|
+
readdirSync as readdirSync5,
|
|
8458
|
+
readFileSync as readFileSync9,
|
|
8288
8459
|
writeFileSync as writeFileSync4
|
|
8289
8460
|
} from "fs";
|
|
8290
|
-
import { join as
|
|
8461
|
+
import { join as join9 } from "path";
|
|
8291
8462
|
|
|
8292
8463
|
// src/eval/run-id.ts
|
|
8293
8464
|
import { execSync } from "child_process";
|
|
@@ -8312,11 +8483,11 @@ function generateRunId(opts = {}) {
|
|
|
8312
8483
|
}
|
|
8313
8484
|
|
|
8314
8485
|
// src/lock/file-lock.ts
|
|
8315
|
-
import { existsSync as
|
|
8486
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8316
8487
|
import { hostname } from "os";
|
|
8317
8488
|
import { dirname as dirname2 } from "path";
|
|
8318
8489
|
function acquireLock(path, runId) {
|
|
8319
|
-
if (
|
|
8490
|
+
if (existsSync9(path))
|
|
8320
8491
|
return false;
|
|
8321
8492
|
mkdirSync3(dirname2(path), { recursive: true });
|
|
8322
8493
|
const data = {
|
|
@@ -8333,13 +8504,13 @@ function acquireLock(path, runId) {
|
|
|
8333
8504
|
}
|
|
8334
8505
|
}
|
|
8335
8506
|
function releaseLock(path) {
|
|
8336
|
-
if (
|
|
8507
|
+
if (existsSync9(path))
|
|
8337
8508
|
unlinkSync(path);
|
|
8338
8509
|
}
|
|
8339
8510
|
function readLock(path) {
|
|
8340
|
-
if (!
|
|
8511
|
+
if (!existsSync9(path))
|
|
8341
8512
|
return null;
|
|
8342
|
-
return JSON.parse(
|
|
8513
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
8343
8514
|
}
|
|
8344
8515
|
function isLockStale(path) {
|
|
8345
8516
|
const lock = readLock(path);
|
|
@@ -8360,7 +8531,7 @@ function forceUnlock(path) {
|
|
|
8360
8531
|
}
|
|
8361
8532
|
|
|
8362
8533
|
// src/bin-internal/eval-prepare.ts
|
|
8363
|
-
function
|
|
8534
|
+
function parseFlags2(argv) {
|
|
8364
8535
|
const flags = { force: false, only_prompt: null, only_ticket: null };
|
|
8365
8536
|
for (const arg of argv) {
|
|
8366
8537
|
if (arg === "--force")
|
|
@@ -8380,42 +8551,42 @@ function parseFlags(argv) {
|
|
|
8380
8551
|
return flags;
|
|
8381
8552
|
}
|
|
8382
8553
|
function readPromptVersion(repoRoot, name) {
|
|
8383
|
-
const path =
|
|
8384
|
-
if (!
|
|
8554
|
+
const path = join9(repoRoot, "packages/prompts", `${name}.md`);
|
|
8555
|
+
if (!existsSync10(path))
|
|
8385
8556
|
return "0.0.0";
|
|
8386
|
-
const text =
|
|
8557
|
+
const text = readFileSync9(path, "utf8");
|
|
8387
8558
|
const m = /^version:\s*(\S+)\s*$/m.exec(text);
|
|
8388
8559
|
return m?.[1] ?? "0.0.0";
|
|
8389
8560
|
}
|
|
8390
8561
|
function discoverEvalTickets(repoRoot) {
|
|
8391
|
-
const root =
|
|
8392
|
-
if (!
|
|
8562
|
+
const root = join9(repoRoot, "fixtures/golden-eval");
|
|
8563
|
+
if (!existsSync10(root))
|
|
8393
8564
|
return [];
|
|
8394
8565
|
const out = [];
|
|
8395
|
-
for (const entry of
|
|
8566
|
+
for (const entry of readdirSync5(root, { withFileTypes: true })) {
|
|
8396
8567
|
if (!entry.isDirectory())
|
|
8397
8568
|
continue;
|
|
8398
8569
|
if (entry.name === "README.md" || entry.name.startsWith("."))
|
|
8399
8570
|
continue;
|
|
8400
|
-
const dir =
|
|
8401
|
-
const metaPath =
|
|
8402
|
-
if (!
|
|
8571
|
+
const dir = join9(root, entry.name);
|
|
8572
|
+
const metaPath = join9(dir, "meta.json");
|
|
8573
|
+
if (!existsSync10(metaPath))
|
|
8403
8574
|
continue;
|
|
8404
|
-
const meta = JSON.parse(
|
|
8575
|
+
const meta = JSON.parse(readFileSync9(metaPath, "utf8"));
|
|
8405
8576
|
out.push({ id: meta.id, dir, stages: meta.stages });
|
|
8406
8577
|
}
|
|
8407
8578
|
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
8408
8579
|
}
|
|
8409
8580
|
function discoverClassifierTickets(repoRoot) {
|
|
8410
|
-
const root =
|
|
8411
|
-
if (!
|
|
8581
|
+
const root = join9(repoRoot, "fixtures/golden-tickets");
|
|
8582
|
+
if (!existsSync10(root))
|
|
8412
8583
|
return [];
|
|
8413
8584
|
const out = [];
|
|
8414
|
-
for (const entry of
|
|
8585
|
+
for (const entry of readdirSync5(root, { withFileTypes: true })) {
|
|
8415
8586
|
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
8416
8587
|
continue;
|
|
8417
|
-
const path =
|
|
8418
|
-
const data = JSON.parse(
|
|
8588
|
+
const path = join9(root, entry.name);
|
|
8589
|
+
const data = JSON.parse(readFileSync9(path, "utf8"));
|
|
8419
8590
|
if (typeof data.ticket === "string")
|
|
8420
8591
|
out.push({ id: data.ticket, path });
|
|
8421
8592
|
}
|
|
@@ -8423,7 +8594,7 @@ function discoverClassifierTickets(repoRoot) {
|
|
|
8423
8594
|
}
|
|
8424
8595
|
async function evalPrepareCmd(argv, opts = {}) {
|
|
8425
8596
|
const repoRoot = opts.cwd ?? process.cwd();
|
|
8426
|
-
const flags =
|
|
8597
|
+
const flags = parseFlags2(argv);
|
|
8427
8598
|
if ("error" in flags) {
|
|
8428
8599
|
console.error(`[xera:eval-prepare] ${flags.error}`);
|
|
8429
8600
|
return 1;
|
|
@@ -8474,7 +8645,7 @@ async function evalPrepareCmd(argv, opts = {}) {
|
|
|
8474
8645
|
...opts.getGitSha ? { getGitSha: opts.getGitSha } : {}
|
|
8475
8646
|
});
|
|
8476
8647
|
const paths = resolveEvalPaths(repoRoot, runId);
|
|
8477
|
-
if (
|
|
8648
|
+
if (existsSync10(paths.root) && !flags.force) {
|
|
8478
8649
|
console.error(`[xera:eval-prepare] run dir already exists: ${paths.root}. Pass --force to re-run.`);
|
|
8479
8650
|
return 1;
|
|
8480
8651
|
}
|
|
@@ -8486,13 +8657,13 @@ async function evalPrepareCmd(argv, opts = {}) {
|
|
|
8486
8657
|
const evalT = evalTickets.find((t) => t.id === ticket);
|
|
8487
8658
|
const classT = classifierTickets.find((t) => t.id === ticket);
|
|
8488
8659
|
if (evalT) {
|
|
8489
|
-
copyFileSync(
|
|
8490
|
-
const featurePath =
|
|
8491
|
-
if (
|
|
8492
|
-
copyFileSync(featurePath,
|
|
8660
|
+
copyFileSync(join9(evalT.dir, "story.md"), join9(ticketInputs, "story.md"));
|
|
8661
|
+
const featurePath = join9(evalT.dir, "golden/test.feature");
|
|
8662
|
+
if (existsSync10(featurePath))
|
|
8663
|
+
copyFileSync(featurePath, join9(ticketInputs, "test.feature"));
|
|
8493
8664
|
}
|
|
8494
8665
|
if (classT) {
|
|
8495
|
-
copyFileSync(classT.path,
|
|
8666
|
+
copyFileSync(classT.path, join9(ticketInputs, "classifier-input.json"));
|
|
8496
8667
|
}
|
|
8497
8668
|
}
|
|
8498
8669
|
const now = (opts.now ?? (() => new Date))();
|
|
@@ -8528,7 +8699,7 @@ async function evalPrepareCmd(argv, opts = {}) {
|
|
|
8528
8699
|
}
|
|
8529
8700
|
|
|
8530
8701
|
// src/bin-internal/eval-report.ts
|
|
8531
|
-
import { existsSync as
|
|
8702
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
8532
8703
|
function scoreJudgment(j) {
|
|
8533
8704
|
const nonNa = j.dimensions.filter((d) => d.verdict !== "NA");
|
|
8534
8705
|
if (nonNa.length === 0)
|
|
@@ -8583,22 +8754,22 @@ async function evalReportCmd(argv, opts = {}) {
|
|
|
8583
8754
|
return 1;
|
|
8584
8755
|
}
|
|
8585
8756
|
const paths = resolveEvalPaths(cwd, runId);
|
|
8586
|
-
if (!
|
|
8757
|
+
if (!existsSync11(paths.manifest)) {
|
|
8587
8758
|
console.error(`[xera:eval-report] missing manifest.json at ${paths.manifest}`);
|
|
8588
8759
|
return 1;
|
|
8589
8760
|
}
|
|
8590
|
-
const manifest = ManifestSchema.parse(JSON.parse(
|
|
8761
|
+
const manifest = ManifestSchema.parse(JSON.parse(readFileSync10(paths.manifest, "utf8")));
|
|
8591
8762
|
try {
|
|
8592
8763
|
let det;
|
|
8593
8764
|
let judge;
|
|
8594
8765
|
try {
|
|
8595
|
-
det = DeterministicScoresSchema.parse(JSON.parse(
|
|
8766
|
+
det = DeterministicScoresSchema.parse(JSON.parse(readFileSync10(paths.deterministicScores, "utf8")));
|
|
8596
8767
|
} catch (err) {
|
|
8597
8768
|
console.error(`[xera:eval-report] invalid deterministic-scores.json: ${err.message}`);
|
|
8598
8769
|
return 2;
|
|
8599
8770
|
}
|
|
8600
8771
|
try {
|
|
8601
|
-
judge = JudgeScoresSchema.parse(JSON.parse(
|
|
8772
|
+
judge = JudgeScoresSchema.parse(JSON.parse(readFileSync10(paths.judgeScores, "utf8")));
|
|
8602
8773
|
} catch (err) {
|
|
8603
8774
|
console.error(`[xera:eval-report] invalid judge-scores.json: ${err.message}`);
|
|
8604
8775
|
return 2;
|
|
@@ -8670,40 +8841,40 @@ async function evalReportCmd(argv, opts = {}) {
|
|
|
8670
8841
|
}
|
|
8671
8842
|
|
|
8672
8843
|
// src/bin-internal/exec.ts
|
|
8673
|
-
import { existsSync as
|
|
8674
|
-
import { join as
|
|
8844
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
|
|
8845
|
+
import { join as join13 } from "path";
|
|
8675
8846
|
import { chromium } from "@playwright/test";
|
|
8676
8847
|
import { runAuthSetup, runPlaywright, stagePlaywrightState } from "@xera-ai/web";
|
|
8677
8848
|
|
|
8678
8849
|
// src/artifact/paths.ts
|
|
8679
|
-
import { join as
|
|
8850
|
+
import { join as join10 } from "path";
|
|
8680
8851
|
var TICKET_RE = /^[A-Z][A-Z0-9_]*-\d+$|^SAMPLE-\d+$/;
|
|
8681
8852
|
function resolveArtifactPaths(repoRoot, ticket) {
|
|
8682
8853
|
if (!TICKET_RE.test(ticket)) {
|
|
8683
8854
|
throw new Error(`Invalid ticket key: "${ticket}" (expected e.g. JIRA-123 or SAMPLE-001)`);
|
|
8684
8855
|
}
|
|
8685
|
-
const ticketDir =
|
|
8856
|
+
const ticketDir = join10(repoRoot, ".xera", ticket);
|
|
8686
8857
|
return {
|
|
8687
8858
|
ticketDir,
|
|
8688
|
-
storyPath:
|
|
8689
|
-
featurePath:
|
|
8690
|
-
specPath:
|
|
8691
|
-
pageObjectsDir:
|
|
8692
|
-
runsDir:
|
|
8693
|
-
metaPath:
|
|
8694
|
-
statusPath:
|
|
8695
|
-
logPath:
|
|
8696
|
-
lockPath:
|
|
8697
|
-
authDir:
|
|
8859
|
+
storyPath: join10(ticketDir, "story.md"),
|
|
8860
|
+
featurePath: join10(ticketDir, "test.feature"),
|
|
8861
|
+
specPath: join10(ticketDir, "spec.ts"),
|
|
8862
|
+
pageObjectsDir: join10(ticketDir, "page-objects"),
|
|
8863
|
+
runsDir: join10(ticketDir, "runs"),
|
|
8864
|
+
metaPath: join10(ticketDir, "meta.json"),
|
|
8865
|
+
statusPath: join10(ticketDir, "status.json"),
|
|
8866
|
+
logPath: join10(ticketDir, "xera.log"),
|
|
8867
|
+
lockPath: join10(ticketDir, ".lock"),
|
|
8868
|
+
authDir: join10(repoRoot, ".xera", ".auth"),
|
|
8698
8869
|
runPath: (runId) => {
|
|
8699
|
-
const runDir =
|
|
8870
|
+
const runDir = join10(ticketDir, "runs", runId);
|
|
8700
8871
|
return {
|
|
8701
8872
|
runDir,
|
|
8702
|
-
reportJsonPath:
|
|
8703
|
-
tracePath:
|
|
8704
|
-
normalizedPath:
|
|
8705
|
-
screenshotsDir:
|
|
8706
|
-
videoDir:
|
|
8873
|
+
reportJsonPath: join10(runDir, "report.json"),
|
|
8874
|
+
tracePath: join10(runDir, "trace.zip"),
|
|
8875
|
+
normalizedPath: join10(runDir, "normalized.json"),
|
|
8876
|
+
screenshotsDir: join10(runDir, "screenshots"),
|
|
8877
|
+
videoDir: join10(runDir, "videos")
|
|
8707
8878
|
};
|
|
8708
8879
|
}
|
|
8709
8880
|
};
|
|
@@ -8714,7 +8885,7 @@ function generateRunId2(now = new Date) {
|
|
|
8714
8885
|
|
|
8715
8886
|
// src/auth/refresh.ts
|
|
8716
8887
|
var RE = /^(\d+)([hms])$/;
|
|
8717
|
-
function
|
|
8888
|
+
function parseDuration2(d) {
|
|
8718
8889
|
const m = RE.exec(d);
|
|
8719
8890
|
if (!m)
|
|
8720
8891
|
throw new Error(`Bad duration "${d}" \u2014 expected e.g. "8h", "30m", "45s"`);
|
|
@@ -8729,8 +8900,8 @@ function parseDuration(d) {
|
|
|
8729
8900
|
function needsRefresh(entry, policy, now = new Date) {
|
|
8730
8901
|
if (!entry)
|
|
8731
8902
|
return true;
|
|
8732
|
-
const ttlMs =
|
|
8733
|
-
const bufMs =
|
|
8903
|
+
const ttlMs = parseDuration2(policy.ttl);
|
|
8904
|
+
const bufMs = parseDuration2(policy.refreshBuffer);
|
|
8734
8905
|
const createdAt = new Date(entry.created_at).getTime();
|
|
8735
8906
|
if (now.getTime() - createdAt > ttlMs)
|
|
8736
8907
|
return true;
|
|
@@ -8741,8 +8912,8 @@ function needsRefresh(entry, policy, now = new Date) {
|
|
|
8741
8912
|
}
|
|
8742
8913
|
|
|
8743
8914
|
// src/auth/state.ts
|
|
8744
|
-
import { existsSync as
|
|
8745
|
-
import { join as
|
|
8915
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
8916
|
+
import { join as join11 } from "path";
|
|
8746
8917
|
import { z as z3 } from "zod";
|
|
8747
8918
|
|
|
8748
8919
|
// src/auth/encrypt.ts
|
|
@@ -8808,7 +8979,7 @@ var AuthStateEntrySchema = z3.object({
|
|
|
8808
8979
|
payload: z3.record(z3.string(), z3.unknown())
|
|
8809
8980
|
});
|
|
8810
8981
|
function pathFor(authDir, role) {
|
|
8811
|
-
return
|
|
8982
|
+
return join11(authDir, `${role}.json`);
|
|
8812
8983
|
}
|
|
8813
8984
|
function writeAuthState(authDir, entry) {
|
|
8814
8985
|
mkdirSync5(authDir, { recursive: true });
|
|
@@ -8817,16 +8988,16 @@ function writeAuthState(authDir, entry) {
|
|
|
8817
8988
|
}
|
|
8818
8989
|
function readAuthState(authDir, role) {
|
|
8819
8990
|
const p = pathFor(authDir, role);
|
|
8820
|
-
if (!
|
|
8991
|
+
if (!existsSync12(p))
|
|
8821
8992
|
return null;
|
|
8822
|
-
const txt =
|
|
8993
|
+
const txt = readFileSync11(p, "utf8");
|
|
8823
8994
|
const plain = decrypt(txt, resolveAuthKey());
|
|
8824
8995
|
return AuthStateEntrySchema.parse(JSON.parse(plain));
|
|
8825
8996
|
}
|
|
8826
8997
|
|
|
8827
8998
|
// src/config/load.ts
|
|
8828
|
-
import { existsSync as
|
|
8829
|
-
import { join as
|
|
8999
|
+
import { existsSync as existsSync13 } from "fs";
|
|
9000
|
+
import { join as join12 } from "path";
|
|
8830
9001
|
import { pathToFileURL } from "url";
|
|
8831
9002
|
|
|
8832
9003
|
// src/config/schema.ts
|
|
@@ -8885,7 +9056,7 @@ var ReportingSchema = z4.object({
|
|
|
8885
9056
|
var RunSchema = z4.object({
|
|
8886
9057
|
autoImpact: z4.object({
|
|
8887
9058
|
enabled: z4.boolean().default(true),
|
|
8888
|
-
threshold: z4.number().nonnegative().default(
|
|
9059
|
+
threshold: z4.number().nonnegative().default(8)
|
|
8889
9060
|
}).prefault({})
|
|
8890
9061
|
}).prefault({});
|
|
8891
9062
|
var XeraConfigSchema = z4.object({
|
|
@@ -8899,8 +9070,8 @@ var XeraConfigSchema = z4.object({
|
|
|
8899
9070
|
|
|
8900
9071
|
// src/config/load.ts
|
|
8901
9072
|
async function loadConfig(cwd) {
|
|
8902
|
-
const path =
|
|
8903
|
-
if (!
|
|
9073
|
+
const path = join12(cwd, "xera.config.ts");
|
|
9074
|
+
if (!existsSync13(path)) {
|
|
8904
9075
|
throw new Error(`xera.config.ts not found in ${cwd}`);
|
|
8905
9076
|
}
|
|
8906
9077
|
const mod = await import(pathToFileURL(path).href);
|
|
@@ -8909,7 +9080,7 @@ async function loadConfig(cwd) {
|
|
|
8909
9080
|
}
|
|
8910
9081
|
|
|
8911
9082
|
// src/logging/ndjson-logger.ts
|
|
8912
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
9083
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync12 } from "fs";
|
|
8913
9084
|
import { dirname as dirname3 } from "path";
|
|
8914
9085
|
|
|
8915
9086
|
class NdjsonLogger {
|
|
@@ -8924,9 +9095,9 @@ class NdjsonLogger {
|
|
|
8924
9095
|
`);
|
|
8925
9096
|
}
|
|
8926
9097
|
static readAll(path) {
|
|
8927
|
-
if (!
|
|
9098
|
+
if (!existsSync14(path))
|
|
8928
9099
|
return [];
|
|
8929
|
-
const txt =
|
|
9100
|
+
const txt = readFileSync12(path, "utf8").trim();
|
|
8930
9101
|
if (!txt)
|
|
8931
9102
|
return [];
|
|
8932
9103
|
return txt.split(`
|
|
@@ -8941,6 +9112,8 @@ async function execCmd(argv) {
|
|
|
8941
9112
|
console.error("[xera:exec] usage: exec <TICKET>");
|
|
8942
9113
|
return 1;
|
|
8943
9114
|
}
|
|
9115
|
+
const grepIdx = argv.indexOf("--grep");
|
|
9116
|
+
const grep = grepIdx > -1 ? argv[grepIdx + 1] : undefined;
|
|
8944
9117
|
const cwd = process.cwd();
|
|
8945
9118
|
const config = await loadConfig(cwd);
|
|
8946
9119
|
const paths = resolveArtifactPaths(cwd, ticket);
|
|
@@ -8977,7 +9150,7 @@ async function execCmd(argv) {
|
|
|
8977
9150
|
await runAuthSetup({
|
|
8978
9151
|
role: roleName,
|
|
8979
9152
|
creds: { email, password },
|
|
8980
|
-
setupScriptPath:
|
|
9153
|
+
setupScriptPath: join13(cwd, config.web.auth.setupScript),
|
|
8981
9154
|
authDir: paths.authDir,
|
|
8982
9155
|
browser
|
|
8983
9156
|
});
|
|
@@ -8995,8 +9168,8 @@ async function execCmd(argv) {
|
|
|
8995
9168
|
}
|
|
8996
9169
|
}
|
|
8997
9170
|
}
|
|
8998
|
-
const cfgPath =
|
|
8999
|
-
if (!
|
|
9171
|
+
const cfgPath = join13(cwd, "playwright.config.ts");
|
|
9172
|
+
if (!existsSync15(cfgPath)) {
|
|
9000
9173
|
console.error(`[xera:exec] missing ${cfgPath}. Run \`xera init\` to scaffold it, then re-run.`);
|
|
9001
9174
|
return 1;
|
|
9002
9175
|
}
|
|
@@ -9004,7 +9177,7 @@ async function execCmd(argv) {
|
|
|
9004
9177
|
mkdirSync7(runDir, { recursive: true });
|
|
9005
9178
|
const envName = process.env.XERA_ENV ?? config.web.defaultEnv;
|
|
9006
9179
|
const baseURL = config.web.baseUrl[envName] ?? config.web.baseUrl[config.web.defaultEnv];
|
|
9007
|
-
const reportJsonPath =
|
|
9180
|
+
const reportJsonPath = join13(runDir, "report.json");
|
|
9008
9181
|
log.log({ step: "exec.start", runId, env: envName, baseURL });
|
|
9009
9182
|
const r = await runPlaywright({
|
|
9010
9183
|
specPath: paths.specPath,
|
|
@@ -9014,7 +9187,8 @@ async function execCmd(argv) {
|
|
|
9014
9187
|
XERA_BASE_URL: baseURL,
|
|
9015
9188
|
XERA_ENV: envName,
|
|
9016
9189
|
PLAYWRIGHT_JSON_OUTPUT_NAME: reportJsonPath
|
|
9017
|
-
}
|
|
9190
|
+
},
|
|
9191
|
+
...grep && { grep }
|
|
9018
9192
|
});
|
|
9019
9193
|
log.log({ step: "exec.done", runId, exit: r.exitCode, ms: Date.now() - t0 });
|
|
9020
9194
|
console.log(`[xera:exec] runId=${runId} outcome=${r.outcome}`);
|
|
@@ -9029,22 +9203,22 @@ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
|
9029
9203
|
import { dirname as dirname5 } from "path";
|
|
9030
9204
|
|
|
9031
9205
|
// src/artifact/hash.ts
|
|
9032
|
-
import { createHash as
|
|
9033
|
-
import { existsSync as
|
|
9206
|
+
import { createHash as createHash4 } from "crypto";
|
|
9207
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
9034
9208
|
function hashString(s) {
|
|
9035
|
-
return `sha256:${
|
|
9209
|
+
return `sha256:${createHash4("sha256").update(s).digest("hex")}`;
|
|
9036
9210
|
}
|
|
9037
9211
|
function hashFile(path) {
|
|
9038
|
-
return hashString(
|
|
9212
|
+
return hashString(readFileSync13(path, "utf8"));
|
|
9039
9213
|
}
|
|
9040
9214
|
function hashFileIfExists(path) {
|
|
9041
|
-
if (!
|
|
9215
|
+
if (!existsSync16(path))
|
|
9042
9216
|
return null;
|
|
9043
9217
|
return hashFile(path);
|
|
9044
9218
|
}
|
|
9045
9219
|
|
|
9046
9220
|
// src/artifact/meta.ts
|
|
9047
|
-
import { existsSync as
|
|
9221
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
9048
9222
|
import { dirname as dirname4 } from "path";
|
|
9049
9223
|
import { z as z5 } from "zod";
|
|
9050
9224
|
var MetaJsonSchema = z5.object({
|
|
@@ -9062,9 +9236,9 @@ var MetaJsonSchema = z5.object({
|
|
|
9062
9236
|
script_warnings: z5.array(z5.string()).optional()
|
|
9063
9237
|
});
|
|
9064
9238
|
function readMeta(path) {
|
|
9065
|
-
if (!
|
|
9239
|
+
if (!existsSync17(path))
|
|
9066
9240
|
return null;
|
|
9067
|
-
return MetaJsonSchema.parse(JSON.parse(
|
|
9241
|
+
return MetaJsonSchema.parse(JSON.parse(readFileSync14(path, "utf8")));
|
|
9068
9242
|
}
|
|
9069
9243
|
function writeMeta(path, meta) {
|
|
9070
9244
|
mkdirSync8(dirname4(path), { recursive: true });
|
|
@@ -9081,32 +9255,32 @@ function updateMeta(path, patch) {
|
|
|
9081
9255
|
}
|
|
9082
9256
|
|
|
9083
9257
|
// src/jira/mcp-backend.ts
|
|
9084
|
-
import { existsSync as
|
|
9258
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
9085
9259
|
import { tmpdir } from "os";
|
|
9086
|
-
import { join as
|
|
9260
|
+
import { join as join14 } from "path";
|
|
9087
9261
|
var MCP_ENV = "XERA_MCP_JIRA";
|
|
9088
9262
|
async function createMcpBackend(_baseUrl) {
|
|
9089
9263
|
if (process.env[MCP_ENV] !== "1")
|
|
9090
9264
|
return null;
|
|
9091
|
-
const tmpDir =
|
|
9265
|
+
const tmpDir = join14(tmpdir(), "xera-mcp");
|
|
9092
9266
|
mkdirSync9(tmpDir, { recursive: true });
|
|
9093
9267
|
return {
|
|
9094
9268
|
backend: "mcp",
|
|
9095
9269
|
async fetchTicket(key, _fields) {
|
|
9096
|
-
const cachePath =
|
|
9097
|
-
if (!
|
|
9270
|
+
const cachePath = join14(tmpDir, `${key}.json`);
|
|
9271
|
+
if (!existsSync18(cachePath)) {
|
|
9098
9272
|
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.`);
|
|
9099
9273
|
}
|
|
9100
|
-
const parsed = JSON.parse(
|
|
9274
|
+
const parsed = JSON.parse(readFileSync15(cachePath, "utf8"));
|
|
9101
9275
|
return parsed;
|
|
9102
9276
|
},
|
|
9103
9277
|
async postComment(key, body) {
|
|
9104
|
-
const outPath =
|
|
9278
|
+
const outPath = join14(tmpDir, `${key}.comment.json`);
|
|
9105
9279
|
writeFileSync8(outPath, JSON.stringify({ key, body }));
|
|
9106
9280
|
return { id: "mcp-pending" };
|
|
9107
9281
|
},
|
|
9108
9282
|
async transitionStatus(key, statusName) {
|
|
9109
|
-
const outPath =
|
|
9283
|
+
const outPath = join14(tmpDir, `${key}.transition.json`);
|
|
9110
9284
|
writeFileSync8(outPath, JSON.stringify({ key, statusName }));
|
|
9111
9285
|
},
|
|
9112
9286
|
async listFields(_sampleKey) {
|
|
@@ -9275,49 +9449,8 @@ function renderStory(t) {
|
|
|
9275
9449
|
`);
|
|
9276
9450
|
}
|
|
9277
9451
|
|
|
9278
|
-
// src/bin-internal/
|
|
9279
|
-
|
|
9280
|
-
import { existsSync as existsSync18, readdirSync as readdirSync5 } from "fs";
|
|
9281
|
-
import { join as join14 } from "path";
|
|
9282
|
-
async function backfillTicket(repoRoot, ticket, dryRun) {
|
|
9283
|
-
const storyPath = join14(repoRoot, ".xera", ticket, "story.md");
|
|
9284
|
-
if (!existsSync18(storyPath))
|
|
9285
|
-
return 0;
|
|
9286
|
-
const { recordFetch: recordFetch2 } = await Promise.resolve().then(() => (init_graph_record(), exports_graph_record));
|
|
9287
|
-
if (dryRun) {
|
|
9288
|
-
console.log(`[backfill dry-run] would backfill ${ticket}`);
|
|
9289
|
-
return 0;
|
|
9290
|
-
}
|
|
9291
|
-
await recordScriptImpl(repoRoot, ticket);
|
|
9292
|
-
await recordFetch2(repoRoot, ticket);
|
|
9293
|
-
return 0;
|
|
9294
|
-
}
|
|
9295
|
-
async function graphBackfillCmd(argv) {
|
|
9296
|
-
const dryRun = argv.includes("--dry-run");
|
|
9297
|
-
const repoRoot = process.cwd();
|
|
9298
|
-
const xeraDir = join14(repoRoot, ".xera");
|
|
9299
|
-
if (!existsSync18(xeraDir)) {
|
|
9300
|
-
console.log("[backfill] no .xera/ directory");
|
|
9301
|
-
return 0;
|
|
9302
|
-
}
|
|
9303
|
-
const tickets = [];
|
|
9304
|
-
for (const entry of readdirSync5(xeraDir, { withFileTypes: true })) {
|
|
9305
|
-
if (!entry.isDirectory())
|
|
9306
|
-
continue;
|
|
9307
|
-
if (entry.name === "graph")
|
|
9308
|
-
continue;
|
|
9309
|
-
if (entry.name.startsWith("."))
|
|
9310
|
-
continue;
|
|
9311
|
-
if (!/^[A-Z]+-\d+$/.test(entry.name))
|
|
9312
|
-
continue;
|
|
9313
|
-
tickets.push(entry.name);
|
|
9314
|
-
}
|
|
9315
|
-
console.log(`[backfill] found ${tickets.length} tickets`);
|
|
9316
|
-
for (const t of tickets)
|
|
9317
|
-
await backfillTicket(repoRoot, t, dryRun);
|
|
9318
|
-
console.log(`[backfill] done`);
|
|
9319
|
-
return 0;
|
|
9320
|
-
}
|
|
9452
|
+
// src/bin-internal/index.ts
|
|
9453
|
+
init_graph_backfill();
|
|
9321
9454
|
|
|
9322
9455
|
// src/graph/enrich.ts
|
|
9323
9456
|
init_store();
|
|
@@ -9421,7 +9554,7 @@ function filterByTicket(snap, ticket) {
|
|
|
9421
9554
|
};
|
|
9422
9555
|
return out;
|
|
9423
9556
|
}
|
|
9424
|
-
function
|
|
9557
|
+
function renderText2(snap) {
|
|
9425
9558
|
const out = [];
|
|
9426
9559
|
out.push(`Graph snapshot \u2014 ${snap.event_count} events`);
|
|
9427
9560
|
out.push(`Tickets: ${Object.keys(snap.tickets).length}`);
|
|
@@ -9450,7 +9583,7 @@ async function graphQueryCmd(argv) {
|
|
|
9450
9583
|
if (format === "json")
|
|
9451
9584
|
process.stdout.write(JSON.stringify(snap, null, 2));
|
|
9452
9585
|
else
|
|
9453
|
-
process.stdout.write(
|
|
9586
|
+
process.stdout.write(renderText2(snap));
|
|
9454
9587
|
return 0;
|
|
9455
9588
|
}
|
|
9456
9589
|
|
|
@@ -11086,6 +11219,7 @@ async function validateFeatureCmd(argv) {
|
|
|
11086
11219
|
|
|
11087
11220
|
// src/bin-internal/index.ts
|
|
11088
11221
|
var COMMANDS = {
|
|
11222
|
+
disputes: disputesCmd,
|
|
11089
11223
|
doctor: doctorCmd,
|
|
11090
11224
|
"eval-deterministic": evalDeterministicCmd,
|
|
11091
11225
|
"eval-prepare": evalPrepareCmd,
|