@graphrefly/stack 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +24 -11
  2. package/dist/assets/contracts/repository/v1/review-bundle.schema.json +49 -0
  3. package/dist/assets/contracts/repository/v1/review-decision-request.schema.json +15 -0
  4. package/dist/assets/contracts/repository/v1/review-decision.schema.json +52 -0
  5. package/dist/assets/contracts/repository/v1/review.schema.json +2 -1
  6. package/dist/grfs.js +320 -26
  7. package/dist/review/assets/{abnfDiagram-VRR7QNED-5JVeQ-ZW.js → abnfDiagram-VRR7QNED-DxGYeLns.js} +1 -1
  8. package/dist/review/assets/{architectureDiagram-ZJ3FMSHR-BnEvDO7X.js → architectureDiagram-ZJ3FMSHR-CoaG1PSB.js} +1 -1
  9. package/dist/review/assets/{chunk-EX3LRPZG-BrpCa9zr.js → chunk-EX3LRPZG-C1ZhSVKp.js} +1 -1
  10. package/dist/review/assets/{chunk-MOJQB5TN-BshP08Ez.js → chunk-MOJQB5TN-Ct40HB9X.js} +1 -1
  11. package/dist/review/assets/{chunk-PUDLZKDR-HkwEqIVB.js → chunk-PUDLZKDR-C2Is490W.js} +1 -1
  12. package/dist/review/assets/{chunk-V7JOEXUC-CswDacm3.js → chunk-V7JOEXUC-Z3n_IPe-.js} +1 -1
  13. package/dist/review/assets/{classDiagram-OUVF2IWQ-U8e_ZvC4.js → classDiagram-OUVF2IWQ-BVlewphI.js} +1 -1
  14. package/dist/review/assets/{classDiagram-v2-EOCWNBFH-U8e_ZvC4.js → classDiagram-v2-EOCWNBFH-BVlewphI.js} +1 -1
  15. package/dist/review/assets/{cynefinDiagram-TSTJHNR4-CfYu2mB5.js → cynefinDiagram-TSTJHNR4-Cjm1OFIk.js} +1 -1
  16. package/dist/review/assets/{dagre-VKFMJZFB-BvyihZfA.js → dagre-VKFMJZFB-EvQo1qj2.js} +1 -1
  17. package/dist/review/assets/{diagram-G47NLZAW-qFNMv5An.js → diagram-G47NLZAW-k0H6Hccx.js} +1 -1
  18. package/dist/review/assets/{diagram-NH7WQ7WH-CsvacND9.js → diagram-NH7WQ7WH-VUneNb0M.js} +1 -1
  19. package/dist/review/assets/{diagram-OA4YK3LP-qFtWcyNi.js → diagram-OA4YK3LP-tUGLM6tT.js} +1 -1
  20. package/dist/review/assets/{diagram-WEI45ONY-C5nYGL8t.js → diagram-WEI45ONY-aN8naqL-.js} +1 -1
  21. package/dist/review/assets/{ebnfDiagram-CCIWWBDH-D_0nhpry.js → ebnfDiagram-CCIWWBDH-PjAeMUc1.js} +1 -1
  22. package/dist/review/assets/{erDiagram-Q63AITRT-CIS7hpdK.js → erDiagram-Q63AITRT-CkxN3vs2.js} +1 -1
  23. package/dist/review/assets/{flowDiagram-23GEKE2U-Df5sjnlg.js → flowDiagram-23GEKE2U-ChS1-2Qo.js} +1 -1
  24. package/dist/review/assets/index-CUaeHlgA.js +64 -0
  25. package/dist/review/assets/index-Cfj-JRmr.css +1 -0
  26. package/dist/review/assets/{infoDiagram-FWYZ7A6U-CT7TZgi6.js → infoDiagram-FWYZ7A6U-CcLr4Fgp.js} +1 -1
  27. package/dist/review/assets/{ishikawaDiagram-FXEZZL3T-BUpotH7r.js → ishikawaDiagram-FXEZZL3T-p7DbRTIs.js} +1 -1
  28. package/dist/review/assets/{kanban-definition-HUTT4EX6-e-emfNuh.js → kanban-definition-HUTT4EX6-BgXdqTkK.js} +1 -1
  29. package/dist/review/assets/{mindmap-definition-LN4V7U3C-ClSEPrvR.js → mindmap-definition-LN4V7U3C-zdfirl2I.js} +1 -1
  30. package/dist/review/assets/{pegDiagram-2B236MQR-Ck_v89jl.js → pegDiagram-2B236MQR-ScPkpaWR.js} +1 -1
  31. package/dist/review/assets/{pieDiagram-ENE6RG2P-C63dxyZj.js → pieDiagram-ENE6RG2P-BAQHg9fC.js} +1 -1
  32. package/dist/review/assets/{railroadDiagram-RFXS5EU6-DYI7CPMt.js → railroadDiagram-RFXS5EU6-DV8AQwDM.js} +1 -1
  33. package/dist/review/assets/{requirementDiagram-TGXJPOKE-BMW46SXM.js → requirementDiagram-TGXJPOKE-CnPqfxpt.js} +1 -1
  34. package/dist/review/assets/{sequenceDiagram-DBY2YBRQ-DVtyVuxr.js → sequenceDiagram-DBY2YBRQ-Bl7q6mbL.js} +1 -1
  35. package/dist/review/assets/{stateDiagram-2N3HPSRC-BM1s-zUo.js → stateDiagram-2N3HPSRC-iIZcI3Ny.js} +1 -1
  36. package/dist/review/assets/{stateDiagram-v2-6OUMAXLB-BEfolAkc.js → stateDiagram-v2-6OUMAXLB-Ct4036z_.js} +1 -1
  37. package/dist/review/assets/{swimlanes-5IMT3BWC-Bnu9mzxz.js → swimlanes-5IMT3BWC-DQR9TtlF.js} +1 -1
  38. package/dist/review/assets/{swimlanesDiagram-G3AALYLV-DlNNBK53.js → swimlanesDiagram-G3AALYLV-LS62ia1i.js} +1 -1
  39. package/dist/review/assets/{timeline-definition-FHXFAJF6-CwlO71Lp.js → timeline-definition-FHXFAJF6-Dm738Pbf.js} +1 -1
  40. package/dist/review/assets/{vennDiagram-L72KCM5P-DZxbcc_O.js → vennDiagram-L72KCM5P-DBxm9_YT.js} +1 -1
  41. package/dist/review/assets/{wardleyDiagram-EHGQE667-DGQto3hn.js → wardleyDiagram-EHGQE667-CKwVekAc.js} +1 -1
  42. package/dist/review/assets/{xychartDiagram-FW5EYKEG-BfD1lSXX.js → xychartDiagram-FW5EYKEG-TVh8_sN-.js} +1 -1
  43. package/dist/review/index.html +2 -2
  44. package/package.json +1 -1
  45. package/dist/review/assets/index-Bt3i8n39.css +0 -1
  46. package/dist/review/assets/index-CqjR4YYp.js +0 -64
