@xera-ai/core 0.11.6 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/internal.ts +12 -4
- package/dist/bin/internal.js +440 -144
- package/dist/src/index.js +1 -1
- package/package.json +3 -3
- package/src/bin-internal/auth-setup.ts +60 -0
- package/src/bin-internal/explore-finalize.ts +152 -0
- package/src/bin-internal/explore-prepare.ts +142 -0
- package/src/bin-internal/index.ts +4 -0
- package/src/config/schema.ts +1 -1
package/dist/bin/internal.js
CHANGED
|
@@ -8559,6 +8559,7 @@ var init_graph_backfill = __esm(() => {
|
|
|
8559
8559
|
|
|
8560
8560
|
// bin/internal.ts
|
|
8561
8561
|
var import_dotenv = __toESM(require_main(), 1);
|
|
8562
|
+
import { existsSync as existsSync33 } from "fs";
|
|
8562
8563
|
|
|
8563
8564
|
// src/bin-internal/ac-coverage-backfill-finalize.ts
|
|
8564
8565
|
init_store();
|
|
@@ -8797,7 +8798,7 @@ var CoverageSchema = z3.object({
|
|
|
8797
8798
|
criticalAreas: z3.array(z3.string().regex(/^[a-z0-9-]+$/)).default([]),
|
|
8798
8799
|
autoSnapshotOnCoverage: z3.boolean().default(true)
|
|
8799
8800
|
}).prefault({});
|
|
8800
|
-
var XeraConfigSchema = z3.
|
|
8801
|
+
var XeraConfigSchema = z3.strictObject({
|
|
8801
8802
|
jira: JiraSchema,
|
|
8802
8803
|
web: WebSchema.optional(),
|
|
8803
8804
|
http: HttpSchema.optional(),
|
|
@@ -8852,6 +8853,46 @@ async function authSetupCmd(argv) {
|
|
|
8852
8853
|
}
|
|
8853
8854
|
const mod = await import(pathToFileURL2(authSetupScript).href);
|
|
8854
8855
|
let exitCode = 0;
|
|
8856
|
+
const shapeRequestsWeb = opts.shape === "all" || opts.shape === "web";
|
|
8857
|
+
const shapeRequestsHttp = opts.shape === "all" || opts.shape === "http";
|
|
8858
|
+
const explicit = opts.shape !== "all";
|
|
8859
|
+
if (shapeRequestsWeb && config.web && typeof mod.web !== "function") {
|
|
8860
|
+
console.error(`[xera:auth-setup] web adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`web\` export.
|
|
8861
|
+
` + ` Add: \`export const web = defineAuthSetup(async (page, role, creds) => { ... })\` \u2014 see docs/CONFIGURATION.md`);
|
|
8862
|
+
exitCode = 1;
|
|
8863
|
+
}
|
|
8864
|
+
if (shapeRequestsHttp && config.http && typeof mod.http !== "function") {
|
|
8865
|
+
console.error(`[xera:auth-setup] http adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`http\` export.
|
|
8866
|
+
` + ` Add: \`export const http = defineHttpAuthSetup(async (request, role, creds) => { ... })\` \u2014 see docs/CONFIGURATION.md`);
|
|
8867
|
+
exitCode = 1;
|
|
8868
|
+
}
|
|
8869
|
+
if (explicit && opts.shape === "web" && !config.web) {
|
|
8870
|
+
console.error(`[xera:auth-setup] --shape web requested, but xera.config.ts has no \`web\` block. Add a web: {...} block or use --shape http/all.`);
|
|
8871
|
+
exitCode = 1;
|
|
8872
|
+
}
|
|
8873
|
+
if (explicit && opts.shape === "http" && !config.http) {
|
|
8874
|
+
console.error(`[xera:auth-setup] --shape http requested, but xera.config.ts has no \`http\` block. Add an http: {...} block or use --shape web/all.`);
|
|
8875
|
+
exitCode = 1;
|
|
8876
|
+
}
|
|
8877
|
+
if (!config.web && !config.http) {
|
|
8878
|
+
console.error(`[xera:auth-setup] no \`web\` or \`http\` block found in xera.config.ts \u2014 nothing to authenticate.`);
|
|
8879
|
+
exitCode = 1;
|
|
8880
|
+
}
|
|
8881
|
+
if (opts.role !== undefined) {
|
|
8882
|
+
const webRoles = shapeRequestsWeb && config.web ? Object.keys(config.web.auth.roles) : [];
|
|
8883
|
+
const httpRoles = shapeRequestsHttp && config.http ? Object.keys(config.http.auth.roles) : [];
|
|
8884
|
+
const allRoles = Array.from(new Set([...webRoles, ...httpRoles]));
|
|
8885
|
+
if (allRoles.length > 0 && !allRoles.includes(opts.role)) {
|
|
8886
|
+
const detail = [];
|
|
8887
|
+
if (webRoles.length > 0)
|
|
8888
|
+
detail.push(`web roles: ${webRoles.join(", ")}`);
|
|
8889
|
+
if (httpRoles.length > 0)
|
|
8890
|
+
detail.push(`http roles: ${httpRoles.join(", ")}`);
|
|
8891
|
+
console.error(`[xera:auth-setup] unknown role '${opts.role}' \u2014 configured roles: ${allRoles.join(", ")}
|
|
8892
|
+
(${detail.join("; ")})`);
|
|
8893
|
+
return 1;
|
|
8894
|
+
}
|
|
8895
|
+
}
|
|
8855
8896
|
if ((opts.shape === "all" || opts.shape === "web") && config.web && typeof mod.web === "function") {
|
|
8856
8897
|
const webConfig = config.web;
|
|
8857
8898
|
const envName = process.env.XERA_ENV ?? webConfig.defaultEnv;
|
|
@@ -10544,21 +10585,270 @@ async function execCmd(argv) {
|
|
|
10544
10585
|
}
|
|
10545
10586
|
}
|
|
10546
10587
|
|
|
10588
|
+
// src/bin-internal/explore-finalize.ts
|
|
10589
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
|
|
10590
|
+
import { join as join18 } from "path";
|
|
10591
|
+
import { z as z7 } from "zod";
|
|
10592
|
+
var CATEGORY_ENUM = z7.enum([
|
|
10593
|
+
"negative",
|
|
10594
|
+
"boundary",
|
|
10595
|
+
"state-combination",
|
|
10596
|
+
"race",
|
|
10597
|
+
"error-recovery",
|
|
10598
|
+
"a11y",
|
|
10599
|
+
"security-smell",
|
|
10600
|
+
"non-functional"
|
|
10601
|
+
]);
|
|
10602
|
+
var ProposalsSchema = z7.object({
|
|
10603
|
+
proposals: z7.array(z7.object({
|
|
10604
|
+
id: z7.string().min(1),
|
|
10605
|
+
ticketId: z7.string().min(1),
|
|
10606
|
+
category: CATEGORY_ENUM,
|
|
10607
|
+
severity: z7.enum(["low", "medium", "high"]),
|
|
10608
|
+
title: z7.string().min(1),
|
|
10609
|
+
rationale: z7.string().min(1),
|
|
10610
|
+
gherkin: z7.string().min(1)
|
|
10611
|
+
}))
|
|
10612
|
+
});
|
|
10613
|
+
function parseArgs4(argv) {
|
|
10614
|
+
let ticket;
|
|
10615
|
+
let accept;
|
|
10616
|
+
for (let i = 0;i < argv.length; i++) {
|
|
10617
|
+
const a = argv[i];
|
|
10618
|
+
if (a === "--accept") {
|
|
10619
|
+
const v = argv[++i];
|
|
10620
|
+
if (v !== undefined)
|
|
10621
|
+
accept = v;
|
|
10622
|
+
} else if (a === "--help-stub") {} else if (a && !a.startsWith("--") && ticket === undefined) {
|
|
10623
|
+
ticket = a;
|
|
10624
|
+
} else {
|
|
10625
|
+
return { error: `unknown flag: ${a}` };
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
if (!ticket)
|
|
10629
|
+
return { error: "ticket key is required as a positional argument" };
|
|
10630
|
+
if (!accept)
|
|
10631
|
+
return { error: '--accept is required (comma-separated IDs, "all", or "high-only")' };
|
|
10632
|
+
return { ticket, accept };
|
|
10633
|
+
}
|
|
10634
|
+
function selectProposals(all, accept) {
|
|
10635
|
+
const trimmed = accept.trim();
|
|
10636
|
+
if (trimmed === "all")
|
|
10637
|
+
return all;
|
|
10638
|
+
if (trimmed === "high-only")
|
|
10639
|
+
return all.filter((p) => p.severity === "high");
|
|
10640
|
+
const ids = new Set(trimmed.split(",").map((s) => s.trim()).filter(Boolean));
|
|
10641
|
+
if (ids.size === 0)
|
|
10642
|
+
return { error: "no IDs supplied" };
|
|
10643
|
+
const picked = all.filter((p) => ids.has(p.id));
|
|
10644
|
+
const found = new Set(picked.map((p) => p.id));
|
|
10645
|
+
const missing = [...ids].filter((id) => !found.has(id));
|
|
10646
|
+
if (missing.length > 0)
|
|
10647
|
+
return { error: `unknown proposal IDs: ${missing.join(", ")}` };
|
|
10648
|
+
return picked;
|
|
10649
|
+
}
|
|
10650
|
+
function ensureFeatureHeader(ticketDir, ticket) {
|
|
10651
|
+
const explorePath = join18(ticketDir, "explore.feature");
|
|
10652
|
+
if (existsSync19(explorePath))
|
|
10653
|
+
return explorePath;
|
|
10654
|
+
const testFeaturePath = join18(ticketDir, "test.feature");
|
|
10655
|
+
let header;
|
|
10656
|
+
if (existsSync19(testFeaturePath)) {
|
|
10657
|
+
const testContent = readFileSync16(testFeaturePath, "utf8");
|
|
10658
|
+
const firstFeatureMatch = testContent.match(/^Feature:.*$/m);
|
|
10659
|
+
header = firstFeatureMatch ? `${firstFeatureMatch[0]} (adversarial)
|
|
10660
|
+
Adversarial scenarios beyond the acceptance criteria.
|
|
10661
|
+
Generated by /xera-explore \u2014 review before merging into test.feature.
|
|
10662
|
+
|
|
10663
|
+
` : `Feature: ${ticket} adversarial
|
|
10664
|
+
|
|
10665
|
+
`;
|
|
10666
|
+
} else {
|
|
10667
|
+
header = `Feature: ${ticket} adversarial
|
|
10668
|
+
Adversarial scenarios beyond the acceptance criteria.
|
|
10669
|
+
Generated by /xera-explore \u2014 review before merging into test.feature.
|
|
10670
|
+
|
|
10671
|
+
`;
|
|
10672
|
+
}
|
|
10673
|
+
writeFileSync10(explorePath, header);
|
|
10674
|
+
return explorePath;
|
|
10675
|
+
}
|
|
10676
|
+
function formatScenario(p) {
|
|
10677
|
+
const tags = `@adversarial @adversarial-${p.category} @severity-${p.severity}`;
|
|
10678
|
+
const rationaleComment = ` # ${p.rationale}`;
|
|
10679
|
+
const indented = p.gherkin.split(`
|
|
10680
|
+
`).map((line) => line.startsWith("Scenario:") ? ` ${line}` : line ? ` ${line}` : line).join(`
|
|
10681
|
+
`);
|
|
10682
|
+
return `
|
|
10683
|
+
${tags}
|
|
10684
|
+
${rationaleComment}
|
|
10685
|
+
${indented}
|
|
10686
|
+
`;
|
|
10687
|
+
}
|
|
10688
|
+
async function exploreFinalizeCmd(argv) {
|
|
10689
|
+
const parsed = parseArgs4(argv);
|
|
10690
|
+
if ("error" in parsed) {
|
|
10691
|
+
console.error(`[explore-finalize] ${parsed.error}`);
|
|
10692
|
+
return 1;
|
|
10693
|
+
}
|
|
10694
|
+
const cwd = process.cwd();
|
|
10695
|
+
const ticketDir = join18(cwd, ".xera", parsed.ticket);
|
|
10696
|
+
const proposalsPath = join18(ticketDir, "adversarial-proposals.json");
|
|
10697
|
+
if (!existsSync19(proposalsPath)) {
|
|
10698
|
+
console.error(`[explore-finalize] adversarial-proposals.json not found for ${parsed.ticket} \u2014 run /xera-explore Step 4 first`);
|
|
10699
|
+
return 2;
|
|
10700
|
+
}
|
|
10701
|
+
let proposals;
|
|
10702
|
+
try {
|
|
10703
|
+
const raw = JSON.parse(readFileSync16(proposalsPath, "utf8"));
|
|
10704
|
+
proposals = ProposalsSchema.parse(raw);
|
|
10705
|
+
} catch (e) {
|
|
10706
|
+
console.error(`[explore-finalize] invalid proposals JSON: ${e.message}`);
|
|
10707
|
+
return 1;
|
|
10708
|
+
}
|
|
10709
|
+
const picked = selectProposals(proposals.proposals, parsed.accept);
|
|
10710
|
+
if ("error" in picked) {
|
|
10711
|
+
console.error(`[explore-finalize] ${picked.error}`);
|
|
10712
|
+
return 1;
|
|
10713
|
+
}
|
|
10714
|
+
if (picked.length === 0) {
|
|
10715
|
+
console.error("[explore-finalize] no proposals matched the --accept filter");
|
|
10716
|
+
return 1;
|
|
10717
|
+
}
|
|
10718
|
+
const explorePath = ensureFeatureHeader(ticketDir, parsed.ticket);
|
|
10719
|
+
for (const p of picked) {
|
|
10720
|
+
appendFileSync3(explorePath, formatScenario(p));
|
|
10721
|
+
}
|
|
10722
|
+
console.log(`[explore-finalize] appended ${picked.length} scenario(s) to ${explorePath} (${picked.map((p) => p.id).join(", ")})`);
|
|
10723
|
+
return 0;
|
|
10724
|
+
}
|
|
10725
|
+
|
|
10726
|
+
// src/bin-internal/explore-prepare.ts
|
|
10727
|
+
import { existsSync as existsSync20, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
10728
|
+
import { join as join19 } from "path";
|
|
10729
|
+
var VALID_CATEGORIES = [
|
|
10730
|
+
"negative",
|
|
10731
|
+
"boundary",
|
|
10732
|
+
"state-combination",
|
|
10733
|
+
"race",
|
|
10734
|
+
"error-recovery",
|
|
10735
|
+
"a11y",
|
|
10736
|
+
"security-smell",
|
|
10737
|
+
"non-functional"
|
|
10738
|
+
];
|
|
10739
|
+
function parseArgs5(argv) {
|
|
10740
|
+
let ticket;
|
|
10741
|
+
let categoriesRaw = "";
|
|
10742
|
+
let userHint = "";
|
|
10743
|
+
for (let i = 0;i < argv.length; i++) {
|
|
10744
|
+
const a = argv[i];
|
|
10745
|
+
if (a === "--categories") {
|
|
10746
|
+
const v = argv[++i];
|
|
10747
|
+
if (v !== undefined)
|
|
10748
|
+
categoriesRaw = v;
|
|
10749
|
+
} else if (a === "--user-hint") {
|
|
10750
|
+
const v = argv[++i];
|
|
10751
|
+
if (v !== undefined)
|
|
10752
|
+
userHint = v;
|
|
10753
|
+
} else if (a === "--help-stub") {} else if (a && !a.startsWith("--") && ticket === undefined) {
|
|
10754
|
+
ticket = a;
|
|
10755
|
+
} else {
|
|
10756
|
+
return { error: `unknown flag: ${a}` };
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
if (!ticket)
|
|
10760
|
+
return { error: "ticket key is required as a positional argument" };
|
|
10761
|
+
const categoriesInclude = [];
|
|
10762
|
+
for (const slug of categoriesRaw.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
10763
|
+
if (!VALID_CATEGORIES.includes(slug)) {
|
|
10764
|
+
return { error: `invalid category: ${slug}` };
|
|
10765
|
+
}
|
|
10766
|
+
categoriesInclude.push(slug);
|
|
10767
|
+
}
|
|
10768
|
+
return { ticket, categoriesInclude, userHint };
|
|
10769
|
+
}
|
|
10770
|
+
function parseStoryMd(content) {
|
|
10771
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
10772
|
+
if (!fmMatch)
|
|
10773
|
+
return { summary: "", ac: [], body: content };
|
|
10774
|
+
const [, fm, body] = fmMatch;
|
|
10775
|
+
const summaryMatch = fm.match(/^summary:\s*(.+)$/m);
|
|
10776
|
+
const summary = summaryMatch?.[1]?.trim() ?? "";
|
|
10777
|
+
const ac = [];
|
|
10778
|
+
const acBlock = fm.match(/^ac:\s*\n((?:\s*-\s.+\n?)+)/m);
|
|
10779
|
+
if (acBlock) {
|
|
10780
|
+
for (const line of acBlock[1].split(`
|
|
10781
|
+
`)) {
|
|
10782
|
+
const m = line.match(/^\s*-\s*(.+)$/);
|
|
10783
|
+
if (m)
|
|
10784
|
+
ac.push(m[1].trim());
|
|
10785
|
+
}
|
|
10786
|
+
}
|
|
10787
|
+
return { summary, ac, body: body.trim() };
|
|
10788
|
+
}
|
|
10789
|
+
async function explorePrepareCmd(argv) {
|
|
10790
|
+
const parsed = parseArgs5(argv);
|
|
10791
|
+
if ("error" in parsed) {
|
|
10792
|
+
console.error(`[explore-prepare] ${parsed.error}`);
|
|
10793
|
+
return 1;
|
|
10794
|
+
}
|
|
10795
|
+
const cwd = process.cwd();
|
|
10796
|
+
const configPath = join19(cwd, "xera.config.ts");
|
|
10797
|
+
if (!existsSync20(configPath)) {
|
|
10798
|
+
console.error("[explore-prepare] xera.config.ts not found \u2014 run inside a xera project");
|
|
10799
|
+
return 2;
|
|
10800
|
+
}
|
|
10801
|
+
const ticketDir = join19(cwd, ".xera", parsed.ticket);
|
|
10802
|
+
const storyPath = join19(ticketDir, "story.md");
|
|
10803
|
+
if (!existsSync20(storyPath)) {
|
|
10804
|
+
console.error(`[explore-prepare] no story for ${parsed.ticket} \u2014 run /xera-fetch ${parsed.ticket} first`);
|
|
10805
|
+
return 2;
|
|
10806
|
+
}
|
|
10807
|
+
const story = readFileSync17(storyPath, "utf8");
|
|
10808
|
+
const { summary, ac, body } = parseStoryMd(story);
|
|
10809
|
+
let adapter = "web";
|
|
10810
|
+
const metaPath = join19(ticketDir, "meta.json");
|
|
10811
|
+
if (existsSync20(metaPath)) {
|
|
10812
|
+
try {
|
|
10813
|
+
const meta = JSON.parse(readFileSync17(metaPath, "utf8"));
|
|
10814
|
+
if (meta.adapter === "http")
|
|
10815
|
+
adapter = "http";
|
|
10816
|
+
} catch {}
|
|
10817
|
+
}
|
|
10818
|
+
const input = {
|
|
10819
|
+
ticket: { id: parsed.ticket, summary, story: body, ac },
|
|
10820
|
+
adapter,
|
|
10821
|
+
categoriesInclude: parsed.categoriesInclude
|
|
10822
|
+
};
|
|
10823
|
+
const featurePath = join19(ticketDir, "test.feature");
|
|
10824
|
+
if (existsSync20(featurePath))
|
|
10825
|
+
input.existingFeature = readFileSync17(featurePath, "utf8");
|
|
10826
|
+
const specPath = join19(ticketDir, "spec.ts");
|
|
10827
|
+
if (existsSync20(specPath))
|
|
10828
|
+
input.existingSpec = readFileSync17(specPath, "utf8");
|
|
10829
|
+
if (parsed.userHint)
|
|
10830
|
+
input.userHint = parsed.userHint;
|
|
10831
|
+
const outPath = join19(ticketDir, "adversarial-input.json");
|
|
10832
|
+
writeFileSync11(outPath, JSON.stringify(input, null, 2));
|
|
10833
|
+
console.log(`[explore-prepare] wrote ${outPath}`);
|
|
10834
|
+
return 0;
|
|
10835
|
+
}
|
|
10836
|
+
|
|
10547
10837
|
// src/bin-internal/fetch.ts
|
|
10548
|
-
import { mkdirSync as mkdirSync12, writeFileSync as
|
|
10838
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
10549
10839
|
import { dirname as dirname5 } from "path";
|
|
10550
10840
|
|
|
10551
10841
|
// src/artifact/hash.ts
|
|
10552
10842
|
import { createHash as createHash4 } from "crypto";
|
|
10553
|
-
import { existsSync as
|
|
10843
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
|
|
10554
10844
|
function hashString(s) {
|
|
10555
10845
|
return `sha256:${createHash4("sha256").update(s).digest("hex")}`;
|
|
10556
10846
|
}
|
|
10557
10847
|
function hashFile(path) {
|
|
10558
|
-
return hashString(
|
|
10848
|
+
return hashString(readFileSync18(path, "utf8"));
|
|
10559
10849
|
}
|
|
10560
10850
|
function hashFileIfExists(path) {
|
|
10561
|
-
if (!
|
|
10851
|
+
if (!existsSync21(path))
|
|
10562
10852
|
return null;
|
|
10563
10853
|
return hashFile(path);
|
|
10564
10854
|
}
|
|
@@ -10567,33 +10857,33 @@ function hashFileIfExists(path) {
|
|
|
10567
10857
|
init_paths2();
|
|
10568
10858
|
|
|
10569
10859
|
// src/jira/mcp-backend.ts
|
|
10570
|
-
import { existsSync as
|
|
10860
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync19, writeFileSync as writeFileSync12 } from "fs";
|
|
10571
10861
|
import { tmpdir } from "os";
|
|
10572
|
-
import { join as
|
|
10862
|
+
import { join as join20 } from "path";
|
|
10573
10863
|
var MCP_ENV = "XERA_MCP_JIRA";
|
|
10574
10864
|
async function createMcpBackend(_baseUrl) {
|
|
10575
10865
|
if (process.env[MCP_ENV] !== "1")
|
|
10576
10866
|
return null;
|
|
10577
|
-
const tmpDir =
|
|
10867
|
+
const tmpDir = join20(tmpdir(), "xera-mcp");
|
|
10578
10868
|
mkdirSync11(tmpDir, { recursive: true });
|
|
10579
10869
|
return {
|
|
10580
10870
|
backend: "mcp",
|
|
10581
10871
|
async fetchTicket(key, _fields) {
|
|
10582
|
-
const cachePath =
|
|
10583
|
-
if (!
|
|
10872
|
+
const cachePath = join20(tmpDir, `${key}.json`);
|
|
10873
|
+
if (!existsSync22(cachePath)) {
|
|
10584
10874
|
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.`);
|
|
10585
10875
|
}
|
|
10586
|
-
const parsed = JSON.parse(
|
|
10876
|
+
const parsed = JSON.parse(readFileSync19(cachePath, "utf8"));
|
|
10587
10877
|
return parsed;
|
|
10588
10878
|
},
|
|
10589
10879
|
async postComment(key, body) {
|
|
10590
|
-
const outPath =
|
|
10591
|
-
|
|
10880
|
+
const outPath = join20(tmpDir, `${key}.comment.json`);
|
|
10881
|
+
writeFileSync12(outPath, JSON.stringify({ key, body }));
|
|
10592
10882
|
return { id: "mcp-pending" };
|
|
10593
10883
|
},
|
|
10594
10884
|
async transitionStatus(key, statusName) {
|
|
10595
|
-
const outPath =
|
|
10596
|
-
|
|
10885
|
+
const outPath = join20(tmpDir, `${key}.transition.json`);
|
|
10886
|
+
writeFileSync12(outPath, JSON.stringify({ key, statusName }));
|
|
10597
10887
|
},
|
|
10598
10888
|
async listFields(_sampleKey) {
|
|
10599
10889
|
throw new Error("listFields is REST-only; init flow uses REST for field discovery.");
|
|
@@ -10747,7 +11037,7 @@ async function fetchCmd(argv, opts = {}) {
|
|
|
10747
11037
|
const acLines = parseAcLines(t.acceptanceCriteria);
|
|
10748
11038
|
const full = renderStory(t.key, t.summary, storyHash, acLines, body);
|
|
10749
11039
|
mkdirSync12(dirname5(paths.storyPath), { recursive: true });
|
|
10750
|
-
|
|
11040
|
+
writeFileSync13(paths.storyPath, full);
|
|
10751
11041
|
const existing = readMeta(paths.metaPath);
|
|
10752
11042
|
writeMeta(paths.metaPath, {
|
|
10753
11043
|
ticket,
|
|
@@ -10808,20 +11098,20 @@ function renderStory(key, summary, storyHash, acLines, body) {
|
|
|
10808
11098
|
}
|
|
10809
11099
|
|
|
10810
11100
|
// src/bin-internal/fill-gap-finalize.ts
|
|
10811
|
-
import { existsSync as
|
|
10812
|
-
import { join as
|
|
10813
|
-
import { z as
|
|
10814
|
-
var
|
|
10815
|
-
proposals:
|
|
10816
|
-
id:
|
|
10817
|
-
ticketId:
|
|
10818
|
-
title:
|
|
10819
|
-
rationale:
|
|
10820
|
-
gherkin:
|
|
10821
|
-
satisfiesAcs:
|
|
11101
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
|
|
11102
|
+
import { join as join21 } from "path";
|
|
11103
|
+
import { z as z8 } from "zod";
|
|
11104
|
+
var ProposalsSchema2 = z8.object({
|
|
11105
|
+
proposals: z8.array(z8.object({
|
|
11106
|
+
id: z8.string().min(1),
|
|
11107
|
+
ticketId: z8.string().min(1),
|
|
11108
|
+
title: z8.string().min(1),
|
|
11109
|
+
rationale: z8.string().min(1),
|
|
11110
|
+
gherkin: z8.string().min(1),
|
|
11111
|
+
satisfiesAcs: z8.array(z8.number().int().nonnegative())
|
|
10822
11112
|
}))
|
|
10823
11113
|
});
|
|
10824
|
-
function
|
|
11114
|
+
function parseArgs6(argv) {
|
|
10825
11115
|
let accept;
|
|
10826
11116
|
let ticket;
|
|
10827
11117
|
let source;
|
|
@@ -10871,21 +11161,21 @@ function formatDraft(ticketId, proposal) {
|
|
|
10871
11161
|
}
|
|
10872
11162
|
async function fillGapFinalizeCmd(argv) {
|
|
10873
11163
|
if (argv.includes("--help-stub")) {}
|
|
10874
|
-
const parsed =
|
|
11164
|
+
const parsed = parseArgs6(argv);
|
|
10875
11165
|
if ("error" in parsed) {
|
|
10876
11166
|
console.error(`[fill-gap-finalize] ${parsed.error}`);
|
|
10877
11167
|
return 1;
|
|
10878
11168
|
}
|
|
10879
11169
|
const cwd = process.cwd();
|
|
10880
|
-
const sourcePath = parsed.source ??
|
|
10881
|
-
if (!
|
|
11170
|
+
const sourcePath = parsed.source ?? join21(cwd, ".xera/coverage/proposals.json");
|
|
11171
|
+
if (!existsSync23(sourcePath)) {
|
|
10882
11172
|
console.error(`[fill-gap-finalize] source not found: ${sourcePath}`);
|
|
10883
11173
|
return 2;
|
|
10884
11174
|
}
|
|
10885
11175
|
let proposals;
|
|
10886
11176
|
try {
|
|
10887
|
-
const raw = JSON.parse(
|
|
10888
|
-
proposals =
|
|
11177
|
+
const raw = JSON.parse(readFileSync20(sourcePath, "utf8"));
|
|
11178
|
+
proposals = ProposalsSchema2.parse(raw);
|
|
10889
11179
|
} catch (e) {
|
|
10890
11180
|
console.error(`[fill-gap-finalize] invalid proposals: ${e.message}`);
|
|
10891
11181
|
return 2;
|
|
@@ -10895,22 +11185,22 @@ async function fillGapFinalizeCmd(argv) {
|
|
|
10895
11185
|
console.error(`[fill-gap-finalize] proposal id "${parsed.accept}" not in source`);
|
|
10896
11186
|
return 2;
|
|
10897
11187
|
}
|
|
10898
|
-
const ticketDir =
|
|
11188
|
+
const ticketDir = join21(cwd, ".xera", parsed.ticket);
|
|
10899
11189
|
mkdirSync13(ticketDir, { recursive: true });
|
|
10900
|
-
const draftPath =
|
|
10901
|
-
if (
|
|
11190
|
+
const draftPath = join21(ticketDir, "feature.draft.md");
|
|
11191
|
+
if (existsSync23(draftPath) && !parsed.force) {
|
|
10902
11192
|
console.error(`[fill-gap-finalize] ${draftPath} exists; pass --force to overwrite`);
|
|
10903
11193
|
return 3;
|
|
10904
11194
|
}
|
|
10905
|
-
|
|
11195
|
+
writeFileSync14(draftPath, formatDraft(parsed.ticket, proposal));
|
|
10906
11196
|
return 0;
|
|
10907
11197
|
}
|
|
10908
11198
|
|
|
10909
11199
|
// src/bin-internal/fill-gap-prepare.ts
|
|
10910
11200
|
init_store();
|
|
10911
|
-
import { mkdirSync as mkdirSync14, writeFileSync as
|
|
10912
|
-
import { join as
|
|
10913
|
-
function
|
|
11201
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync15 } from "fs";
|
|
11202
|
+
import { join as join22 } from "path";
|
|
11203
|
+
function parseArgs7(argv) {
|
|
10914
11204
|
const args = {};
|
|
10915
11205
|
for (let i = 0;i < argv.length; i++) {
|
|
10916
11206
|
const a = argv[i];
|
|
@@ -10976,7 +11266,7 @@ function buildTicketContext(snap, ticketId) {
|
|
|
10976
11266
|
};
|
|
10977
11267
|
}
|
|
10978
11268
|
async function fillGapPrepareCmd(argv) {
|
|
10979
|
-
const parsed =
|
|
11269
|
+
const parsed = parseArgs7(argv);
|
|
10980
11270
|
if ("error" in parsed) {
|
|
10981
11271
|
console.error(`[fill-gap-prepare] ${parsed.error}`);
|
|
10982
11272
|
return 1;
|
|
@@ -11000,9 +11290,9 @@ async function fillGapPrepareCmd(argv) {
|
|
|
11000
11290
|
return 2;
|
|
11001
11291
|
}
|
|
11002
11292
|
}
|
|
11003
|
-
const outDir = parsed.outputDir ??
|
|
11293
|
+
const outDir = parsed.outputDir ?? join22(cwd, ".xera/coverage", scope);
|
|
11004
11294
|
mkdirSync14(outDir, { recursive: true });
|
|
11005
|
-
|
|
11295
|
+
writeFileSync15(join22(outDir, "context.json"), JSON.stringify(context, null, 2));
|
|
11006
11296
|
return 0;
|
|
11007
11297
|
}
|
|
11008
11298
|
|
|
@@ -11012,18 +11302,18 @@ init_graph_backfill();
|
|
|
11012
11302
|
// src/graph/enrich.ts
|
|
11013
11303
|
init_store();
|
|
11014
11304
|
init_ulid();
|
|
11015
|
-
import { existsSync as
|
|
11016
|
-
import { join as
|
|
11017
|
-
import { z as
|
|
11305
|
+
import { existsSync as existsSync24, readFileSync as readFileSync21, unlinkSync as unlinkSync2 } from "fs";
|
|
11306
|
+
import { join as join23 } from "path";
|
|
11307
|
+
import { z as z9 } from "zod";
|
|
11018
11308
|
var MAX_SIMILAR_EDGES = 10;
|
|
11019
11309
|
var MIN_CONFIDENCE = 0.7;
|
|
11020
|
-
var SimilarEntrySchema =
|
|
11021
|
-
ticketId:
|
|
11022
|
-
confidence:
|
|
11023
|
-
reason:
|
|
11310
|
+
var SimilarEntrySchema = z9.object({
|
|
11311
|
+
ticketId: z9.string().regex(/^[A-Z][A-Z0-9]*-\d+$/),
|
|
11312
|
+
confidence: z9.number(),
|
|
11313
|
+
reason: z9.string()
|
|
11024
11314
|
});
|
|
11025
|
-
var EnrichmentInputSchema =
|
|
11026
|
-
similar:
|
|
11315
|
+
var EnrichmentInputSchema = z9.object({
|
|
11316
|
+
similar: z9.array(SimilarEntrySchema)
|
|
11027
11317
|
});
|
|
11028
11318
|
var nowIso3 = () => new Date().toISOString();
|
|
11029
11319
|
var mk2 = (actor, type, payload) => ({
|
|
@@ -11042,11 +11332,11 @@ async function enrichTicket(repoRoot, ticketId, opts) {
|
|
|
11042
11332
|
if (snapshot.tickets[ticketId].enrichedAt && !opts.force) {
|
|
11043
11333
|
return { ticketId, similarCount: 0, enrichedAt: snapshot.tickets[ticketId].enrichedAt };
|
|
11044
11334
|
}
|
|
11045
|
-
const inputPath =
|
|
11046
|
-
if (!
|
|
11335
|
+
const inputPath = join23(repoRoot, ".xera", ticketId, "enrichment-input.json");
|
|
11336
|
+
if (!existsSync24(inputPath)) {
|
|
11047
11337
|
throw new Error(`enrichment-input.json not found at ${inputPath}`);
|
|
11048
11338
|
}
|
|
11049
|
-
const raw = JSON.parse(
|
|
11339
|
+
const raw = JSON.parse(readFileSync21(inputPath, "utf8"));
|
|
11050
11340
|
const parsed = EnrichmentInputSchema.safeParse(raw);
|
|
11051
11341
|
if (!parsed.success) {
|
|
11052
11342
|
throw new Error(`invalid enrichment-input.json: ${parsed.error.message}`);
|
|
@@ -11151,12 +11441,12 @@ async function graphQueryCmd(argv) {
|
|
|
11151
11441
|
init_graph_record();
|
|
11152
11442
|
|
|
11153
11443
|
// src/bin-internal/graph-render.ts
|
|
11154
|
-
import { existsSync as
|
|
11155
|
-
import { dirname as dirname7, join as
|
|
11444
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync23, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "fs";
|
|
11445
|
+
import { dirname as dirname7, join as join25 } from "path";
|
|
11156
11446
|
|
|
11157
11447
|
// src/graph/render.ts
|
|
11158
|
-
import { readFileSync as
|
|
11159
|
-
import { dirname as dirname6, join as
|
|
11448
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
11449
|
+
import { dirname as dirname6, join as join24 } from "path";
|
|
11160
11450
|
import { fileURLToPath } from "url";
|
|
11161
11451
|
var COLORS = {
|
|
11162
11452
|
ticket: "#3B82F6",
|
|
@@ -11375,9 +11665,9 @@ function transformForVisNetwork(snap, opts) {
|
|
|
11375
11665
|
}
|
|
11376
11666
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
11377
11667
|
var __dirname2 = dirname6(__filename2);
|
|
11378
|
-
var TEMPLATES_DIR =
|
|
11668
|
+
var TEMPLATES_DIR = join24(__dirname2, "templates");
|
|
11379
11669
|
function loadTemplate(name) {
|
|
11380
|
-
return
|
|
11670
|
+
return readFileSync22(join24(TEMPLATES_DIR, name), "utf8");
|
|
11381
11671
|
}
|
|
11382
11672
|
function statsToHuman(s) {
|
|
11383
11673
|
return `${s.tickets} tickets \xB7 ${s.scenarios} scenarios \xB7 ${s.poms} POMs \xB7 ${s.edges} edges`;
|
|
@@ -11429,7 +11719,7 @@ async function graphRenderCmd(argv) {
|
|
|
11429
11719
|
includeCoverage = true;
|
|
11430
11720
|
}
|
|
11431
11721
|
const repoRoot = process.cwd();
|
|
11432
|
-
const finalPath = outPath ??
|
|
11722
|
+
const finalPath = outPath ?? join25(repoRoot, ".xera/graph.html");
|
|
11433
11723
|
const events = loadAllEvents(repoRoot);
|
|
11434
11724
|
const snap = deriveSnapshot(events);
|
|
11435
11725
|
const totalNodeCount = Object.keys(snap.tickets).length + Object.keys(snap.scenarios).length + Object.keys(snap.poms).length + Object.keys(snap.areas).length;
|
|
@@ -11437,7 +11727,7 @@ async function graphRenderCmd(argv) {
|
|
|
11437
11727
|
if (performanceMode === "text-fallback") {
|
|
11438
11728
|
const txtPath = finalPath.replace(/\.html$/, ".txt");
|
|
11439
11729
|
mkdirSync15(dirname7(txtPath), { recursive: true });
|
|
11440
|
-
|
|
11730
|
+
writeFileSync16(txtPath, `Graph too large for HTML viewer (${totalNodeCount} nodes). Use 'xera:graph-query --format text' instead.
|
|
11441
11731
|
`);
|
|
11442
11732
|
console.log(`[graph-render] graph too large (${totalNodeCount} nodes); wrote ${txtPath}`);
|
|
11443
11733
|
return 0;
|
|
@@ -11449,9 +11739,9 @@ async function graphRenderCmd(argv) {
|
|
|
11449
11739
|
opts.since = since;
|
|
11450
11740
|
let coverage;
|
|
11451
11741
|
if (includeCoverage) {
|
|
11452
|
-
const reportPath =
|
|
11453
|
-
if (
|
|
11454
|
-
const report = JSON.parse(
|
|
11742
|
+
const reportPath = join25(repoRoot, ".xera/coverage/report.json");
|
|
11743
|
+
if (existsSync25(reportPath)) {
|
|
11744
|
+
const report = JSON.parse(readFileSync23(reportPath, "utf8"));
|
|
11455
11745
|
const snapshots = events.filter((e) => e.type === "coverage.snapshot").map((e) => e.payload);
|
|
11456
11746
|
coverage = { report, snapshots };
|
|
11457
11747
|
} else {
|
|
@@ -11469,7 +11759,7 @@ async function graphRenderCmd(argv) {
|
|
|
11469
11759
|
const html = renderHtml(renderInput);
|
|
11470
11760
|
mkdirSync15(dirname7(finalPath), { recursive: true });
|
|
11471
11761
|
const tmpPath = `${finalPath}.tmp`;
|
|
11472
|
-
|
|
11762
|
+
writeFileSync16(tmpPath, html);
|
|
11473
11763
|
renameSync2(tmpPath, finalPath);
|
|
11474
11764
|
console.log(`[graph-render] wrote ${finalPath} (${data.stats.tickets} tickets \xB7 ${data.stats.scenarios} scenarios \xB7 ${html.length} bytes)`);
|
|
11475
11765
|
return 0;
|
|
@@ -11500,8 +11790,8 @@ async function graphSnapshotCmd(argv) {
|
|
|
11500
11790
|
}
|
|
11501
11791
|
|
|
11502
11792
|
// src/bin-internal/heal-prepare.ts
|
|
11503
|
-
import { existsSync as
|
|
11504
|
-
import { join as
|
|
11793
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync24, writeFileSync as writeFileSync17 } from "fs";
|
|
11794
|
+
import { join as join26 } from "path";
|
|
11505
11795
|
import { scrubFreeText } from "@xera-ai/web";
|
|
11506
11796
|
|
|
11507
11797
|
// ../../node_modules/fflate/esm/index.mjs
|
|
@@ -11848,15 +12138,15 @@ function strFromU8(dat, latin1) {
|
|
|
11848
12138
|
var slzh = function(d, b) {
|
|
11849
12139
|
return b + 30 + b2(d, b + 26) + b2(d, b + 28);
|
|
11850
12140
|
};
|
|
11851
|
-
var zh = function(d, b,
|
|
12141
|
+
var zh = function(d, b, z10) {
|
|
11852
12142
|
var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
|
|
11853
|
-
var _a2 = z64hs(d, es, efl,
|
|
12143
|
+
var _a2 = z64hs(d, es, efl, z10, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
|
|
11854
12144
|
return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
|
|
11855
12145
|
};
|
|
11856
|
-
var z64hs = function(d, b, l,
|
|
12146
|
+
var z64hs = function(d, b, l, z10, sc, su, off) {
|
|
11857
12147
|
var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
|
|
11858
12148
|
var nf = nsc + nsu + noff;
|
|
11859
|
-
if (
|
|
12149
|
+
if (z10 && nf) {
|
|
11860
12150
|
for (;b + 4 < e; b += 4 + b2(d, b + 2)) {
|
|
11861
12151
|
if (b2(d, b) == 1) {
|
|
11862
12152
|
return [
|
|
@@ -11867,7 +12157,7 @@ var z64hs = function(d, b, l, z9, sc, su, off) {
|
|
|
11867
12157
|
];
|
|
11868
12158
|
}
|
|
11869
12159
|
}
|
|
11870
|
-
if (
|
|
12160
|
+
if (z10 < 2)
|
|
11871
12161
|
err(13);
|
|
11872
12162
|
}
|
|
11873
12163
|
return [sc, su, off, 0];
|
|
@@ -11883,18 +12173,18 @@ function unzipSync(data, opts) {
|
|
|
11883
12173
|
if (!c)
|
|
11884
12174
|
return {};
|
|
11885
12175
|
var o = b4(data, e + 16);
|
|
11886
|
-
var
|
|
11887
|
-
if (
|
|
12176
|
+
var z10 = b4(data, e - 20) == 117853008;
|
|
12177
|
+
if (z10) {
|
|
11888
12178
|
var ze = b4(data, e - 12);
|
|
11889
|
-
|
|
11890
|
-
if (
|
|
12179
|
+
z10 = b4(data, ze) == 101075792;
|
|
12180
|
+
if (z10) {
|
|
11891
12181
|
c = b4(data, ze + 32);
|
|
11892
12182
|
o = b4(data, ze + 48);
|
|
11893
12183
|
}
|
|
11894
12184
|
}
|
|
11895
12185
|
var fltr = opts && opts.filter;
|
|
11896
12186
|
for (var i2 = 0;i2 < c; ++i2) {
|
|
11897
|
-
var _a2 = zh(data, o,
|
|
12187
|
+
var _a2 = zh(data, o, z10), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
11898
12188
|
o = no;
|
|
11899
12189
|
if (!fltr || fltr({
|
|
11900
12190
|
name: fn,
|
|
@@ -11930,9 +12220,9 @@ function classifyKind(raw) {
|
|
|
11930
12220
|
return "other";
|
|
11931
12221
|
}
|
|
11932
12222
|
function extractDomSnapshot(tracePath) {
|
|
11933
|
-
if (!
|
|
12223
|
+
if (!existsSync26(tracePath))
|
|
11934
12224
|
return "";
|
|
11935
|
-
const buf =
|
|
12225
|
+
const buf = readFileSync24(tracePath);
|
|
11936
12226
|
const entries = unzipSync(buf);
|
|
11937
12227
|
const traceKey = Object.keys(entries).find((name) => name.endsWith(".trace"));
|
|
11938
12228
|
let chosenKey = null;
|
|
@@ -11980,16 +12270,16 @@ function extractDomSnapshot(tracePath) {
|
|
|
11980
12270
|
return scrubFreeText(html);
|
|
11981
12271
|
}
|
|
11982
12272
|
function findPomLine(ticketDir, rawLocator) {
|
|
11983
|
-
const pomDir =
|
|
12273
|
+
const pomDir = join26(ticketDir, "page-objects");
|
|
11984
12274
|
const candidates = [];
|
|
11985
|
-
if (
|
|
12275
|
+
if (existsSync26(pomDir)) {
|
|
11986
12276
|
for (const name of readdirSync6(pomDir)) {
|
|
11987
12277
|
if (name.endsWith(".ts"))
|
|
11988
|
-
candidates.push(
|
|
12278
|
+
candidates.push(join26(pomDir, name));
|
|
11989
12279
|
}
|
|
11990
12280
|
}
|
|
11991
12281
|
for (const file of candidates) {
|
|
11992
|
-
const text =
|
|
12282
|
+
const text = readFileSync24(file, "utf8");
|
|
11993
12283
|
const lines = text.split(`
|
|
11994
12284
|
`);
|
|
11995
12285
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
@@ -12027,13 +12317,13 @@ function findGherkinStep(featureText, rawLocator) {
|
|
|
12027
12317
|
}
|
|
12028
12318
|
function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
12029
12319
|
const paths = resolveArtifactPaths(repoRoot, ticket);
|
|
12030
|
-
const classifierPath =
|
|
12031
|
-
const classifier = JSON.parse(
|
|
12320
|
+
const classifierPath = join26(paths.ticketDir, "classifier-input.json");
|
|
12321
|
+
const classifier = JSON.parse(readFileSync24(classifierPath, "utf8"));
|
|
12032
12322
|
const cls = classifier.scenarios.find((s) => s.name === scenarioName);
|
|
12033
12323
|
if (!cls)
|
|
12034
12324
|
throw new Error(`scenario not found in classifier-input: "${scenarioName}"`);
|
|
12035
|
-
const runDir =
|
|
12036
|
-
const normalized = JSON.parse(
|
|
12325
|
+
const runDir = join26(paths.runsDir, runId);
|
|
12326
|
+
const normalized = JSON.parse(readFileSync24(join26(runDir, "normalized.json"), "utf8"));
|
|
12037
12327
|
const normSc = normalized.scenarios.find((s) => s.name === scenarioName);
|
|
12038
12328
|
if (!normSc?.failure)
|
|
12039
12329
|
throw new Error(`no failure recorded for scenario "${scenarioName}"`);
|
|
@@ -12044,9 +12334,9 @@ function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
|
12044
12334
|
const raw = m[1].trim();
|
|
12045
12335
|
const kind = classifyKind(raw);
|
|
12046
12336
|
const pomLoc = findPomLine(paths.ticketDir, raw);
|
|
12047
|
-
const featureText =
|
|
12337
|
+
const featureText = readFileSync24(paths.featurePath, "utf8");
|
|
12048
12338
|
const gherkinStep = findGherkinStep(featureText, raw);
|
|
12049
|
-
const domSnapshotAtFailure = extractDomSnapshot(
|
|
12339
|
+
const domSnapshotAtFailure = extractDomSnapshot(join26(runDir, "trace.zip"));
|
|
12050
12340
|
return {
|
|
12051
12341
|
ticket,
|
|
12052
12342
|
runId,
|
|
@@ -12066,8 +12356,8 @@ async function healPrepareCmd(argv) {
|
|
|
12066
12356
|
try {
|
|
12067
12357
|
const result = healPrepare(process.cwd(), ticket, runId, scenarioName);
|
|
12068
12358
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
12069
|
-
const outPath =
|
|
12070
|
-
|
|
12359
|
+
const outPath = join26(paths.runsDir, runId, "heal-input.json");
|
|
12360
|
+
writeFileSync17(outPath, JSON.stringify(result, null, 2));
|
|
12071
12361
|
console.log(`[xera:heal-prepare] wrote ${outPath}`);
|
|
12072
12362
|
return 0;
|
|
12073
12363
|
} catch (err2) {
|
|
@@ -12077,8 +12367,8 @@ async function healPrepareCmd(argv) {
|
|
|
12077
12367
|
}
|
|
12078
12368
|
|
|
12079
12369
|
// src/bin-internal/impact-prepare.ts
|
|
12080
|
-
import { mkdirSync as mkdirSync16, writeFileSync as
|
|
12081
|
-
import { join as
|
|
12370
|
+
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync18 } from "fs";
|
|
12371
|
+
import { join as join27 } from "path";
|
|
12082
12372
|
|
|
12083
12373
|
// src/graph/impact.ts
|
|
12084
12374
|
var PRIORITY_WEIGHT = { p0: 3, p1: 2, p2: 1 };
|
|
@@ -12328,11 +12618,11 @@ async function impactPrepareCmd(argv) {
|
|
|
12328
12618
|
scenarios,
|
|
12329
12619
|
generatedAt: new Date().toISOString()
|
|
12330
12620
|
};
|
|
12331
|
-
const impactDir =
|
|
12621
|
+
const impactDir = join27(repoRoot, ".xera/impact");
|
|
12332
12622
|
mkdirSync16(impactDir, { recursive: true });
|
|
12333
|
-
|
|
12623
|
+
writeFileSync18(join27(impactDir, `${ticket}.json`), JSON.stringify(report, null, 2));
|
|
12334
12624
|
if (!quiet) {
|
|
12335
|
-
|
|
12625
|
+
writeFileSync18(join27(impactDir, `${ticket}.md`), renderImpactMarkdown(report));
|
|
12336
12626
|
}
|
|
12337
12627
|
return 0;
|
|
12338
12628
|
}
|
|
@@ -12358,8 +12648,8 @@ async function lintCmd(argv) {
|
|
|
12358
12648
|
}
|
|
12359
12649
|
|
|
12360
12650
|
// src/bin-internal/normalize.ts
|
|
12361
|
-
import { existsSync as
|
|
12362
|
-
import { join as
|
|
12651
|
+
import { existsSync as existsSync27, readdirSync as readdirSync7 } from "fs";
|
|
12652
|
+
import { join as join28 } from "path";
|
|
12363
12653
|
init_paths2();
|
|
12364
12654
|
async function normalizeCmd(argv) {
|
|
12365
12655
|
const ticket = argv[0];
|
|
@@ -12374,8 +12664,8 @@ async function normalizeCmd(argv) {
|
|
|
12374
12664
|
console.error("[xera:normalize] no run found");
|
|
12375
12665
|
return 1;
|
|
12376
12666
|
}
|
|
12377
|
-
const runDir =
|
|
12378
|
-
if (!
|
|
12667
|
+
const runDir = join28(paths.runsDir, runId);
|
|
12668
|
+
if (!existsSync27(runDir)) {
|
|
12379
12669
|
console.error(`[xera:normalize] runs/${runId} missing`);
|
|
12380
12670
|
return 1;
|
|
12381
12671
|
}
|
|
@@ -12395,14 +12685,14 @@ async function normalizeCmd(argv) {
|
|
|
12395
12685
|
|
|
12396
12686
|
// src/bin-internal/post.ts
|
|
12397
12687
|
init_paths2();
|
|
12398
|
-
import { existsSync as
|
|
12399
|
-
import { join as
|
|
12688
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
|
|
12689
|
+
import { join as join29 } from "path";
|
|
12400
12690
|
|
|
12401
12691
|
// src/artifact/status.ts
|
|
12402
|
-
import { existsSync as
|
|
12692
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
|
|
12403
12693
|
import { dirname as dirname8 } from "path";
|
|
12404
|
-
import { z as
|
|
12405
|
-
var ClassificationEnum =
|
|
12694
|
+
import { z as z10 } from "zod";
|
|
12695
|
+
var ClassificationEnum = z10.enum([
|
|
12406
12696
|
"PASS",
|
|
12407
12697
|
"REAL_BUG",
|
|
12408
12698
|
"SELECTOR_DRIFT",
|
|
@@ -12413,37 +12703,37 @@ var ClassificationEnum = z9.enum([
|
|
|
12413
12703
|
"RATE_LIMITED",
|
|
12414
12704
|
"AUTH_EXPIRED"
|
|
12415
12705
|
]);
|
|
12416
|
-
var ResultEnum =
|
|
12417
|
-
var ConfidenceEnum =
|
|
12418
|
-
var HistoryEntrySchema =
|
|
12419
|
-
ts:
|
|
12706
|
+
var ResultEnum = z10.enum(["PASS", "FAIL"]);
|
|
12707
|
+
var ConfidenceEnum = z10.enum(["low", "medium", "high"]);
|
|
12708
|
+
var HistoryEntrySchema = z10.object({
|
|
12709
|
+
ts: z10.string(),
|
|
12420
12710
|
result: ResultEnum,
|
|
12421
12711
|
class: ClassificationEnum
|
|
12422
12712
|
});
|
|
12423
|
-
var StatusJsonSchema =
|
|
12424
|
-
ticket:
|
|
12425
|
-
lastRun:
|
|
12713
|
+
var StatusJsonSchema = z10.object({
|
|
12714
|
+
ticket: z10.string(),
|
|
12715
|
+
lastRun: z10.string(),
|
|
12426
12716
|
result: ResultEnum,
|
|
12427
12717
|
classification: ClassificationEnum,
|
|
12428
12718
|
confidence: ConfidenceEnum,
|
|
12429
|
-
scenarios:
|
|
12430
|
-
total:
|
|
12431
|
-
passed:
|
|
12432
|
-
failed:
|
|
12433
|
-
skipped:
|
|
12719
|
+
scenarios: z10.object({
|
|
12720
|
+
total: z10.number().int().nonnegative(),
|
|
12721
|
+
passed: z10.number().int().nonnegative(),
|
|
12722
|
+
failed: z10.number().int().nonnegative(),
|
|
12723
|
+
skipped: z10.number().int().nonnegative()
|
|
12434
12724
|
}),
|
|
12435
|
-
history:
|
|
12436
|
-
last_jira_comment_id:
|
|
12725
|
+
history: z10.array(HistoryEntrySchema).default([]),
|
|
12726
|
+
last_jira_comment_id: z10.string().optional()
|
|
12437
12727
|
});
|
|
12438
12728
|
var HISTORY_CAP = 20;
|
|
12439
12729
|
function readStatus(path) {
|
|
12440
|
-
if (!
|
|
12730
|
+
if (!existsSync28(path))
|
|
12441
12731
|
return null;
|
|
12442
|
-
return StatusJsonSchema.parse(JSON.parse(
|
|
12732
|
+
return StatusJsonSchema.parse(JSON.parse(readFileSync25(path, "utf8")));
|
|
12443
12733
|
}
|
|
12444
12734
|
function writeStatus(path, status) {
|
|
12445
12735
|
mkdirSync17(dirname8(path), { recursive: true });
|
|
12446
|
-
|
|
12736
|
+
writeFileSync19(path, JSON.stringify(status, null, 2));
|
|
12447
12737
|
}
|
|
12448
12738
|
function appendHistory(path, entry) {
|
|
12449
12739
|
const s = readStatus(path);
|
|
@@ -12469,12 +12759,12 @@ async function postCmd(argv) {
|
|
|
12469
12759
|
return 0;
|
|
12470
12760
|
}
|
|
12471
12761
|
const paths = resolveArtifactPaths(cwd, ticket);
|
|
12472
|
-
const draftPath =
|
|
12473
|
-
if (!
|
|
12762
|
+
const draftPath = join29(paths.ticketDir, "jira-comment.draft.md");
|
|
12763
|
+
if (!existsSync29(draftPath)) {
|
|
12474
12764
|
console.error(`[xera:post] no draft at ${draftPath}; run \`xera-internal report\` first.`);
|
|
12475
12765
|
return 1;
|
|
12476
12766
|
}
|
|
12477
|
-
const body =
|
|
12767
|
+
const body = readFileSync26(draftPath, "utf8");
|
|
12478
12768
|
const client = await createJiraClient({
|
|
12479
12769
|
baseUrl: config.jira.baseUrl,
|
|
12480
12770
|
preferMcp: true,
|
|
@@ -12502,8 +12792,8 @@ async function promoteCmd(argv) {
|
|
|
12502
12792
|
}
|
|
12503
12793
|
|
|
12504
12794
|
// src/bin-internal/report.ts
|
|
12505
|
-
import { existsSync as
|
|
12506
|
-
import { join as
|
|
12795
|
+
import { existsSync as existsSync31, readFileSync as readFileSync27, writeFileSync as writeFileSync20 } from "fs";
|
|
12796
|
+
import { join as join30 } from "path";
|
|
12507
12797
|
init_paths2();
|
|
12508
12798
|
|
|
12509
12799
|
// src/classifier/aggregate.ts
|
|
@@ -12770,11 +13060,11 @@ xera v${input.xeraVersion} \u2022 prompts v${input.promptsVersion}`;
|
|
|
12770
13060
|
}
|
|
12771
13061
|
|
|
12772
13062
|
// src/reporter/status-writer.ts
|
|
12773
|
-
import { existsSync as
|
|
13063
|
+
import { existsSync as existsSync30 } from "fs";
|
|
12774
13064
|
function writeStatusFromClassification(path, input) {
|
|
12775
13065
|
const result = input.classification.overall === "PASS" ? "PASS" : "FAIL";
|
|
12776
13066
|
const entry = { ts: input.runTs, result, class: input.classification.overall };
|
|
12777
|
-
if (!
|
|
13067
|
+
if (!existsSync30(path)) {
|
|
12778
13068
|
writeStatus(path, {
|
|
12779
13069
|
ticket: input.ticket,
|
|
12780
13070
|
lastRun: input.runTs,
|
|
@@ -12808,22 +13098,22 @@ async function reportCmd(argv) {
|
|
|
12808
13098
|
}
|
|
12809
13099
|
const cwd = process.cwd();
|
|
12810
13100
|
const paths = resolveArtifactPaths(cwd, ticket);
|
|
12811
|
-
const input = JSON.parse(
|
|
13101
|
+
const input = JSON.parse(readFileSync27(inputArg.slice("--input=".length), "utf8"));
|
|
12812
13102
|
let httpRuleOverride = null;
|
|
12813
13103
|
const meta = readMeta(paths.metaPath);
|
|
12814
13104
|
if (meta?.adapter === "http") {
|
|
12815
13105
|
const config = await loadConfig(cwd);
|
|
12816
13106
|
if (config.http) {
|
|
12817
|
-
const normalizedPath =
|
|
12818
|
-
if (
|
|
12819
|
-
const norm = JSON.parse(
|
|
13107
|
+
const normalizedPath = join30(paths.ticketDir, "runs", input.runId, "normalized.json");
|
|
13108
|
+
if (existsSync31(normalizedPath)) {
|
|
13109
|
+
const norm = JSON.parse(readFileSync27(normalizedPath, "utf8"));
|
|
12820
13110
|
const calls = norm.http?.calls ?? [];
|
|
12821
13111
|
const rate = classifyRateLimited({ calls });
|
|
12822
13112
|
if (rate)
|
|
12823
13113
|
httpRuleOverride = rate;
|
|
12824
13114
|
if (!httpRuleOverride) {
|
|
12825
13115
|
const authFiles = {};
|
|
12826
|
-
const httpAuthDir =
|
|
13116
|
+
const httpAuthDir = join30(cwd, ".xera", ".auth", "http");
|
|
12827
13117
|
for (const role of Object.keys(config.http.auth.roles)) {
|
|
12828
13118
|
const entry = readAuthState(httpAuthDir, role);
|
|
12829
13119
|
if (entry) {
|
|
@@ -12868,8 +13158,8 @@ async function reportCmd(argv) {
|
|
|
12868
13158
|
confidence: "high"
|
|
12869
13159
|
} : s) : input.scenarios;
|
|
12870
13160
|
const aggregated = aggregateScenarios(scenariosForAggregation);
|
|
12871
|
-
const decisionsPath =
|
|
12872
|
-
const decisions =
|
|
13161
|
+
const decisionsPath = join30(paths.ticketDir, "runs", input.runId, "outdated-decisions.json");
|
|
13162
|
+
const decisions = existsSync31(decisionsPath) ? JSON.parse(readFileSync27(decisionsPath, "utf8")) : {};
|
|
12873
13163
|
const graph = deriveSnapshot(loadAllEvents(process.cwd()));
|
|
12874
13164
|
const normalizeScenarioName = (name) => name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
12875
13165
|
const scenarioIdByName = {};
|
|
@@ -12917,8 +13207,8 @@ async function reportCmd(argv) {
|
|
|
12917
13207
|
xeraVersion: XERA_VERSION,
|
|
12918
13208
|
promptsVersion: PROMPTS_VERSION
|
|
12919
13209
|
});
|
|
12920
|
-
const draftPath =
|
|
12921
|
-
|
|
13210
|
+
const draftPath = join30(paths.ticketDir, "jira-comment.draft.md");
|
|
13211
|
+
writeFileSync20(draftPath, md);
|
|
12922
13212
|
console.log(`[xera:report] wrote status.json and ${draftPath}`);
|
|
12923
13213
|
return 0;
|
|
12924
13214
|
}
|
|
@@ -12989,7 +13279,7 @@ async function unlockCmd(argv) {
|
|
|
12989
13279
|
|
|
12990
13280
|
// src/bin-internal/validate-feature.ts
|
|
12991
13281
|
init_paths2();
|
|
12992
|
-
import { existsSync as
|
|
13282
|
+
import { existsSync as existsSync32, readFileSync as readFileSync28 } from "fs";
|
|
12993
13283
|
import { validateGherkin as validateGherkin2 } from "@xera-ai/web";
|
|
12994
13284
|
async function validateFeatureCmd(argv) {
|
|
12995
13285
|
const ticket = argv[0];
|
|
@@ -12998,11 +13288,11 @@ async function validateFeatureCmd(argv) {
|
|
|
12998
13288
|
return 1;
|
|
12999
13289
|
}
|
|
13000
13290
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
13001
|
-
if (!
|
|
13291
|
+
if (!existsSync32(paths.featurePath)) {
|
|
13002
13292
|
console.error(`[xera:validate-feature] missing ${paths.featurePath}`);
|
|
13003
13293
|
return 1;
|
|
13004
13294
|
}
|
|
13005
|
-
const r = validateGherkin2(
|
|
13295
|
+
const r = validateGherkin2(readFileSync28(paths.featurePath, "utf8"));
|
|
13006
13296
|
if (r.ok) {
|
|
13007
13297
|
console.log("[xera:validate-feature] ok");
|
|
13008
13298
|
return 0;
|
|
@@ -13024,6 +13314,8 @@ var COMMANDS = {
|
|
|
13024
13314
|
"eval-prepare": evalPrepareCmd,
|
|
13025
13315
|
"eval-report": evalReportCmd,
|
|
13026
13316
|
exec: execCmd,
|
|
13317
|
+
"explore-finalize": exploreFinalizeCmd,
|
|
13318
|
+
"explore-prepare": explorePrepareCmd,
|
|
13027
13319
|
"fill-gap-finalize": fillGapFinalizeCmd,
|
|
13028
13320
|
"fill-gap-prepare": fillGapPrepareCmd,
|
|
13029
13321
|
fetch: fetchCmd,
|
|
@@ -13062,7 +13354,11 @@ Commands: ${Object.keys(COMMANDS).join(", ")}`);
|
|
|
13062
13354
|
}
|
|
13063
13355
|
|
|
13064
13356
|
// bin/internal.ts
|
|
13065
|
-
|
|
13357
|
+
if (existsSync33(".env.local")) {
|
|
13358
|
+
console.error(`
|
|
13359
|
+
warning: .env.local detected but ignored. xera uses .env only \u2014 ` + `merge values from .env.local into .env and delete .env.local to silence this warning.
|
|
13360
|
+
`);
|
|
13361
|
+
}
|
|
13066
13362
|
import_dotenv.config();
|
|
13067
13363
|
var code = await run(process.argv.slice(2));
|
|
13068
13364
|
process.exit(code);
|