@graphrefly/stack 0.1.0 → 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 +49 -24
  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 +360 -32
  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,
@@ -8563,8 +8563,9 @@ var require_semver2 = __commonJS({
8563
8563
  });
8564
8564
 
8565
8565
  // packages/cli/dist/cli.js
8566
- import { readFile as readFile7 } from "node:fs/promises";
8567
- import { resolve as resolve10 } from "node:path";
8566
+ import { realpathSync } from "node:fs";
8567
+ import { readFile as readFile8 } from "node:fs/promises";
8568
+ import { resolve as resolve11 } from "node:path";
8568
8569
  import { pathToFileURL as pathToFileURL3 } from "node:url";
8569
8570
 
8570
8571
  // packages/contracts/dist/index.js
@@ -8626,6 +8627,8 @@ function sha256Jcs(value) {
8626
8627
  // packages/contracts/dist/repository-review.js
8627
8628
  var REPOSITORY_CONFIG_SCHEMA = "graphrefly.stack.repository.v1";
8628
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";
8629
8632
  var SUPPORTED_GRAPHREFLY_RANGE = ">=0.3.0 <0.4.0";
8630
8633
 
8631
8634
  // packages/contracts/dist/index.js
@@ -10149,7 +10152,7 @@ process.stdout.write(JSON.stringify(blueprint));
10149
10152
  // packages/cli/dist/repository-review.js
10150
10153
  import { spawnSync as spawnSync5 } from "node:child_process";
10151
10154
  import { createHash as createHash6 } from "node:crypto";
10152
- import { access as access2, mkdir as mkdir5, readFile as readFile6, realpath as realpath3, rm as rm4, symlink } from "node:fs/promises";
10155
+ import { access as access2, mkdir as mkdir5, readFile as readFile6, realpath as realpath3, rm as rm4, symlink, writeFile as writeFile5 } from "node:fs/promises";
10153
10156
  import { createRequire as createRequire2 } from "node:module";
10154
10157
  import { basename as basename2, dirname as dirname5, parse, resolve as resolve8, sep as sep4 } from "node:path";
10155
10158
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -10283,6 +10286,7 @@ var reviewSchemaPath = runtimeAssetPath("contracts/repository/v1/review.schema.j
10283
10286
  var configName2 = ".graphrefly-stack.json";
10284
10287
  var lockCandidates = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
10285
10288
  var maxReviewCommits = 64;
10289
+ var maxEntrypointBytes = 64 * 1024;
10286
10290
  var RepositoryReviewError = class extends Error {
10287
10291
  code;
10288
10292
  constructor(code, message) {
@@ -10365,6 +10369,22 @@ function ensureInside(root, candidate) {
10365
10369
  throw new RepositoryReviewError("ENTRYPOINT_ESCAPE", "Blueprint entrypoint escapes the worktree");
10366
10370
  }
10367
10371
  }
10372
+ async function readConfiguredEntrypoint(repository, entrypointPath) {
10373
+ const entrypoint = resolve8(repository, entrypointPath);
10374
+ ensureInside(repository, entrypoint);
10375
+ let canonicalEntrypoint;
10376
+ try {
10377
+ canonicalEntrypoint = await realpath3(entrypoint);
10378
+ } catch {
10379
+ throw new RepositoryReviewError("ENTRYPOINT_MISSING", "Configured Blueprint entrypoint is missing");
10380
+ }
10381
+ ensureInside(repository, canonicalEntrypoint);
10382
+ const source = await readFile6(canonicalEntrypoint);
10383
+ if (source.byteLength > maxEntrypointBytes) {
10384
+ throw new RepositoryReviewError("ENTRYPOINT_TOO_LARGE", `Configured Blueprint entrypoint exceeds ${maxEntrypointBytes} bytes`);
10385
+ }
10386
+ return source;
10387
+ }
10368
10388
  function containsAbsolutePath(value) {
10369
10389
  if (typeof value === "string")
10370
10390
  return value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value);
@@ -10416,7 +10436,7 @@ async function resolveTargetRuntime(repository) {
10416
10436
  diffGraphBlueprints: graphModule.diffGraphBlueprints
10417
10437
  };
10418
10438
  }
10419
- async function executeBlueprint(repository, revision, entrypointPath, runtime) {
10439
+ async function executeBlueprint(repository, revision, entrypointPath, entrypointSource, runtime) {
10420
10440
  const worktreeRoot = resolve8(dirname5(repository), `.graphrefly-stack-review-${basename2(repository)}-${process.pid}`);
10421
10441
  const worktree = resolve8(worktreeRoot, revision.slice(0, 16));
10422
10442
  await mkdir5(worktreeRoot, { recursive: true });
@@ -10429,8 +10449,19 @@ async function executeBlueprint(repository, revision, entrypointPath, runtime) {
10429
10449
  try {
10430
10450
  await access2(entrypoint);
10431
10451
  } catch {
10432
- throw new RepositoryReviewError("ENTRYPOINT_MISSING", `Blueprint entrypoint is missing at ${revision.slice(0, 12)}`);
10452
+ let canonicalParent;
10453
+ try {
10454
+ canonicalParent = await realpath3(dirname5(entrypoint));
10455
+ } catch {
10456
+ throw new RepositoryReviewError("ENTRYPOINT_MISSING", `Blueprint entrypoint parent is missing at ${revision.slice(0, 12)}`);
10457
+ }
10458
+ ensureInside(canonicalWorktree, canonicalParent);
10459
+ await writeFile5(resolve8(canonicalParent, basename2(entrypoint)), entrypointSource, {
10460
+ flag: "wx"
10461
+ });
10433
10462
  }
10463
+ const canonicalEntrypoint = await realpath3(entrypoint);
10464
+ ensureInside(canonicalWorktree, canonicalEntrypoint);
10434
10465
  const worktreeNodeModules = resolve8(canonicalWorktree, "node_modules");
10435
10466
  await rm4(worktreeNodeModules, { recursive: true, force: true });
10436
10467
  await symlink(runtime.nodeModulesRoot, worktreeNodeModules, "dir");
@@ -10439,7 +10470,7 @@ async function executeBlueprint(repository, revision, entrypointPath, runtime) {
10439
10470
  `--allow-fs-read=${canonicalWorktree}`,
10440
10471
  `--allow-fs-read=${runtime.nodeModulesRoot}`,
10441
10472
  "--disable-warning=ExperimentalWarning",
10442
- entrypoint
10473
+ canonicalEntrypoint
10443
10474
  ], {
10444
10475
  cwd: canonicalWorktree,
10445
10476
  encoding: "utf8",
@@ -10543,6 +10574,27 @@ async function validateReview(review) {
10543
10574
  throw new RepositoryReviewError("REVIEW_SCHEMA_INVALID", `Repository review failed validation: ${JSON.stringify(validate.errors)}`);
10544
10575
  }
10545
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
+ }
10546
10598
  async function createRepositoryReview(options) {
10547
10599
  let repository;
10548
10600
  try {
@@ -10552,6 +10604,7 @@ async function createRepositoryReview(options) {
10552
10604
  throw new RepositoryReviewError("REPOSITORY_INVALID", "Repository must be a local Git worktree");
10553
10605
  }
10554
10606
  const config = await readRepositoryConfig(repository);
10607
+ const entrypointSource = await readConfiguredEntrypoint(repository, config.blueprint.entrypoint);
10555
10608
  const git2 = new SystemGitAdapter();
10556
10609
  let baseOid;
10557
10610
  let headOid;
@@ -10595,7 +10648,7 @@ async function createRepositoryReview(options) {
10595
10648
  await validateDependencyContinuity(repository, allRevisions, runtime.version);
10596
10649
  const evidence = [];
10597
10650
  for (const revision of allRevisions) {
10598
- const blueprint = await executeBlueprint(repository, revision, config.blueprint.entrypoint, runtime);
10651
+ const blueprint = await executeBlueprint(repository, revision, config.blueprint.entrypoint, entrypointSource, runtime);
10599
10652
  evidence.push({
10600
10653
  oid: revision,
10601
10654
  subject: gitText(repository, ["show", "-s", "--format=%s", revision]),
@@ -10626,6 +10679,7 @@ async function createRepositoryReview(options) {
10626
10679
  source: "generic-repository",
10627
10680
  repository: {
10628
10681
  label: basename2(repository),
10682
+ headLabel: reviewHeadLabel(repository, options.head, headOid),
10629
10683
  graphreflyVersion: runtime.version,
10630
10684
  entrypoint: config.blueprint.entrypoint,
10631
10685
  baseOid,
@@ -10643,7 +10697,172 @@ async function createRepositoryReview(options) {
10643
10697
  import { createReadStream } from "node:fs";
10644
10698
  import { stat as stat4 } from "node:fs/promises";
10645
10699
  import { createServer } from "node:http";
10646
- 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
10647
10866
  var defaultReviewDist = runtimeReviewDist();
10648
10867
  var contentTypes = {
10649
10868
  ".css": "text/css; charset=utf-8",
@@ -10652,6 +10871,32 @@ var contentTypes = {
10652
10871
  ".json": "application/json; charset=utf-8",
10653
10872
  ".svg": "image/svg+xml"
10654
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
+ }
10655
10900
  function safeAssetPath(distDir, requestUrl) {
10656
10901
  let pathname;
10657
10902
  try {
@@ -10660,23 +10905,58 @@ function safeAssetPath(distDir, requestUrl) {
10660
10905
  return null;
10661
10906
  }
10662
10907
  const relative2 = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
10663
- const candidate = resolve9(distDir, relative2);
10664
- 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;
10665
10910
  }
10666
10911
  async function startReviewServer(options = {}) {
10667
10912
  const host = options.host ?? "127.0.0.1";
10668
10913
  const port = options.port ?? 4173;
10669
- const distDir = resolve9(options.distDir ?? defaultReviewDist);
10670
- 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);
10671
10916
  const server = createServer(async (request, response) => {
10672
10917
  response.setHeader("Cache-Control", "no-store");
10673
10918
  response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'");
10674
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
+ }
10675
10955
  if (request.method !== "GET" && request.method !== "HEAD") {
10676
10956
  response.writeHead(405).end("Method not allowed");
10677
10957
  return;
10678
10958
  }
10679
- if (new URL(request.url ?? "/", "http://localhost").pathname === "/api/review-data") {
10959
+ if (pathname === "/api/review-data") {
10680
10960
  if (options.reviewData === void 0) {
10681
10961
  response.writeHead(404).end("Review data unavailable");
10682
10962
  return;
@@ -10689,7 +10969,47 @@ async function startReviewServer(options = {}) {
10689
10969
  response.end(JSON.stringify(options.reviewData));
10690
10970
  return;
10691
10971
  }
10692
- 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") {
10693
11013
  if (evidenceBundlePath === void 0) {
10694
11014
  response.writeHead(404).end("Portable evidence bundle unavailable");
10695
11015
  return;
@@ -10753,11 +11073,11 @@ Usage:
10753
11073
  grfs review [--bundle <path>] [--fixture <runtime-suite.json>] [--host 127.0.0.1] [--port 4173]
10754
11074
  grfs export [--fixture <runtime-suite.json>] [--plan-run <path>] [--replan-run <path>] [--output <path>] --json
10755
11075
  `;
10756
- var defaultFixtureOutput = resolve10(".private/fixtures/refresh-token-rotation-v1");
10757
- 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");
10758
11078
  function isWithin(root, candidate) {
10759
- const normalizedRoot = resolve10(root);
10760
- const normalizedCandidate = resolve10(candidate);
11079
+ const normalizedRoot = resolve11(root);
11080
+ const normalizedCandidate = resolve11(candidate);
10761
11081
  return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`);
10762
11082
  }
10763
11083
  function commandFrom(argv) {
@@ -10806,12 +11126,12 @@ function caseFrom(runtime, caseId) {
10806
11126
  return runtime.cases.find((fixtureCase) => fixtureCase.caseId === caseId);
10807
11127
  }
10808
11128
  async function readJson2(path) {
10809
- return JSON.parse(await readFile7(path, "utf8"));
11129
+ return JSON.parse(await readFile8(path, "utf8"));
10810
11130
  }
10811
11131
  async function readLiveRun(path, kind) {
10812
11132
  if (path === void 0)
10813
11133
  return void 0;
10814
- const value = await readJson2(resolve10(path));
11134
+ const value = await readJson2(resolve11(path));
10815
11135
  if (value.schema !== "urn:graphrefly-stack:live-run:v1" || value.kind !== kind || value.mode !== "live" || typeof value.provenance !== "object" || value.provenance === null) {
10816
11136
  throw new Error(`Invalid ${kind} live-run artifact`);
10817
11137
  }
@@ -10925,14 +11245,14 @@ ${help}`);
10925
11245
  }
10926
11246
  }
10927
11247
  if (command === "fixture-create") {
10928
- const output = resolve10(readOption(argv, "--output") ?? defaultFixtureOutput);
10929
- if (!isWithin(resolve10(".private"), output)) {
11248
+ const output = resolve11(readOption(argv, "--output") ?? defaultFixtureOutput);
11249
+ if (!isWithin(resolve11(".private"), output)) {
10930
11250
  return failure(command, json, "OUTPUT_OUTSIDE_PRIVATE_ROOT", output, "replay");
10931
11251
  }
10932
11252
  const runtime = await createFlagshipFixture(output, argv.includes("--force"));
10933
11253
  success(command, "replay", {
10934
11254
  repository: runtime.repository,
10935
- runtimeSuite: resolve10(output, ".graphrefly-stack/runtime-suite.json"),
11255
+ runtimeSuite: resolve11(output, ".graphrefly-stack/runtime-suite.json"),
10936
11256
  refs: runtime.refs
10937
11257
  }, json);
10938
11258
  return 0;
@@ -10983,8 +11303,8 @@ ${help}`);
10983
11303
  }
10984
11304
  if (command === "export") {
10985
11305
  const runtime = await readRuntimeSuite(readOption(argv, "--fixture") ?? defaultRuntimeSuite);
10986
- const requestedOutput = resolve10(readOption(argv, "--output") ?? resolve10(".private/exports/refresh-token-rotation-v1"));
10987
- 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)) {
10988
11308
  return failure(command, json, "OUTPUT_OUTSIDE_EVIDENCE_ROOT", requestedOutput);
10989
11309
  }
10990
11310
  const liveRuns = {
@@ -11006,6 +11326,7 @@ ${help}`);
11006
11326
  }
11007
11327
  let reviewData;
11008
11328
  let evidenceBundlePath;
11329
+ let repositoryReviewState;
11009
11330
  const repository = readOption(argv, "--repo");
11010
11331
  const base = readOption(argv, "--base");
11011
11332
  const head = readOption(argv, "--head");
@@ -11019,7 +11340,9 @@ ${help}`);
11019
11340
  return failure(command, json, "REVIEW_SOURCE_CONFLICT", "Generic repository review cannot be mixed with fixture or bundle input");
11020
11341
  }
11021
11342
  try {
11022
- reviewData = await createRepositoryReview({ repository, base, head });
11343
+ const repositoryReview = await createRepositoryReview({ repository, base, head });
11344
+ reviewData = repositoryReview;
11345
+ repositoryReviewState = { repository: resolve11(repository), review: repositoryReview };
11023
11346
  } catch (error) {
11024
11347
  if (error instanceof RepositoryReviewError) {
11025
11348
  return failure(command, json, error.code, error.message);
@@ -11070,7 +11393,8 @@ ${help}`);
11070
11393
  host,
11071
11394
  port,
11072
11395
  reviewData,
11073
- evidenceBundlePath
11396
+ evidenceBundlePath,
11397
+ repositoryReviewState
11074
11398
  });
11075
11399
  process.stderr.write(`GraphReFly Stack review shell (${CORE_ARCHITECTURE.version}) listening at ${running.url}
11076
11400
  `);
@@ -11079,7 +11403,11 @@ ${help}`);
11079
11403
  process.once("SIGTERM", close);
11080
11404
  return 0;
11081
11405
  }
11082
- var entry = process.argv[1] ? pathToFileURL3(process.argv[1]).href : "";
11406
+ var entry = "";
11407
+ try {
11408
+ entry = process.argv[1] ? pathToFileURL3(realpathSync(process.argv[1])).href : "";
11409
+ } catch {
11410
+ }
11083
11411
  if (import.meta.url === entry) {
11084
11412
  runCli().then((code) => {
11085
11413
  process.exitCode = code;
@@ -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};