package/dist/grfs.js CHANGED
@@ -2980,7 +2980,7 @@ var require_compile = __commonJS({
2980
2980
  const schOrFunc = root.refs[ref];
2981
2981
  if (schOrFunc)
2982
2982
  return schOrFunc;
2983
- let _sch = resolve11.call(this, root, ref);
2983
+ let _sch = resolve12.call(this, root, ref);
2984
2984
  if (_sch === void 0) {
2985
2985
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2986
2986
  const { schemaId } = this.opts;
@@ -3007,7 +3007,7 @@ var require_compile = __commonJS({
3007
3007
  function sameSchemaEnv(s1, s2) {
3008
3008
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3009
3009
  }
3010
- function resolve11(root, ref) {
3010
+ function resolve12(root, ref) {
3011
3011
  let sch;
3012
3012
  while (typeof (sch = this.refs[ref]) == "string")
3013
3013
  ref = sch;
@@ -3638,7 +3638,7 @@ var require_fast_uri = __commonJS({
3638
3638
  }
3639
3639
  return uri;
3640
3640
  }
3641
- function resolve11(baseURI, relativeURI, options) {
3641
+ function resolve12(baseURI, relativeURI, options) {
3642
3642
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3643
3643
  const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
3644
3644
  schemelessOptions.skipEscape = true;
@@ -3896,7 +3896,7 @@ var require_fast_uri = __commonJS({
3896
3896
  var fastUri = {
3897
3897
  SCHEMES,
3898
3898
  normalize,
3899
- resolve: resolve11,
3899
+ resolve: resolve12,
3900
3900
  resolveComponent,
3901
3901
  equal,
3902
3902
  serialize: serialize2,
@@ -8564,8 +8564,8 @@ var require_semver2 = __commonJS({
8564
8564
 
8565
8565
  // packages/cli/dist/cli.js
8566
8566
  import { realpathSync } from "node:fs";
8567
- import { readFile as readFile7 } from "node:fs/promises";
8568
- import { resolve as resolve10 } from "node:path";
8567
+ import { readFile as readFile8 } from "node:fs/promises";
8568
+ import { resolve as resolve11 } from "node:path";
8569
8569
  import { pathToFileURL as pathToFileURL3 } from "node:url";
8570
8570
 
8571
8571
  // packages/contracts/dist/index.js
@@ -8627,6 +8627,8 @@ function sha256Jcs(value) {
8627
8627
  // packages/contracts/dist/repository-review.js
8628
8628
  var REPOSITORY_CONFIG_SCHEMA = "graphrefly.stack.repository.v1";
8629
8629
  var REPOSITORY_REVIEW_SCHEMA = "graphrefly.stack.review.v1";
8630
+ var REPOSITORY_REVIEW_DECISION_SCHEMA = "graphrefly.stack.repository-review-decision.v1";
8631
+ var REPOSITORY_REVIEW_BUNDLE_SCHEMA = "graphrefly.stack.repository-review-bundle.v1";
8630
8632
  var SUPPORTED_GRAPHREFLY_RANGE = ">=0.3.0 <0.4.0";
8631
8633
 
8632
8634
  // packages/contracts/dist/index.js
@@ -10572,6 +10574,27 @@ async function validateReview(review) {
10572
10574
  throw new RepositoryReviewError("REVIEW_SCHEMA_INVALID", `Repository review failed validation: ${JSON.stringify(validate.errors)}`);
10573
10575
  }
10574
10576
  }
10577
+ function reviewHeadLabel(repository, requested, headOid) {
10578
+ try {
10579
+ const symbolic = gitText(repository, ["rev-parse", "--symbolic-full-name", requested]);
10580
+ if (symbolic.startsWith("refs/heads/"))
10581
+ return symbolic.slice("refs/heads/".length);
10582
+ } catch {
10583
+ }
10584
+ try {
10585
+ const branch = gitText(repository, [
10586
+ "for-each-ref",
10587
+ "--format=%(refname:short)",
10588
+ "--points-at",
10589
+ headOid,
10590
+ "refs/heads"
10591
+ ]).split("\n").find(Boolean);
10592
+ if (branch !== void 0)
10593
+ return branch;
10594
+ } catch {
10595
+ }
10596
+ return headOid.slice(0, 12);
10597
+ }
10575
10598
  async function createRepositoryReview(options) {
10576
10599
  let repository;
10577
10600
  try {
@@ -10656,6 +10679,7 @@ async function createRepositoryReview(options) {
10656
10679
  source: "generic-repository",
10657
10680
  repository: {
10658
10681
  label: basename2(repository),
10682
+ headLabel: reviewHeadLabel(repository, options.head, headOid),
10659
10683
  graphreflyVersion: runtime.version,
10660
10684
  entrypoint: config.blueprint.entrypoint,
10661
10685
  baseOid,
@@ -10673,7 +10697,172 @@ async function createRepositoryReview(options) {
10673
10697
  import { createReadStream } from "node:fs";
10674
10698
  import { stat as stat4 } from "node:fs/promises";
10675
10699
  import { createServer } from "node:http";
10676
- import { extname, resolve as resolve9, sep as sep5 } from "node:path";
10700
+ import { extname, resolve as resolve10, sep as sep6 } from "node:path";
10701
+
10702
+ // packages/cli/dist/repository-review-state.js
10703
+ import { randomUUID } from "node:crypto";
10704
+ import { lstat, mkdir as mkdir6, readdir, readFile as readFile7, realpath as realpath4, rename, writeFile as writeFile6 } from "node:fs/promises";
10705
+ import { resolve as resolve9, sep as sep5 } from "node:path";
10706
+ var schemaPaths = [
10707
+ "contracts/repository/v1/repository-config.schema.json",
10708
+ "contracts/repository/v1/review.schema.json",
10709
+ "contracts/repository/v1/review-decision-request.schema.json",
10710
+ "contracts/repository/v1/review-decision.schema.json",
10711
+ "contracts/repository/v1/review-bundle.schema.json"
10712
+ ];
10713
+ var RepositoryReviewStateError = class extends Error {
10714
+ code;
10715
+ constructor(code, message) {
10716
+ super(message);
10717
+ this.code = code;
10718
+ this.name = "RepositoryReviewStateError";
10719
+ }
10720
+ };
10721
+ async function validators() {
10722
+ const schemas2 = await Promise.all(schemaPaths.map(async (path) => JSON.parse(await readFile7(runtimeAssetPath(path), "utf8"))));
10723
+ const ajv = createStrictAjv();
10724
+ for (const schema of schemas2)
10725
+ ajv.addSchema(schema);
10726
+ return {
10727
+ request: ajv.getSchema("urn:graphrefly-stack:schema:repository-review-decision-request:v1"),
10728
+ decision: ajv.getSchema("urn:graphrefly-stack:schema:repository-review-decision:v1"),
10729
+ bundle: ajv.getSchema("urn:graphrefly-stack:schema:repository-review-bundle:v1")
10730
+ };
10731
+ }
10732
+ async function safeDirectory(path, parent) {
10733
+ try {
10734
+ const current = await lstat(path);
10735
+ if (!current.isDirectory() || current.isSymbolicLink()) {
10736
+ throw new RepositoryReviewStateError("REVIEW_STATE_PATH_UNSAFE", "Local review state path must be a real directory");
10737
+ }
10738
+ } catch (error) {
10739
+ if (error instanceof RepositoryReviewStateError)
10740
+ throw error;
10741
+ if (error.code !== "ENOENT")
10742
+ throw error;
10743
+ await mkdir6(path, { recursive: false, mode: 448 });
10744
+ }
10745
+ const canonical = await realpath4(path);
10746
+ if (!canonical.startsWith(`${parent}${sep5}`)) {
10747
+ throw new RepositoryReviewStateError("REVIEW_STATE_PATH_UNSAFE", "Local review state escaped the Git common directory");
10748
+ }
10749
+ return canonical;
10750
+ }
10751
+ async function repositoryReviewStateRoot(repository) {
10752
+ const commonDirectory = await realpath4(gitText(repository, ["rev-parse", "--path-format=absolute", "--git-common-dir"]));
10753
+ return safeDirectory(resolve9(commonDirectory, "grfs"), commonDirectory);
10754
+ }
10755
+ async function decisionsDirectory(repository) {
10756
+ const root = await repositoryReviewStateRoot(repository);
10757
+ return safeDirectory(resolve9(root, "reviews"), root);
10758
+ }
10759
+ function blueprintHashFor(review, commitOid) {
10760
+ const commit = review.commits.find((candidate) => candidate.oid === commitOid);
10761
+ const hash2 = commit?.blueprint.hash;
10762
+ if (typeof hash2 !== "object" || hash2 === null)
10763
+ return void 0;
10764
+ return typeof hash2.value === "string" ? hash2.value : void 0;
10765
+ }
10766
+ function matchesCurrentReview(review, record) {
10767
+ const commit = review.commits.find((candidate) => candidate.oid === record.target.commitOid);
10768
+ return commit !== void 0 && record.target.baseOid === review.repository.baseOid && record.target.headOid === review.repository.headOid && record.target.parentOid === commit.parentOid && record.target.blueprintHash === blueprintHashFor(review, commit.oid);
10769
+ }
10770
+ async function readRepositoryReviewDecisions(repository, review) {
10771
+ const directory = await decisionsDirectory(repository);
10772
+ const { decision: validate } = await validators();
10773
+ if (validate === void 0)
10774
+ throw new Error("Repository review decision validator unavailable");
10775
+ const entries = (await readdir(directory, { withFileTypes: true })).filter((entry2) => entry2.isFile() && entry2.name.endsWith(".json")).map((entry2) => entry2.name).sort();
10776
+ const records = [];
10777
+ for (const entry2 of entries) {
10778
+ let value;
10779
+ try {
10780
+ value = JSON.parse(await readFile7(resolve9(directory, entry2), "utf8"));
10781
+ } catch {
10782
+ throw new RepositoryReviewStateError("REVIEW_STATE_INVALID", `Local review record is not valid JSON: ${entry2}`);
10783
+ }
10784
+ if (!validate(value)) {
10785
+ throw new RepositoryReviewStateError("REVIEW_STATE_INVALID", `Local review record failed validation: ${entry2}`);
10786
+ }
10787
+ const record = value;
10788
+ if (matchesCurrentReview(review, record))
10789
+ records.push(record);
10790
+ }
10791
+ return records.sort((left, right) => left.recordedAt.localeCompare(right.recordedAt));
10792
+ }
10793
+ async function writeRepositoryReviewDecision(repository, review, input) {
10794
+ const { request: validateRequest, decision: validateDecision } = await validators();
10795
+ if (validateRequest === void 0 || validateDecision === void 0) {
10796
+ throw new Error("Repository review decision validators unavailable");
10797
+ }
10798
+ if (!validateRequest(input)) {
10799
+ throw new RepositoryReviewStateError("REVIEW_DECISION_INVALID", `Review decision request failed validation: ${JSON.stringify(validateRequest.errors)}`);
10800
+ }
10801
+ const request = input;
10802
+ const reviewerLabel = request.reviewerLabel.trim();
10803
+ const summary = request.summary.trim();
10804
+ if (reviewerLabel.length === 0) {
10805
+ throw new RepositoryReviewStateError("REVIEW_DECISION_INVALID", "Reviewer name cannot be blank");
10806
+ }
10807
+ const commit = review.commits.find((candidate) => candidate.oid === request.commitOid);
10808
+ const blueprintHash = blueprintHashFor(review, request.commitOid);
10809
+ if (commit === void 0 || blueprintHash === void 0) {
10810
+ throw new RepositoryReviewStateError("REVIEW_TARGET_STALE", "The selected commit is not part of the current review range");
10811
+ }
10812
+ const record = {
10813
+ schema: REPOSITORY_REVIEW_DECISION_SCHEMA,
10814
+ id: randomUUID(),
10815
+ target: {
10816
+ baseOid: review.repository.baseOid,
10817
+ headOid: review.repository.headOid,
10818
+ parentOid: commit.parentOid,
10819
+ commitOid: commit.oid,
10820
+ blueprintHash
10821
+ },
10822
+ decision: request.decision,
10823
+ reviewerLabel,
10824
+ summary,
10825
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
10826
+ identityVerified: false
10827
+ };
10828
+ if (!validateDecision(record)) {
10829
+ throw new Error(`Generated review decision failed validation: ${JSON.stringify(validateDecision.errors)}`);
10830
+ }
10831
+ const directory = await decisionsDirectory(repository);
10832
+ const destination = resolve9(directory, `${record.id}.json`);
10833
+ const temporary = resolve9(directory, `.${record.id}.tmp`);
10834
+ await writeFile6(temporary, `${JSON.stringify(record, null, 2)}
10835
+ `, {
10836
+ encoding: "utf8",
10837
+ flag: "wx",
10838
+ mode: 384
10839
+ });
10840
+ await rename(temporary, destination);
10841
+ return record;
10842
+ }
10843
+ async function createRepositoryReviewBundle(repository, review) {
10844
+ const records = await readRepositoryReviewDecisions(repository, review);
10845
+ const bundle = {
10846
+ schema: REPOSITORY_REVIEW_BUNDLE_SCHEMA,
10847
+ repository: {
10848
+ label: review.repository.label,
10849
+ baseOid: review.repository.baseOid,
10850
+ headOid: review.repository.headOid
10851
+ },
10852
+ artifacts: records.map((record) => ({
10853
+ path: `reviews/${record.id}.json`,
10854
+ hash: { algorithm: "sha256", value: sha256Jcs(record) },
10855
+ record
10856
+ }))
10857
+ };
10858
+ const { bundle: validate } = await validators();
10859
+ if (validate === void 0 || !validate(bundle)) {
10860
+ throw new Error(`Generated review bundle failed validation: ${JSON.stringify(validate?.errors)}`);
10861
+ }
10862
+ return bundle;
10863
+ }
10864
+
10865
+ // packages/cli/dist/review-server.js
10677
10866
  var defaultReviewDist = runtimeReviewDist();
10678
10867
  var contentTypes = {
10679
10868
  ".css": "text/css; charset=utf-8",
@@ -10682,6 +10871,32 @@ var contentTypes = {
10682
10871
  ".json": "application/json; charset=utf-8",
10683
10872
  ".svg": "image/svg+xml"
10684
10873
  };
10874
+ async function readJsonBody(request) {
10875
+ const maximumBytes = 32 * 1024;
10876
+ const declaredLength = Number(request.headers["content-length"] ?? "0");
10877
+ if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
10878
+ throw new RepositoryReviewStateError("REVIEW_DECISION_TOO_LARGE", "Review decision request exceeds 32 KiB");
10879
+ }
10880
+ const chunks = [];
10881
+ let size = 0;
10882
+ for await (const chunk of request) {
10883
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
10884
+ size += bytes.length;
10885
+ if (size > maximumBytes) {
10886
+ throw new RepositoryReviewStateError("REVIEW_DECISION_TOO_LARGE", "Review decision request exceeds 32 KiB");
10887
+ }
10888
+ chunks.push(bytes);
10889
+ }
10890
+ try {
10891
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
10892
+ } catch {
10893
+ throw new RepositoryReviewStateError("REVIEW_DECISION_INVALID", "Review decision request is not valid JSON");
10894
+ }
10895
+ }
10896
+ function sendJson(response, status, value) {
10897
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
10898
+ response.writeHead(status).end(JSON.stringify(value));
10899
+ }
10685
10900
  function safeAssetPath(distDir, requestUrl) {
10686
10901
  let pathname;
10687
10902
  try {
@@ -10690,23 +10905,58 @@ function safeAssetPath(distDir, requestUrl) {
10690
10905
  return null;
10691
10906
  }
10692
10907
  const relative2 = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
10693
- const candidate = resolve9(distDir, relative2);
10694
- return candidate === distDir || candidate.startsWith(`${distDir}${sep5}`) ? candidate : null;
10908
+ const candidate = resolve10(distDir, relative2);
10909
+ return candidate === distDir || candidate.startsWith(`${distDir}${sep6}`) ? candidate : null;
10695
10910
  }
10696
10911
  async function startReviewServer(options = {}) {
10697
10912
  const host = options.host ?? "127.0.0.1";
10698
10913
  const port = options.port ?? 4173;
10699
- const distDir = resolve9(options.distDir ?? defaultReviewDist);
10700
- const evidenceBundlePath = options.evidenceBundlePath === void 0 ? void 0 : resolve9(options.evidenceBundlePath);
10914
+ const distDir = resolve10(options.distDir ?? defaultReviewDist);
10915
+ const evidenceBundlePath = options.evidenceBundlePath === void 0 ? void 0 : resolve10(options.evidenceBundlePath);
10701
10916
  const server = createServer(async (request, response) => {
10702
10917
  response.setHeader("Cache-Control", "no-store");
10703
10918
  response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'");
10704
10919
  response.setHeader("X-Content-Type-Options", "nosniff");
10920
+ const pathname = new URL(request.url ?? "/", "http://localhost").pathname;
10921
+ if (pathname === "/api/review-decisions" && request.method === "POST") {
10922
+ const state = options.repositoryReviewState;
10923
+ if (state === void 0) {
10924
+ response.writeHead(404).end("Local review state unavailable");
10925
+ return;
10926
+ }
10927
+ const expectedOrigin = request.headers.host === void 0 ? void 0 : `http://${request.headers.host}`;
10928
+ if (request.headers.origin !== expectedOrigin || request.headers["x-graphrefly-review"] !== "1") {
10929
+ response.writeHead(403).end("Same-origin review action required");
10930
+ return;
10931
+ }
10932
+ if (!request.headers["content-type"]?.startsWith("application/json")) {
10933
+ response.writeHead(415).end("application/json required");
10934
+ return;
10935
+ }
10936
+ try {
10937
+ const input = await readJsonBody(request);
10938
+ const record = await writeRepositoryReviewDecision(state.repository, state.review, input);
10939
+ sendJson(response, 201, record);
10940
+ } catch (error) {
10941
+ if (error instanceof RepositoryReviewStateError) {
10942
+ sendJson(response, error.code === "REVIEW_TARGET_STALE" ? 409 : 400, {
10943
+ code: error.code,
10944
+ message: error.message
10945
+ });
10946
+ } else {
10947
+ sendJson(response, 500, {
10948
+ code: "REVIEW_STATE_FAILED",
10949
+ message: "Local review state could not be updated"
10950
+ });
10951
+ }
10952
+ }
10953
+ return;
10954
+ }
10705
10955
  if (request.method !== "GET" && request.method !== "HEAD") {
10706
10956
  response.writeHead(405).end("Method not allowed");
10707
10957
  return;
10708
10958
  }
10709
- if (new URL(request.url ?? "/", "http://localhost").pathname === "/api/review-data") {
10959
+ if (pathname === "/api/review-data") {
10710
10960
  if (options.reviewData === void 0) {
10711
10961
  response.writeHead(404).end("Review data unavailable");
10712
10962
  return;
@@ -10719,7 +10969,47 @@ async function startReviewServer(options = {}) {
10719
10969
  response.end(JSON.stringify(options.reviewData));
10720
10970
  return;
10721
10971
  }
10722
- if (new URL(request.url ?? "/", "http://localhost").pathname === "/api/evidence-bundle") {
10972
+ if (pathname === "/api/review-decisions") {
10973
+ const state = options.repositoryReviewState;
10974
+ if (state === void 0) {
10975
+ response.writeHead(404).end("Local review state unavailable");
10976
+ return;
10977
+ }
10978
+ try {
10979
+ const records = await readRepositoryReviewDecisions(state.repository, state.review);
10980
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
10981
+ response.writeHead(200);
10982
+ if (request.method === "HEAD")
10983
+ response.end();
10984
+ else
10985
+ response.end(JSON.stringify(records));
10986
+ } catch {
10987
+ response.writeHead(500).end("Local review state unavailable");
10988
+ }
10989
+ return;
10990
+ }
10991
+ if (pathname === "/api/review-decisions/export") {
10992
+ const state = options.repositoryReviewState;
10993
+ if (state === void 0) {
10994
+ response.writeHead(404).end("Portable review bundle unavailable");
10995
+ return;
10996
+ }
10997
+ try {
10998
+ const bundle = await createRepositoryReviewBundle(state.repository, state.review);
10999
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
11000
+ response.setHeader("Content-Disposition", 'attachment; filename="graphrefly-stack-reviews.json"');
11001
+ response.writeHead(200);
11002
+ if (request.method === "HEAD")
11003
+ response.end();
11004
+ else
11005
+ response.end(`${JSON.stringify(bundle, null, 2)}
11006
+ `);
11007
+ } catch {
11008
+ response.writeHead(500).end("Portable review bundle unavailable");
11009
+ }
11010
+ return;
11011
+ }
11012
+ if (pathname === "/api/evidence-bundle") {
10723
11013
  if (evidenceBundlePath === void 0) {
10724
11014
  response.writeHead(404).end("Portable evidence bundle unavailable");
10725
11015
  return;
@@ -10783,11 +11073,11 @@ Usage:
10783
11073
  grfs review [--bundle <path>] [--fixture <runtime-suite.json>] [--host 127.0.0.1] [--port 4173]
10784
11074
  grfs export [--fixture <runtime-suite.json>] [--plan-run <path>] [--replan-run <path>] [--output <path>] --json
10785
11075
  `;
10786
- var defaultFixtureOutput = resolve10(".private/fixtures/refresh-token-rotation-v1");
10787
- var defaultRuntimeSuite = resolve10(defaultFixtureOutput, ".graphrefly-stack/runtime-suite.json");
11076
+ var defaultFixtureOutput = resolve11(".private/fixtures/refresh-token-rotation-v1");
11077
+ var defaultRuntimeSuite = resolve11(defaultFixtureOutput, ".graphrefly-stack/runtime-suite.json");
10788
11078
  function isWithin(root, candidate) {
10789
- const normalizedRoot = resolve10(root);
10790
- const normalizedCandidate = resolve10(candidate);
11079
+ const normalizedRoot = resolve11(root);
11080
+ const normalizedCandidate = resolve11(candidate);
10791
11081
  return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`);
10792
11082
  }
10793
11083
  function commandFrom(argv) {
@@ -10836,12 +11126,12 @@ function caseFrom(runtime, caseId) {
10836
11126
  return runtime.cases.find((fixtureCase) => fixtureCase.caseId === caseId);
10837
11127
  }
10838
11128
  async function readJson2(path) {
10839
- return JSON.parse(await readFile7(path, "utf8"));
11129
+ return JSON.parse(await readFile8(path, "utf8"));
10840
11130
  }
10841
11131
  async function readLiveRun(path, kind) {
10842
11132
  if (path === void 0)
10843
11133
  return void 0;
10844
- const value = await readJson2(resolve10(path));
11134
+ const value = await readJson2(resolve11(path));
10845
11135
  if (value.schema !== "urn:graphrefly-stack:live-run:v1" || value.kind !== kind || value.mode !== "live" || typeof value.provenance !== "object" || value.provenance === null) {
10846
11136
  throw new Error(`Invalid ${kind} live-run artifact`);
10847
11137
  }
@@ -10955,14 +11245,14 @@ ${help}`);
10955
11245
  }
10956
11246
  }
10957
11247
  if (command === "fixture-create") {
10958
- const output = resolve10(readOption(argv, "--output") ?? defaultFixtureOutput);
10959
- if (!isWithin(resolve10(".private"), output)) {
11248
+ const output = resolve11(readOption(argv, "--output") ?? defaultFixtureOutput);
11249
+ if (!isWithin(resolve11(".private"), output)) {
10960
11250
  return failure(command, json, "OUTPUT_OUTSIDE_PRIVATE_ROOT", output, "replay");
10961
11251
  }
10962
11252
  const runtime = await createFlagshipFixture(output, argv.includes("--force"));
10963
11253
  success(command, "replay", {
10964
11254
  repository: runtime.repository,
10965
- runtimeSuite: resolve10(output, ".graphrefly-stack/runtime-suite.json"),
11255
+ runtimeSuite: resolve11(output, ".graphrefly-stack/runtime-suite.json"),
10966
11256
  refs: runtime.refs
10967
11257
  }, json);
10968
11258
  return 0;
@@ -11013,8 +11303,8 @@ ${help}`);
11013
11303
  }
11014
11304
  if (command === "export") {
11015
11305
  const runtime = await readRuntimeSuite(readOption(argv, "--fixture") ?? defaultRuntimeSuite);
11016
- const requestedOutput = resolve10(readOption(argv, "--output") ?? resolve10(".private/exports/refresh-token-rotation-v1"));
11017
- if (!isWithin(resolve10(".private"), requestedOutput) && !isWithin(resolve10("evidence/runs"), requestedOutput)) {
11306
+ const requestedOutput = resolve11(readOption(argv, "--output") ?? resolve11(".private/exports/refresh-token-rotation-v1"));
11307
+ if (!isWithin(resolve11(".private"), requestedOutput) && !isWithin(resolve11("evidence/runs"), requestedOutput)) {
11018
11308
  return failure(command, json, "OUTPUT_OUTSIDE_EVIDENCE_ROOT", requestedOutput);
11019
11309
  }
11020
11310
  const liveRuns = {
@@ -11036,6 +11326,7 @@ ${help}`);
11036
11326
  }
11037
11327
  let reviewData;
11038
11328
  let evidenceBundlePath;
11329
+ let repositoryReviewState;
11039
11330
  const repository = readOption(argv, "--repo");
11040
11331
  const base = readOption(argv, "--base");
11041
11332
  const head = readOption(argv, "--head");
@@ -11049,7 +11340,9 @@ ${help}`);
11049
11340
  return failure(command, json, "REVIEW_SOURCE_CONFLICT", "Generic repository review cannot be mixed with fixture or bundle input");
11050
11341
  }
11051
11342
  try {
11052
- reviewData = await createRepositoryReview({ repository, base, head });
11343
+ const repositoryReview = await createRepositoryReview({ repository, base, head });
11344
+ reviewData = repositoryReview;
11345
+ repositoryReviewState = { repository: resolve11(repository), review: repositoryReview };
11053
11346
  } catch (error) {
11054
11347
  if (error instanceof RepositoryReviewError) {
11055
11348
  return failure(command, json, error.code, error.message);
@@ -11100,7 +11393,8 @@ ${help}`);
11100
11393
  host,
11101
11394
  port,
11102
11395
  reviewData,
11103
- evidenceBundlePath
11396
+ evidenceBundlePath,
11397
+ repositoryReviewState
11104
11398
  });
11105
11399
  process.stderr.write(`GraphReFly Stack review shell (${CORE_ARCHITECTURE.version}) listening at ${running.url}
11106
11400
  `);
@@ -1 +1 @@
1
- import{m as e}from"./src-BMa7vLb8.js";import{n as t}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import"./chunk-WYO6CB5R-C36byBU-.js";import"./index-CqjR4YYp.js";import{m as n,t as r}from"./mermaid-parser.core-BJLV4lM2.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-BshP08Ez.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";var c=n().RailroadAbnf.parser.LangiumParser,l=t(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=t(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=t(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=t(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=t(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=t(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=t(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:t(t=>{o.clear(),e.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(t);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new r(n);let i=n.value;e.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),e.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
1
+ import{m as e}from"./src-BMa7vLb8.js";import{n as t}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import"./chunk-WYO6CB5R-C36byBU-.js";import"./index-CUaeHlgA.js";import{m as n,t as r}from"./mermaid-parser.core-BJLV4lM2.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-Ct40HB9X.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";var c=n().RailroadAbnf.parser.LangiumParser,l=t(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=t(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=t(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=t(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=t(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=t(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=t(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:t(t=>{o.clear(),e.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(t);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new r(n);let i=n.value;e.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),e.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};