@a9n-shoji/rvw 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +143 -38
  3. package/dist/cli.mjs +1590 -83
  4. package/dist/cli.mjs.map +4 -4
  5. package/dist/web/assets/{DocumentViewer-CWTnSlpG.js → DocumentViewer-D_PY7MU3.js} +2 -2
  6. package/dist/web/assets/StructureViewer-dhpw7G3m.js +7 -0
  7. package/dist/web/assets/{WalkthroughPanel-D2KqSIf9.js → WalkthroughPanel-DvyIFY5z.js} +1 -1
  8. package/dist/web/assets/{WalkthroughViewer-BDb-99Qk.js → WalkthroughViewer-y-grnBAx.js} +1 -1
  9. package/dist/web/assets/index-D6YvPSBz.css +1 -0
  10. package/dist/web/assets/index-k6AcFBgL.js +11 -0
  11. package/dist/web/index.html +3 -3
  12. package/migrations/018_comment_watch_tasks.sql +10 -0
  13. package/migrations/019_comment_watch_write_reservations.sql +11 -0
  14. package/migrations/020_structure_retired_region_ids.sql +6 -0
  15. package/package.json +3 -1
  16. package/skills/rvw/SKILL.md +1 -1
  17. package/skills/rvw-review-compose/SKILL.md +171 -0
  18. package/skills/rvw-review-compose/agents/openai.yaml +4 -0
  19. package/skills/rvw-review-compose/references/review-composition.md +311 -0
  20. package/skills/rvw-structure/SKILL.md +107 -22
  21. package/skills/rvw-structure/agents/openai.yaml +2 -2
  22. package/skills/rvw-structure/references/structure-authoring.md +174 -30
  23. package/skills/rvw-walkthrough/SKILL.md +37 -12
  24. package/skills/rvw-walkthrough/agents/openai.yaml +2 -2
  25. package/skills/rvw-walkthrough/references/walkthrough-authoring.md +29 -4
  26. package/skills/rvw-watch-comments/SKILL.md +58 -13
  27. package/skills/rvw-watch-comments/scripts/auto-ack.mjs +67 -9
  28. package/skills/rvw-watch-comments/scripts/preflight.mjs +2 -1
  29. package/skills/rvw-watch-comments/scripts/watch-driver.mjs +47 -1
  30. package/skills/rvw-watch-comments/scripts/watch-state.mjs +376 -42
  31. package/dist/web/assets/StructureViewer-EKDvAQ2a.js +0 -5
  32. package/dist/web/assets/index-B0xRoLW2.css +0 -1
  33. package/dist/web/assets/index-DC6BgBp4.js +0 -11
package/dist/cli.mjs CHANGED
@@ -18698,6 +18698,52 @@ function parseStructureUri(uri) {
18698
18698
  return id;
18699
18699
  }
18700
18700
 
18701
+ // src/domain/structure-presentation.ts
18702
+ function compareStructureIds(left, right) {
18703
+ return left < right ? -1 : left > right ? 1 : 0;
18704
+ }
18705
+ function canonicalStructureBackboneEdgeIds(edgeIds) {
18706
+ return [...edgeIds].sort(compareStructureIds);
18707
+ }
18708
+ function canonicalStructureRegionNodeIds(nodeIds) {
18709
+ return [...nodeIds].sort(compareStructureIds);
18710
+ }
18711
+ function canonicalStructurePresentationRegions(regions) {
18712
+ return [...regions].map((region) => ({
18713
+ ...region,
18714
+ nodeIds: canonicalStructureRegionNodeIds(region.nodeIds)
18715
+ })).sort((left, right) => compareStructureIds(left.id, right.id));
18716
+ }
18717
+ function structureBackboneNodeIds(edges) {
18718
+ const nodeIds = /* @__PURE__ */ new Set();
18719
+ for (const edge of edges) {
18720
+ nodeIds.add(edge.from);
18721
+ nodeIds.add(edge.to);
18722
+ }
18723
+ return nodeIds;
18724
+ }
18725
+ function isStructureBackboneWeaklyConnected(edges, startNodeId) {
18726
+ const nodeIds = structureBackboneNodeIds(edges);
18727
+ if (!nodeIds.has(startNodeId)) return false;
18728
+ const neighbors = new Map([...nodeIds].map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
18729
+ for (const edge of edges) {
18730
+ if (edge.from === edge.to) continue;
18731
+ neighbors.get(edge.from).add(edge.to);
18732
+ neighbors.get(edge.to).add(edge.from);
18733
+ }
18734
+ const reached = /* @__PURE__ */ new Set([startNodeId]);
18735
+ const queue = [startNodeId];
18736
+ while (queue.length > 0) {
18737
+ const current = queue.shift();
18738
+ for (const neighbor of neighbors.get(current) ?? []) {
18739
+ if (reached.has(neighbor)) continue;
18740
+ reached.add(neighbor);
18741
+ queue.push(neighbor);
18742
+ }
18743
+ }
18744
+ return reached.size === nodeIds.size;
18745
+ }
18746
+
18701
18747
  // src/domain/walkthrough-uri.ts
18702
18748
  var prefix2 = "rvw://walkthrough/";
18703
18749
  var walkthroughIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -18715,6 +18761,63 @@ function parseWalkthroughUri(uri) {
18715
18761
  return id;
18716
18762
  }
18717
18763
 
18764
+ // src/shared/constants.ts
18765
+ var APP_VERSION = "0.6.0";
18766
+ var PROTOCOL_VERSION = 5;
18767
+ var VIEWER_ID_HEADER = "x-rvw-viewer-id";
18768
+ var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
18769
+ var VIEWER_OPEN_LEASE_QUERY = "viewerLease";
18770
+ var DEFAULT_VIEWER_PORT = 43117;
18771
+ var GIT_OBJECT_ID_PATTERN = /^[0-9a-f]{40,64}$/i;
18772
+ var CONTENT_FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
18773
+ var MAX_TEXT_DOCUMENT_BYTES = 1024 * 1024;
18774
+ var MAX_MARKDOWN_ASSET_BYTES = 5 * 1024 * 1024;
18775
+ var MAX_GITHUB_ATTACHMENT_BYTES = 10 * 1024 * 1024;
18776
+ var MAX_GITHUB_ATTACHMENT_STDERR_BYTES = 64 * 1024;
18777
+ var GITHUB_ATTACHMENT_TIMEOUT_MS = 3e4;
18778
+ var MAX_COMMENT_BODY_BYTES = 64 * 1024;
18779
+ var MAX_AUTHOR_LABEL_CHARACTERS = 100;
18780
+ var MAX_IDEMPOTENCY_KEY_CHARACTERS = 200;
18781
+ var DEFAULT_PULL_REQUEST_LIST_LIMIT = 50;
18782
+ var MAX_PULL_REQUEST_LIST_LIMIT = 100;
18783
+ var DEFAULT_COMMENT_LIST_LIMIT = 50;
18784
+ var MAX_COMMENT_LIST_LIMIT = 100;
18785
+ var DEFAULT_COMMENT_WATCH_LIMIT = 100;
18786
+ var MAX_COMMENT_WATCH_LIMIT = 100;
18787
+ var DEFAULT_COMMENT_WATCH_INTERVAL_SECONDS = 10;
18788
+ var MAX_COMMENT_WATCH_INTERVAL_SECONDS = 300;
18789
+ var MAX_COMMENT_LIST_BODY_PREVIEW_BYTES = 512;
18790
+ var MAX_CODE_REFERENCES = 200;
18791
+ var MAX_CODE_REFERENCE_LABEL_CHARACTERS = 200;
18792
+ var MAX_CODE_REFERENCE_PATH_CHARACTERS = 4096;
18793
+ var MAX_CODE_REFERENCE_DESCRIPTION_CHARACTERS = 1e3;
18794
+ var MAX_WALKTHROUGH_BODY_BYTES = 256 * 1024;
18795
+ var MAX_WALKTHROUGH_TITLE_CHARACTERS = 200;
18796
+ var MAX_STRUCTURE_PAYLOAD_BYTES = 2 * 1024 * 1024;
18797
+ var MAX_STRUCTURE_TITLE_CHARACTERS = 200;
18798
+ var MAX_STRUCTURE_SCOPE_CHARACTERS = 4e3;
18799
+ var MAX_STRUCTURE_NODES = 50;
18800
+ var MAX_STRUCTURE_EDGES = 200;
18801
+ var MAX_STRUCTURE_EDGE_ANCHORS = 20;
18802
+ var MAX_STRUCTURE_SOURCE_ANCHORS = 400;
18803
+ var MAX_STRUCTURE_ID_CHARACTERS = 64;
18804
+ var STRUCTURE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
18805
+ var MAX_STRUCTURE_LABEL_CHARACTERS = 200;
18806
+ var MAX_STRUCTURE_DESCRIPTION_CHARACTERS = 2e3;
18807
+ var MAX_STRUCTURE_KIND_CHARACTERS = 100;
18808
+ var MAX_STRUCTURE_PRESENTATION_THESIS_CHARACTERS = 1e3;
18809
+ var MAX_STRUCTURE_PRIMARY_BACKBONE_NODES = 12;
18810
+ var MAX_STRUCTURE_PRIMARY_BACKBONE_EDGES = 16;
18811
+ var MAX_STRUCTURE_PRESENTATION_REGIONS = 12;
18812
+ var MAX_STRUCTURE_PRESENTATION_REGION_LABEL_CHARACTERS = 100;
18813
+ var MAX_STRUCTURE_PRESENTATION_REGION_SUMMARY_CHARACTERS = 500;
18814
+ var MAX_SEARCH_QUERY_BYTES = 1024;
18815
+ var MAX_SEARCH_RESULTS = 500;
18816
+ var MAX_SEARCH_STDOUT_BYTES = 8 * 1024 * 1024;
18817
+ var DEFAULT_PROCESS_TIMEOUT_MS = 3e4;
18818
+ var DEFAULT_PROCESS_STDOUT_BYTES = 16 * 1024 * 1024;
18819
+ var DEFAULT_PROCESS_STDERR_BYTES = 2 * 1024 * 1024;
18820
+
18718
18821
  // src/shared/preferences.ts
18719
18822
  var themePreferences = ["light", "dark", "system"];
18720
18823
  function isThemePreference(value) {
@@ -18791,17 +18894,122 @@ function isStructureNode(value) {
18791
18894
  function isStructureEdge(value) {
18792
18895
  return isRecord(value) && typeof value.id === "string" && typeof value.from === "string" && typeof value.to === "string" && typeof value.label === "string" && typeof value.directed === "boolean" && Array.isArray(value.anchors) && value.anchors.every(isSourceAnchor);
18793
18896
  }
18897
+ function hasExactKeys(value, keys2) {
18898
+ const actualKeys = Object.keys(value);
18899
+ return actualKeys.length === keys2.length && actualKeys.every((key2) => keys2.includes(key2));
18900
+ }
18901
+ function isCanonicalPresentationText(value, maximum) {
18902
+ return typeof value === "string" && value === value.trim() && value.length >= 1 && value.length <= maximum;
18903
+ }
18904
+ function isStructurePresentation(value, nodes, edges) {
18905
+ if (!isRecord(value) || !hasExactKeys(value, ["thesis", "startNodeId", "primaryBackbone", "regions"]) || !isCanonicalPresentationText(value.thesis, MAX_STRUCTURE_PRESENTATION_THESIS_CHARACTERS) || typeof value.startNodeId !== "string" || !Array.isArray(value.regions) || value.regions.length > MAX_STRUCTURE_PRESENTATION_REGIONS) {
18906
+ return false;
18907
+ }
18908
+ const nodeIds = new Set(nodes.map((node2) => node2.id));
18909
+ if (!STRUCTURE_ID_PATTERN.test(value.startNodeId) || !nodeIds.has(value.startNodeId)) {
18910
+ return false;
18911
+ }
18912
+ if (value.primaryBackbone !== null) {
18913
+ if (!isRecord(value.primaryBackbone) || !hasExactKeys(value.primaryBackbone, ["edgeIds"]) || !Array.isArray(value.primaryBackbone.edgeIds) || value.primaryBackbone.edgeIds.length < 1 || value.primaryBackbone.edgeIds.length > MAX_STRUCTURE_PRIMARY_BACKBONE_EDGES) {
18914
+ return false;
18915
+ }
18916
+ const edgeById = new Map(edges.map((edge) => [edge.id, edge]));
18917
+ const selectedEdgeIds = /* @__PURE__ */ new Set();
18918
+ const selectedEdges = [];
18919
+ for (const edgeId of value.primaryBackbone.edgeIds) {
18920
+ if (typeof edgeId !== "string" || !STRUCTURE_ID_PATTERN.test(edgeId) || selectedEdgeIds.has(edgeId)) {
18921
+ return false;
18922
+ }
18923
+ selectedEdgeIds.add(edgeId);
18924
+ const edge = edgeById.get(edgeId);
18925
+ if (edge === void 0) return false;
18926
+ selectedEdges.push(edge);
18927
+ }
18928
+ const primaryBackboneNodeIds = structureBackboneNodeIds(selectedEdges);
18929
+ if (primaryBackboneNodeIds.size < 2 || primaryBackboneNodeIds.size > MAX_STRUCTURE_PRIMARY_BACKBONE_NODES || !primaryBackboneNodeIds.has(value.startNodeId) || !isStructureBackboneWeaklyConnected(selectedEdges, value.startNodeId)) {
18930
+ return false;
18931
+ }
18932
+ }
18933
+ const regionIds = /* @__PURE__ */ new Set();
18934
+ const regionByNodeId = /* @__PURE__ */ new Map();
18935
+ for (const region of value.regions) {
18936
+ if (!isRecord(region) || !hasExactKeys(region, ["id", "label", "summary", "nodeIds"]) || typeof region.id !== "string" || !STRUCTURE_ID_PATTERN.test(region.id) || regionIds.has(region.id) || !isCanonicalPresentationText(
18937
+ region.label,
18938
+ MAX_STRUCTURE_PRESENTATION_REGION_LABEL_CHARACTERS
18939
+ ) || !isCanonicalPresentationText(
18940
+ region.summary,
18941
+ MAX_STRUCTURE_PRESENTATION_REGION_SUMMARY_CHARACTERS
18942
+ ) || !Array.isArray(region.nodeIds) || region.nodeIds.length < 1 || region.nodeIds.length > MAX_STRUCTURE_NODES) {
18943
+ return false;
18944
+ }
18945
+ regionIds.add(region.id);
18946
+ const currentRegionNodeIds = /* @__PURE__ */ new Set();
18947
+ for (const nodeId of region.nodeIds) {
18948
+ if (typeof nodeId !== "string" || !STRUCTURE_ID_PATTERN.test(nodeId) || !nodeIds.has(nodeId) || currentRegionNodeIds.has(nodeId) || regionByNodeId.has(nodeId)) {
18949
+ return false;
18950
+ }
18951
+ currentRegionNodeIds.add(nodeId);
18952
+ regionByNodeId.set(nodeId, region.id);
18953
+ }
18954
+ }
18955
+ return true;
18956
+ }
18794
18957
  function structureGraphValue(row) {
18795
18958
  try {
18796
- const value = JSON.parse(stringValue(row, "graph_json"));
18797
- const originNodeId = isRecord(value) && typeof value.originNodeId === "string" ? value.originNodeId : null;
18798
- if (!isRecord(value) || originNodeId === null || !Array.isArray(value.nodes) || !value.nodes.every(isStructureNode) || !Array.isArray(value.edges) || !value.edges.every(isStructureEdge)) {
18959
+ const serializedGraph = stringValue(row, "graph_json");
18960
+ if (Buffer.byteLength(serializedGraph, "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
18961
+ throw new Error("invalid Structure graph size");
18962
+ }
18963
+ const value = JSON.parse(serializedGraph);
18964
+ if (!isRecord(value)) throw new Error("invalid Structure graph");
18965
+ const originNodeId = typeof value.originNodeId === "string" ? value.originNodeId : null;
18966
+ const presentation = value.presentation === void 0 ? null : value.presentation;
18967
+ if (originNodeId === null || !Array.isArray(value.nodes) || !value.nodes.every(isStructureNode) || !Array.isArray(value.edges) || !value.edges.every(isStructureEdge)) {
18799
18968
  throw new Error("invalid Structure graph");
18800
18969
  }
18970
+ const nodes = value.nodes;
18971
+ const edges = value.edges;
18972
+ if (nodes.length < 1 || nodes.length > MAX_STRUCTURE_NODES) {
18973
+ throw new Error("invalid Structure Node count");
18974
+ }
18975
+ const nodeIds = /* @__PURE__ */ new Set();
18976
+ for (const node2 of nodes) {
18977
+ if (!STRUCTURE_ID_PATTERN.test(node2.id) || nodeIds.has(node2.id)) {
18978
+ throw new Error("invalid Structure Node identity");
18979
+ }
18980
+ nodeIds.add(node2.id);
18981
+ }
18982
+ const originNode = nodes.find((node2) => node2.id === originNodeId);
18983
+ if (originNode === void 0 || originNode.anchor === null) {
18984
+ throw new Error("invalid Structure origin");
18985
+ }
18986
+ const edgeIds = /* @__PURE__ */ new Set();
18987
+ for (const edge of edges) {
18988
+ if (!STRUCTURE_ID_PATTERN.test(edge.id) || edgeIds.has(edge.id)) {
18989
+ throw new Error("invalid Structure Edge identity");
18990
+ }
18991
+ edgeIds.add(edge.id);
18992
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
18993
+ throw new Error("invalid Structure Edge endpoint");
18994
+ }
18995
+ }
18996
+ if (presentation !== null && !isStructurePresentation(presentation, nodes, edges)) {
18997
+ throw new Error("invalid Structure presentation");
18998
+ }
18999
+ const normalizedPresentation = presentation === null ? null : {
19000
+ ...presentation,
19001
+ primaryBackbone: presentation.primaryBackbone === null ? null : {
19002
+ edgeIds: canonicalStructureBackboneEdgeIds(
19003
+ presentation.primaryBackbone.edgeIds
19004
+ )
19005
+ },
19006
+ regions: canonicalStructurePresentationRegions(presentation.regions)
19007
+ };
18801
19008
  return {
18802
19009
  originNodeId,
18803
- nodes: value.nodes.map((node2) => ({ ...node2, notation: node2.notation ?? "plain" })),
18804
- edges: value.edges
19010
+ nodes: nodes.map((node2) => ({ ...node2, notation: node2.notation ?? "plain" })),
19011
+ edges,
19012
+ presentation: normalizedPresentation
18805
19013
  };
18806
19014
  } catch (error51) {
18807
19015
  throw new RvwError("DATABASE_ERROR", "Structure graph_json\u304C\u4E0D\u6B63\u3067\u3059\u3002", { cause: error51 });
@@ -19165,6 +19373,163 @@ var RvwDatabase = class {
19165
19373
  if (!row) throw new RvwError("DATABASE_ERROR", "comment watch\u306Edatabase ID\u304C\u3042\u308A\u307E\u305B\u3093\u3002");
19166
19374
  return stringValue(row, "value");
19167
19375
  }
19376
+ activateCommentWatchTask(taskId) {
19377
+ return this.immediateTransaction(() => {
19378
+ const existing = this.database.prepare(
19379
+ `SELECT task_id, generation, superseded_at
19380
+ FROM comment_watch_tasks WHERE task_id = ?`
19381
+ ).get(taskId);
19382
+ if (existing) {
19383
+ const generation2 = numberValue(existing, "generation");
19384
+ if (nullableString(existing, "superseded_at") !== null) {
19385
+ const active = this.database.prepare(
19386
+ `SELECT task_id, generation FROM comment_watch_tasks
19387
+ WHERE superseded_at IS NULL`
19388
+ ).get();
19389
+ throw new RvwError(
19390
+ "WATCH_TASK_SUPERSEDED",
19391
+ "\u3053\u306Ecomment watch task\u306F\u65E2\u306B\u5225\u306Etask\u3078\u5F15\u304D\u7D99\u304C\u308C\u3066\u3044\u307E\u3059\u3002",
19392
+ {
19393
+ details: {
19394
+ taskId,
19395
+ generation: generation2,
19396
+ activeTaskId: active ? stringValue(active, "task_id") : null,
19397
+ activeGeneration: active ? numberValue(active, "generation") : null
19398
+ },
19399
+ suggestions: ["\u73FE\u5728active\u306Awatch task\u3092resume\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
19400
+ }
19401
+ );
19402
+ }
19403
+ return {
19404
+ databaseId: this.getCommentWatchDatabaseId(),
19405
+ taskId,
19406
+ generation: generation2,
19407
+ status: "existing"
19408
+ };
19409
+ }
19410
+ const latest = this.database.prepare("SELECT COALESCE(MAX(generation), 0) AS generation FROM comment_watch_tasks").get();
19411
+ const generation = numberValue(latest, "generation") + 1;
19412
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19413
+ this.database.prepare(
19414
+ `UPDATE comment_watch_tasks SET superseded_at = ?
19415
+ WHERE superseded_at IS NULL`
19416
+ ).run(now);
19417
+ this.database.prepare(
19418
+ `INSERT INTO comment_watch_tasks(task_id, generation, activated_at, superseded_at)
19419
+ VALUES (?, ?, ?, NULL)`
19420
+ ).run(taskId, generation, now);
19421
+ return {
19422
+ databaseId: this.getCommentWatchDatabaseId(),
19423
+ taskId,
19424
+ generation,
19425
+ status: "activated"
19426
+ };
19427
+ });
19428
+ }
19429
+ assertCommentWatchTaskAuthority(taskId, generation) {
19430
+ const active = this.database.prepare(
19431
+ `SELECT task_id, generation FROM comment_watch_tasks
19432
+ WHERE superseded_at IS NULL`
19433
+ ).get();
19434
+ if (!active || stringValue(active, "task_id") !== taskId || numberValue(active, "generation") !== generation) {
19435
+ throw new RvwError(
19436
+ "WATCH_TASK_SUPERSEDED",
19437
+ "\u3053\u306Ecomment watch task\u306Factive generation\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
19438
+ {
19439
+ details: {
19440
+ taskId,
19441
+ generation,
19442
+ activeTaskId: active ? stringValue(active, "task_id") : null,
19443
+ activeGeneration: active ? numberValue(active, "generation") : null
19444
+ },
19445
+ suggestions: ["\u73FE\u5728active\u306Awatch task\u3092resume\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
19446
+ }
19447
+ );
19448
+ }
19449
+ return { databaseId: this.getCommentWatchDatabaseId(), taskId, generation };
19450
+ }
19451
+ reserveCommentWatchWriter(taskId, generation, leaseId, writeKey) {
19452
+ if (!/^[^/\s]+\/[^/\s]+$/.test(writeKey)) {
19453
+ throw new RvwError("INVALID_INPUT", "writer reservation key\u306Fowner/repository\u5F62\u5F0F\u3067\u3059\u3002");
19454
+ }
19455
+ const canonicalWriteKey = writeKey.toLowerCase();
19456
+ return this.immediateTransaction(() => {
19457
+ const authority = this.assertCommentWatchTaskAuthority(taskId, generation);
19458
+ const existingLease = this.database.prepare(
19459
+ `SELECT lease_id, write_key, task_id, generation
19460
+ FROM comment_watch_write_reservations WHERE lease_id = ?`
19461
+ ).get(leaseId);
19462
+ if (existingLease) {
19463
+ if (stringValue(existingLease, "write_key") !== canonicalWriteKey || stringValue(existingLease, "task_id") !== taskId || numberValue(existingLease, "generation") !== generation) {
19464
+ throw new RvwError(
19465
+ "WATCH_WRITE_RESERVED",
19466
+ "\u3053\u306Elease\u306F\u5225\u306Ecomment watch writer reservation\u306B\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
19467
+ { details: { taskId, generation, leaseId, writeKey: canonicalWriteKey } }
19468
+ );
19469
+ }
19470
+ return { ...authority, leaseId, writeKey: canonicalWriteKey, status: "existing" };
19471
+ }
19472
+ const existingWriter = this.database.prepare(
19473
+ `SELECT lease_id, task_id, generation
19474
+ FROM comment_watch_write_reservations WHERE write_key = ?`
19475
+ ).get(canonicalWriteKey);
19476
+ if (existingWriter) {
19477
+ throw new RvwError(
19478
+ "WATCH_WRITE_RESERVED",
19479
+ `repository ${canonicalWriteKey} \u306F\u5225\u306Ewatch worker\u304C\u4F7F\u7528\u4E2D\u3067\u3059\u3002`,
19480
+ {
19481
+ details: {
19482
+ writeKey: canonicalWriteKey,
19483
+ ownerLeaseId: stringValue(existingWriter, "lease_id"),
19484
+ ownerTaskId: stringValue(existingWriter, "task_id"),
19485
+ ownerGeneration: numberValue(existingWriter, "generation")
19486
+ }
19487
+ }
19488
+ );
19489
+ }
19490
+ this.database.prepare(
19491
+ `INSERT INTO comment_watch_write_reservations(
19492
+ lease_id, write_key, task_id, generation, acquired_at
19493
+ ) VALUES (?, ?, ?, ?, ?)`
19494
+ ).run(leaseId, canonicalWriteKey, taskId, generation, (/* @__PURE__ */ new Date()).toISOString());
19495
+ return { ...authority, leaseId, writeKey: canonicalWriteKey, status: "reserved" };
19496
+ });
19497
+ }
19498
+ releaseCommentWatchWriter(taskId, generation, leaseId) {
19499
+ return this.immediateTransaction(() => {
19500
+ const existing = this.database.prepare(
19501
+ `SELECT lease_id, write_key, task_id, generation
19502
+ FROM comment_watch_write_reservations WHERE lease_id = ?`
19503
+ ).get(leaseId);
19504
+ if (!existing) {
19505
+ return {
19506
+ databaseId: this.getCommentWatchDatabaseId(),
19507
+ taskId,
19508
+ generation,
19509
+ leaseId,
19510
+ writeKey: null,
19511
+ status: "absent"
19512
+ };
19513
+ }
19514
+ if (stringValue(existing, "task_id") !== taskId || numberValue(existing, "generation") !== generation) {
19515
+ throw new RvwError(
19516
+ "WATCH_WRITE_RESERVED",
19517
+ "\u5225\u306Ecomment watch task\u304C\u6240\u6709\u3059\u308Bwriter reservation\u306F\u89E3\u653E\u3067\u304D\u307E\u305B\u3093\u3002",
19518
+ { details: { taskId, generation, leaseId } }
19519
+ );
19520
+ }
19521
+ const writeKey = stringValue(existing, "write_key");
19522
+ this.database.prepare("DELETE FROM comment_watch_write_reservations WHERE lease_id = ?").run(leaseId);
19523
+ return {
19524
+ databaseId: this.getCommentWatchDatabaseId(),
19525
+ taskId,
19526
+ generation,
19527
+ leaseId,
19528
+ writeKey,
19529
+ status: "released"
19530
+ };
19531
+ });
19532
+ }
19168
19533
  getLatestCommentPostEventSequence() {
19169
19534
  const row = this.database.prepare("SELECT COALESCE(MAX(sequence), 0) AS sequence FROM comment_post_events").get();
19170
19535
  return numberValue(row, "sequence");
@@ -19551,13 +19916,15 @@ var RvwDatabase = class {
19551
19916
  const graphJson = JSON.stringify({
19552
19917
  originNodeId: input.originNodeId,
19553
19918
  nodes: input.nodes,
19554
- edges: input.edges
19919
+ edges: input.edges,
19920
+ presentation: input.presentation
19555
19921
  });
19556
19922
  this.immediateTransaction(() => {
19557
19923
  const keyHash = hashIdempotencyKey(input.idempotencyKey);
19558
19924
  const existingRow = this.database.prepare("SELECT * FROM structure_publish_idempotency WHERE key_hash = ?").get(keyHash);
19559
19925
  if (existingRow) {
19560
- if (stringValue(existingRow, "request_hash") !== input.idempotencyRequestHash) {
19926
+ const existingRequestHash = stringValue(existingRow, "request_hash");
19927
+ if (existingRequestHash !== input.idempotencyRequestHash && (input.presentation !== null || existingRequestHash !== input.legacyNullPresentationIdempotencyRequestHash)) {
19561
19928
  throw new RvwError(
19562
19929
  "IDEMPOTENCY_CONFLICT",
19563
19930
  "\u540C\u3058idempotencyKey\u304C\u5225\u306EStructure publish\u306B\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059\u3002"
@@ -19590,6 +19957,9 @@ var RvwDatabase = class {
19590
19957
  now,
19591
19958
  now
19592
19959
  );
19960
+ if (!this.getStructure(id)) {
19961
+ throw new RvwError("DATABASE_ERROR", "\u4FDD\u5B58\u3057\u305FStructure\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
19962
+ }
19593
19963
  this.database.prepare(
19594
19964
  `INSERT INTO structure_publish_idempotency(
19595
19965
  key_hash, request_hash, structure_id, created_at
@@ -19614,7 +19984,8 @@ var RvwDatabase = class {
19614
19984
  const graphJson = JSON.stringify({
19615
19985
  originNodeId: input.originNodeId,
19616
19986
  nodes: input.nodes,
19617
- edges: input.edges
19987
+ edges: input.edges,
19988
+ presentation: input.presentation
19618
19989
  });
19619
19990
  this.immediateTransaction(() => {
19620
19991
  const current = this.getStructure(id);
@@ -19637,6 +20008,9 @@ var RvwDatabase = class {
19637
20008
  const retiredEdgeIds = new Set(
19638
20009
  this.database.prepare("SELECT edge_id FROM structure_retired_edge_ids WHERE structure_id = ?").all(id).map((row) => stringValue(row, "edge_id"))
19639
20010
  );
20011
+ const retiredRegionIds = new Set(
20012
+ this.database.prepare("SELECT region_id FROM structure_retired_region_ids WHERE structure_id = ?").all(id).map((row) => stringValue(row, "region_id"))
20013
+ );
19640
20014
  const reusedNode = input.nodes.find((node2) => retiredNodeIds.has(node2.id));
19641
20015
  if (reusedNode) {
19642
20016
  throw new RvwError(
@@ -19651,10 +20025,21 @@ var RvwDatabase = class {
19651
20025
  `\u524A\u9664\u6E08\u307F\u306EStructure Edge ID\u306F\u518D\u5229\u7528\u3067\u304D\u307E\u305B\u3093: ${reusedEdge.id}`
19652
20026
  );
19653
20027
  }
20028
+ const reusedRegion = input.presentation?.regions.find(
20029
+ (region) => retiredRegionIds.has(region.id)
20030
+ );
20031
+ if (reusedRegion) {
20032
+ throw new RvwError(
20033
+ "INVALID_INPUT",
20034
+ `\u524A\u9664\u6E08\u307F\u306EStructure Region ID\u306F\u518D\u5229\u7528\u3067\u304D\u307E\u305B\u3093: ${reusedRegion.id}`
20035
+ );
20036
+ }
19654
20037
  const nextNodeIds = new Set(input.nodes.map((node2) => node2.id));
19655
20038
  const nextEdgeIds = new Set(input.edges.map((edge) => edge.id));
20039
+ const nextRegionIds = new Set(input.presentation?.regions.map((region) => region.id) ?? []);
19656
20040
  const retiredAtThisUpdate = current.nodes.map((node2) => node2.id).filter((nodeId) => !nextNodeIds.has(nodeId));
19657
20041
  const retiredEdgesAtThisUpdate = current.edges.map((edge) => edge.id).filter((edgeId) => !nextEdgeIds.has(edgeId));
20042
+ const retiredRegionsAtThisUpdate = (current.presentation?.regions ?? []).map((region) => region.id).filter((regionId) => !nextRegionIds.has(regionId));
19658
20043
  const result = this.database.prepare(
19659
20044
  `UPDATE structures
19660
20045
  SET source_oid = ?, title = ?, scope = ?, graph_json = ?, updated_at = ?
@@ -19670,6 +20055,9 @@ var RvwDatabase = class {
19670
20055
  }
19671
20056
  );
19672
20057
  }
20058
+ if (!this.getStructure(id)) {
20059
+ throw new RvwError("DATABASE_ERROR", "\u66F4\u65B0\u3057\u305FStructure\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
20060
+ }
19673
20061
  const retireNode = this.database.prepare(
19674
20062
  "INSERT OR IGNORE INTO structure_retired_node_ids(structure_id, node_id, retired_at) VALUES (?, ?, ?)"
19675
20063
  );
@@ -19678,6 +20066,10 @@ var RvwDatabase = class {
19678
20066
  "INSERT OR IGNORE INTO structure_retired_edge_ids(structure_id, edge_id, retired_at) VALUES (?, ?, ?)"
19679
20067
  );
19680
20068
  for (const edgeId of retiredEdgesAtThisUpdate) retireEdge.run(id, edgeId, now);
20069
+ const retireRegion = this.database.prepare(
20070
+ "INSERT OR IGNORE INTO structure_retired_region_ids(structure_id, region_id, retired_at) VALUES (?, ?, ?)"
20071
+ );
20072
+ for (const regionId of retiredRegionsAtThisUpdate) retireRegion.run(id, regionId, now);
19681
20073
  this.incrementDomainRevisions(["structures"]);
19682
20074
  });
19683
20075
  const structure = this.getStructure(id);
@@ -20095,10 +20487,20 @@ var RvwDatabase = class {
20095
20487
  insertReply(commentId, input, incrementSequence = true) {
20096
20488
  let result;
20097
20489
  const write = () => {
20490
+ if (input.watchTask) {
20491
+ this.assertCommentWatchTaskAuthority(input.watchTask.taskId, input.watchTask.generation);
20492
+ }
20098
20493
  const comment3 = this.getComment(commentId);
20099
20494
  if (!comment3) {
20100
20495
  throw new RvwError("COMMENT_NOT_FOUND", "\u30B3\u30E1\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
20101
20496
  }
20497
+ if (input.watchTask && comment3.resolvedAt !== null) {
20498
+ throw new RvwError(
20499
+ "COMMENT_NOT_ACTIONABLE",
20500
+ "\u89E3\u6C7A\u6E08\u307Fcomment\u306B\u306Fwatch acknowledgement\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3002",
20501
+ { details: { commentRef: comment3.ref } }
20502
+ );
20503
+ }
20102
20504
  const pullRequest = this.getPullRequest(comment3.pullRequestId);
20103
20505
  if (!pullRequest) throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
20104
20506
  const relatedCommitOid = input.relatedCommitOid ?? null;
@@ -20181,9 +20583,23 @@ var RvwDatabase = class {
20181
20583
  if (!result) throw new RvwError("DATABASE_ERROR", "\u8FD4\u4FE1\u7D50\u679C\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
20182
20584
  return result;
20183
20585
  }
20184
- updateCommentPost(commentId, postId, body3, relatedCommitOid, references, lastModifiedBy) {
20586
+ updateCommentPost(commentId, postId, body3, relatedCommitOid, references, lastModifiedBy, watchTask) {
20185
20587
  const now = (/* @__PURE__ */ new Date()).toISOString();
20186
20588
  this.immediateTransaction(() => {
20589
+ if (watchTask) {
20590
+ this.assertCommentWatchTaskAuthority(watchTask.taskId, watchTask.generation);
20591
+ const comment3 = this.getComment(commentId);
20592
+ if (!comment3) {
20593
+ throw new RvwError("COMMENT_NOT_FOUND", "\u30B3\u30E1\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
20594
+ }
20595
+ if (comment3.resolvedAt !== null) {
20596
+ throw new RvwError(
20597
+ "COMMENT_NOT_ACTIONABLE",
20598
+ "\u89E3\u6C7A\u6E08\u307Fcomment\u306Ewatch acknowledgement\u306F\u5FA9\u5143\u3067\u304D\u307E\u305B\u3093\u3002",
20599
+ { details: { commentRef: comment3.ref } }
20600
+ );
20601
+ }
20602
+ }
20187
20603
  const result = this.database.prepare(
20188
20604
  `UPDATE comment_posts
20189
20605
  SET body = ?, related_commit_oid = ?, last_modified_by = ?, updated_at = ?
@@ -20294,56 +20710,6 @@ import { randomUUID as randomUUID2 } from "node:crypto";
20294
20710
  import path5 from "node:path";
20295
20711
  import { TextDecoder as TextDecoder2 } from "node:util";
20296
20712
 
20297
- // src/shared/constants.ts
20298
- var APP_VERSION = "0.4.3";
20299
- var PROTOCOL_VERSION = 4;
20300
- var VIEWER_ID_HEADER = "x-rvw-viewer-id";
20301
- var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
20302
- var VIEWER_OPEN_LEASE_QUERY = "viewerLease";
20303
- var GIT_OBJECT_ID_PATTERN = /^[0-9a-f]{40,64}$/i;
20304
- var CONTENT_FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
20305
- var MAX_TEXT_DOCUMENT_BYTES = 1024 * 1024;
20306
- var MAX_MARKDOWN_ASSET_BYTES = 5 * 1024 * 1024;
20307
- var MAX_GITHUB_ATTACHMENT_BYTES = 10 * 1024 * 1024;
20308
- var MAX_GITHUB_ATTACHMENT_STDERR_BYTES = 64 * 1024;
20309
- var GITHUB_ATTACHMENT_TIMEOUT_MS = 3e4;
20310
- var MAX_COMMENT_BODY_BYTES = 64 * 1024;
20311
- var MAX_AUTHOR_LABEL_CHARACTERS = 100;
20312
- var MAX_IDEMPOTENCY_KEY_CHARACTERS = 200;
20313
- var DEFAULT_PULL_REQUEST_LIST_LIMIT = 50;
20314
- var MAX_PULL_REQUEST_LIST_LIMIT = 100;
20315
- var DEFAULT_COMMENT_LIST_LIMIT = 50;
20316
- var MAX_COMMENT_LIST_LIMIT = 100;
20317
- var DEFAULT_COMMENT_WATCH_LIMIT = 100;
20318
- var MAX_COMMENT_WATCH_LIMIT = 100;
20319
- var DEFAULT_COMMENT_WATCH_INTERVAL_SECONDS = 10;
20320
- var MAX_COMMENT_WATCH_INTERVAL_SECONDS = 300;
20321
- var MAX_COMMENT_LIST_BODY_PREVIEW_BYTES = 512;
20322
- var MAX_CODE_REFERENCES = 200;
20323
- var MAX_CODE_REFERENCE_LABEL_CHARACTERS = 200;
20324
- var MAX_CODE_REFERENCE_PATH_CHARACTERS = 4096;
20325
- var MAX_CODE_REFERENCE_DESCRIPTION_CHARACTERS = 1e3;
20326
- var MAX_WALKTHROUGH_BODY_BYTES = 256 * 1024;
20327
- var MAX_WALKTHROUGH_TITLE_CHARACTERS = 200;
20328
- var MAX_STRUCTURE_PAYLOAD_BYTES = 2 * 1024 * 1024;
20329
- var MAX_STRUCTURE_TITLE_CHARACTERS = 200;
20330
- var MAX_STRUCTURE_SCOPE_CHARACTERS = 4e3;
20331
- var MAX_STRUCTURE_NODES = 50;
20332
- var MAX_STRUCTURE_EDGES = 200;
20333
- var MAX_STRUCTURE_EDGE_ANCHORS = 20;
20334
- var MAX_STRUCTURE_SOURCE_ANCHORS = 400;
20335
- var MAX_STRUCTURE_ID_CHARACTERS = 64;
20336
- var STRUCTURE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
20337
- var MAX_STRUCTURE_LABEL_CHARACTERS = 200;
20338
- var MAX_STRUCTURE_DESCRIPTION_CHARACTERS = 2e3;
20339
- var MAX_STRUCTURE_KIND_CHARACTERS = 100;
20340
- var MAX_SEARCH_QUERY_BYTES = 1024;
20341
- var MAX_SEARCH_RESULTS = 500;
20342
- var MAX_SEARCH_STDOUT_BYTES = 8 * 1024 * 1024;
20343
- var DEFAULT_PROCESS_TIMEOUT_MS = 3e4;
20344
- var DEFAULT_PROCESS_STDOUT_BYTES = 16 * 1024 * 1024;
20345
- var DEFAULT_PROCESS_STDERR_BYTES = 2 * 1024 * 1024;
20346
-
20347
20713
  // src/domain/search.ts
20348
20714
  var wordCharacter = /[\p{L}\p{N}_]/u;
20349
20715
  function escapeRegularExpression(value) {
@@ -42710,6 +43076,21 @@ var RvwService = class {
42710
43076
  events
42711
43077
  };
42712
43078
  }
43079
+ activateCommentWatchTask(taskId) {
43080
+ return this.database.activateCommentWatchTask(taskId);
43081
+ }
43082
+ verifyCommentWatchTask(taskId, generation) {
43083
+ return {
43084
+ ...this.database.assertCommentWatchTaskAuthority(taskId, generation),
43085
+ status: "active"
43086
+ };
43087
+ }
43088
+ reserveCommentWatchWriter(taskId, generation, leaseId, writeKey) {
43089
+ return this.database.reserveCommentWatchWriter(taskId, generation, leaseId, writeKey);
43090
+ }
43091
+ releaseCommentWatchWriter(taskId, generation, leaseId) {
43092
+ return this.database.releaseCommentWatchWriter(taskId, generation, leaseId);
43093
+ }
42713
43094
  listComments(pullRequestId, resolved) {
42714
43095
  this.getPullRequest(pullRequestId);
42715
43096
  return this.database.listComments(pullRequestId, resolved);
@@ -42852,6 +43233,16 @@ var RvwService = class {
42852
43233
  }
42853
43234
  return normalized;
42854
43235
  }
43236
+ assertStructurePresentationText(value, maximum, subject) {
43237
+ if (typeof value !== "string") {
43238
+ throw new RvwError("INVALID_INPUT", `${subject}\u306F\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
43239
+ }
43240
+ const normalized = value.trim();
43241
+ if (normalized.length < 1 || normalized.length > maximum) {
43242
+ throw new RvwError("INVALID_INPUT", `${subject}\u306Ftrim\u5F8C1\u301C${maximum}\u6587\u5B57\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
43243
+ }
43244
+ return normalized;
43245
+ }
42855
43246
  assertStructureId(value, subject) {
42856
43247
  if (!STRUCTURE_ID_PATTERN.test(value)) {
42857
43248
  throw new RvwError(
@@ -43026,7 +43417,172 @@ var RvwService = class {
43026
43417
  `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
43027
43418
  );
43028
43419
  }
43029
- const graph = { originNodeId, nodes, edges };
43420
+ if (input.presentation === void 0) {
43421
+ throw new RvwError(
43422
+ "INVALID_INPUT",
43423
+ "Structure presentation\u306Fnull\u307E\u305F\u306Fobject\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
43424
+ );
43425
+ }
43426
+ let presentation = null;
43427
+ if (input.presentation !== null) {
43428
+ if (typeof input.presentation !== "object" || Array.isArray(input.presentation) || Object.keys(input.presentation).some(
43429
+ (key2) => key2 !== "thesis" && key2 !== "startNodeId" && key2 !== "primaryBackbone" && key2 !== "regions"
43430
+ )) {
43431
+ throw new RvwError("INVALID_INPUT", "Structure presentation\u304C\u4E0D\u6B63\u3067\u3059\u3002");
43432
+ }
43433
+ const thesis = this.assertStructurePresentationText(
43434
+ input.presentation.thesis,
43435
+ MAX_STRUCTURE_PRESENTATION_THESIS_CHARACTERS,
43436
+ "Structure presentation thesis"
43437
+ );
43438
+ const startNodeId = this.assertStructureId(
43439
+ input.presentation.startNodeId,
43440
+ "Structure presentation startNodeId"
43441
+ );
43442
+ if (!nodeIds.has(startNodeId)) {
43443
+ throw new RvwError(
43444
+ "INVALID_INPUT",
43445
+ `Structure presentation startNodeId\u306ENode\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${startNodeId}`
43446
+ );
43447
+ }
43448
+ let primaryBackbone = null;
43449
+ if (input.presentation.primaryBackbone !== null) {
43450
+ const rawPrimaryBackbone = input.presentation.primaryBackbone;
43451
+ if (typeof rawPrimaryBackbone !== "object" || Array.isArray(rawPrimaryBackbone) || Object.keys(rawPrimaryBackbone).some((key2) => key2 !== "edgeIds") || !Array.isArray(rawPrimaryBackbone.edgeIds) || rawPrimaryBackbone.edgeIds.length < 1 || rawPrimaryBackbone.edgeIds.length > MAX_STRUCTURE_PRIMARY_BACKBONE_EDGES) {
43452
+ throw new RvwError(
43453
+ "INVALID_INPUT",
43454
+ `Structure presentation primaryBackbone.edgeIds\u306F1\u301C${MAX_STRUCTURE_PRIMARY_BACKBONE_EDGES}\u4EF6\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
43455
+ );
43456
+ }
43457
+ const edgeById = new Map(edges.map((edge) => [edge.id, edge]));
43458
+ const selectedEdgeIds = /* @__PURE__ */ new Set();
43459
+ const selectedEdges = rawPrimaryBackbone.edgeIds.map((rawEdgeId) => {
43460
+ const edgeId = this.assertStructureId(
43461
+ rawEdgeId,
43462
+ "Structure presentation primaryBackbone Edge ID"
43463
+ );
43464
+ if (selectedEdgeIds.has(edgeId)) {
43465
+ throw new RvwError(
43466
+ "INVALID_INPUT",
43467
+ `Structure presentation primaryBackbone\u306EEdge ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${edgeId}`
43468
+ );
43469
+ }
43470
+ selectedEdgeIds.add(edgeId);
43471
+ const edge = edgeById.get(edgeId);
43472
+ if (edge === void 0) {
43473
+ throw new RvwError(
43474
+ "INVALID_INPUT",
43475
+ `Structure presentation primaryBackbone\u306EEdge\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${edgeId}`
43476
+ );
43477
+ }
43478
+ return edge;
43479
+ });
43480
+ const primaryBackboneNodeIds = structureBackboneNodeIds(selectedEdges);
43481
+ if (primaryBackboneNodeIds.size < 2 || primaryBackboneNodeIds.size > MAX_STRUCTURE_PRIMARY_BACKBONE_NODES) {
43482
+ throw new RvwError(
43483
+ "INVALID_INPUT",
43484
+ `Structure presentation primaryBackbone\u306F2\u301C${MAX_STRUCTURE_PRIMARY_BACKBONE_NODES}\u4EF6\u306ENode\u3092\u7D50\u3076\u3088\u3046\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
43485
+ );
43486
+ }
43487
+ if (!primaryBackboneNodeIds.has(startNodeId)) {
43488
+ throw new RvwError(
43489
+ "INVALID_INPUT",
43490
+ "Structure presentation startNodeId\u306FprimaryBackbone\u306EEdge endpoint\u306B\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
43491
+ );
43492
+ }
43493
+ if (!isStructureBackboneWeaklyConnected(selectedEdges, startNodeId)) {
43494
+ throw new RvwError(
43495
+ "INVALID_INPUT",
43496
+ "Structure presentation primaryBackbone\u306FstartNodeId\u304B\u3089\u8FBF\u308C\u308B\u4E00\u3064\u306Econnected graph\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
43497
+ );
43498
+ }
43499
+ primaryBackbone = {
43500
+ edgeIds: canonicalStructureBackboneEdgeIds([...selectedEdgeIds])
43501
+ };
43502
+ }
43503
+ if (!Array.isArray(input.presentation.regions) || input.presentation.regions.length > MAX_STRUCTURE_PRESENTATION_REGIONS) {
43504
+ throw new RvwError(
43505
+ "INVALID_INPUT",
43506
+ `Structure presentation regions\u306F${MAX_STRUCTURE_PRESENTATION_REGIONS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
43507
+ );
43508
+ }
43509
+ const regionIds = /* @__PURE__ */ new Set();
43510
+ const assignedRegionByNodeId = /* @__PURE__ */ new Map();
43511
+ const regions = input.presentation.regions.map((region, regionIndex) => {
43512
+ if (typeof region !== "object" || region === null || Array.isArray(region) || Object.keys(region).some(
43513
+ (key2) => key2 !== "id" && key2 !== "label" && key2 !== "summary" && key2 !== "nodeIds"
43514
+ )) {
43515
+ throw new RvwError(
43516
+ "INVALID_INPUT",
43517
+ `Structure presentation region ${regionIndex + 1}\u304C\u4E0D\u6B63\u3067\u3059\u3002`
43518
+ );
43519
+ }
43520
+ const id = this.assertStructureId(
43521
+ region.id,
43522
+ `Structure presentation region ${regionIndex + 1} ID`
43523
+ );
43524
+ if (regionIds.has(id)) {
43525
+ throw new RvwError(
43526
+ "INVALID_INPUT",
43527
+ `Structure presentation region ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id}`
43528
+ );
43529
+ }
43530
+ regionIds.add(id);
43531
+ const label = this.assertStructurePresentationText(
43532
+ region.label,
43533
+ MAX_STRUCTURE_PRESENTATION_REGION_LABEL_CHARACTERS,
43534
+ `Structure presentation region ${regionIndex + 1} label`
43535
+ );
43536
+ const summary = this.assertStructurePresentationText(
43537
+ region.summary,
43538
+ MAX_STRUCTURE_PRESENTATION_REGION_SUMMARY_CHARACTERS,
43539
+ `Structure presentation region ${label} summary`
43540
+ );
43541
+ if (!Array.isArray(region.nodeIds) || region.nodeIds.length < 1 || region.nodeIds.length > MAX_STRUCTURE_NODES) {
43542
+ throw new RvwError(
43543
+ "INVALID_INPUT",
43544
+ `Structure presentation region ${label}\u306EnodeIds\u306F1\u301C${MAX_STRUCTURE_NODES}\u4EF6\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
43545
+ );
43546
+ }
43547
+ const currentRegionNodeIds = /* @__PURE__ */ new Set();
43548
+ const normalizedNodeIds = region.nodeIds.map((rawNodeId) => {
43549
+ const nodeId = this.assertStructureId(
43550
+ rawNodeId,
43551
+ `Structure presentation region ${label} Node ID`
43552
+ );
43553
+ if (currentRegionNodeIds.has(nodeId)) {
43554
+ throw new RvwError(
43555
+ "INVALID_INPUT",
43556
+ `Structure presentation region ${label}\u306ENode ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${nodeId}`
43557
+ );
43558
+ }
43559
+ if (!nodeIds.has(nodeId)) {
43560
+ throw new RvwError(
43561
+ "INVALID_INPUT",
43562
+ `Structure presentation region ${label}\u306ENode\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${nodeId}`
43563
+ );
43564
+ }
43565
+ const assignedRegion = assignedRegionByNodeId.get(nodeId);
43566
+ if (assignedRegion !== void 0) {
43567
+ throw new RvwError(
43568
+ "INVALID_INPUT",
43569
+ `Structure presentation Node ${nodeId}\u306F\u8907\u6570region\u306B\u6240\u5C5E\u3067\u304D\u307E\u305B\u3093: ${assignedRegion.label} (${assignedRegion.id}), ${label} (${id})`
43570
+ );
43571
+ }
43572
+ currentRegionNodeIds.add(nodeId);
43573
+ assignedRegionByNodeId.set(nodeId, { id, label });
43574
+ return nodeId;
43575
+ });
43576
+ return { id, label, summary, nodeIds: normalizedNodeIds };
43577
+ });
43578
+ presentation = {
43579
+ thesis,
43580
+ startNodeId,
43581
+ primaryBackbone,
43582
+ regions: canonicalStructurePresentationRegions(regions)
43583
+ };
43584
+ }
43585
+ const graph = { originNodeId, nodes, edges, presentation };
43030
43586
  if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
43031
43587
  throw new RvwError(
43032
43588
  "INVALID_INPUT",
@@ -43042,6 +43598,18 @@ var RvwService = class {
43042
43598
  assertIdempotencyKey(input.idempotencyKey);
43043
43599
  const pullRequest = this.resolveStoredPullRequest(input.pullRequest);
43044
43600
  const content3 = await this.validateStructureContent(pullRequest, input);
43601
+ const legacyNullPresentationIdempotencyRequestHash = content3.presentation === null ? idempotencyRequestHash({
43602
+ operation: "structure.publish",
43603
+ pullRequestId: pullRequest.id,
43604
+ content: {
43605
+ sourceOid: content3.sourceOid,
43606
+ title: content3.title,
43607
+ scope: content3.scope,
43608
+ originNodeId: content3.originNodeId,
43609
+ nodes: content3.nodes,
43610
+ edges: content3.edges
43611
+ }
43612
+ }) : void 0;
43045
43613
  return await this.writeWithRetainedCommit(
43046
43614
  pullRequest,
43047
43615
  content3.sourceOid,
@@ -43054,7 +43622,8 @@ var RvwService = class {
43054
43622
  operation: "structure.publish",
43055
43623
  pullRequestId: pullRequest.id,
43056
43624
  content: content3
43057
- })
43625
+ }),
43626
+ ...legacyNullPresentationIdempotencyRequestHash === void 0 ? {} : { legacyNullPresentationIdempotencyRequestHash }
43058
43627
  })
43059
43628
  );
43060
43629
  }
@@ -43475,7 +44044,8 @@ var RvwService = class {
43475
44044
  body3,
43476
44045
  relatedCommitOid,
43477
44046
  references,
43478
- input.lastModifiedBy ?? post.lastModifiedBy
44047
+ input.lastModifiedBy ?? post.lastModifiedBy,
44048
+ input.watchTask
43479
44049
  );
43480
44050
  return relatedCommitOid ? await this.writeWithRetainedCommit(pullRequest, relatedCommitOid, "comment", write) : write();
43481
44051
  }
@@ -43805,6 +44375,696 @@ function createRuntime(options = {}) {
43805
44375
  return { database, git, github, service, close: () => database.close() };
43806
44376
  }
43807
44377
 
44378
+ // src/domain/structure-projection.ts
44379
+ var STRUCTURE_NODE_WIDTH = 228;
44380
+ var STRUCTURE_NODE_HEIGHT = 112;
44381
+ function stableCompare(left, right) {
44382
+ return left === right ? 0 : left < right ? -1 : 1;
44383
+ }
44384
+ function compareLinks([leftA, leftB], [rightA, rightB]) {
44385
+ const first = stableCompare(leftA, rightA);
44386
+ return first === 0 ? stableCompare(leftB, rightB) : first;
44387
+ }
44388
+ function simpleStructureTopology(structure) {
44389
+ const neighborSets = new Map(structure.nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
44390
+ const pairNodes = /* @__PURE__ */ new Map();
44391
+ const pairDirections = /* @__PURE__ */ new Map();
44392
+ for (const edge of structure.edges) {
44393
+ if (edge.from === edge.to || !neighborSets.has(edge.from) || !neighborSets.has(edge.to)) {
44394
+ continue;
44395
+ }
44396
+ neighborSets.get(edge.from)?.add(edge.to);
44397
+ neighborSets.get(edge.to)?.add(edge.from);
44398
+ const ordered = [edge.from, edge.to].sort(stableCompare);
44399
+ const pairKey = JSON.stringify(ordered);
44400
+ pairNodes.set(pairKey, ordered);
44401
+ const directions = pairDirections.get(pairKey) ?? {
44402
+ hasUndirected: false,
44403
+ directions: /* @__PURE__ */ new Map()
44404
+ };
44405
+ if (edge.directed) {
44406
+ directions.directions.set(JSON.stringify([edge.from, edge.to]), [edge.from, edge.to]);
44407
+ } else {
44408
+ directions.hasUndirected = true;
44409
+ }
44410
+ pairDirections.set(pairKey, directions);
44411
+ }
44412
+ return {
44413
+ neighbors: new Map(
44414
+ [...neighborSets].map(([nodeId, neighbors]) => [nodeId, [...neighbors].sort(stableCompare)])
44415
+ ),
44416
+ links: [...pairNodes.values()].sort(compareLinks),
44417
+ directionalLinks: [...pairDirections.values()].flatMap(
44418
+ ({ hasUndirected, directions }) => !hasUndirected && directions.size === 1 ? [...directions.values()] : []
44419
+ ).sort(compareLinks)
44420
+ };
44421
+ }
44422
+ function topologyComponents(topology) {
44423
+ const components = [];
44424
+ const assigned = /* @__PURE__ */ new Set();
44425
+ for (const first of [...topology.neighbors.keys()].sort(stableCompare)) {
44426
+ if (assigned.has(first)) continue;
44427
+ const component = [];
44428
+ const queue = [first];
44429
+ assigned.add(first);
44430
+ for (let index2 = 0; index2 < queue.length; index2 += 1) {
44431
+ const current = queue[index2];
44432
+ component.push(current);
44433
+ for (const neighbor of topology.neighbors.get(current) ?? []) {
44434
+ if (assigned.has(neighbor)) continue;
44435
+ assigned.add(neighbor);
44436
+ queue.push(neighbor);
44437
+ }
44438
+ }
44439
+ component.sort(stableCompare);
44440
+ components.push(component);
44441
+ }
44442
+ return components.sort(
44443
+ (left, right) => right.length - left.length || stableCompare(left[0], right[0])
44444
+ );
44445
+ }
44446
+ function topologyRoot(nodeIds, topology) {
44447
+ const neighborDegree = (nodeId) => (topology.neighbors.get(nodeId) ?? []).reduce(
44448
+ (total, neighbor) => total + (topology.neighbors.get(neighbor)?.length ?? 0),
44449
+ 0
44450
+ );
44451
+ return [...nodeIds].sort(
44452
+ (left, right) => (topology.neighbors.get(right)?.length ?? 0) - (topology.neighbors.get(left)?.length ?? 0) || neighborDegree(right) - neighborDegree(left) || stableCompare(left, right)
44453
+ )[0];
44454
+ }
44455
+ function countNonForwardLinks(ranks, directionalLinks) {
44456
+ return directionalLinks.reduce((count, [from, to]) => {
44457
+ const fromRank = ranks.get(from);
44458
+ const toRank = ranks.get(to);
44459
+ return count + (fromRank !== void 0 && toRank !== void 0 && fromRank >= toRank ? 1 : 0);
44460
+ }, 0);
44461
+ }
44462
+ function normalizedRankIndexes(ranks) {
44463
+ return new Map(
44464
+ [...new Set(ranks.values())].sort((left, right) => left - right).map((rank, index2) => [rank, index2])
44465
+ );
44466
+ }
44467
+ function totalDirectionalSpan(ranks, directionalLinks) {
44468
+ const indexes = normalizedRankIndexes(ranks);
44469
+ return directionalLinks.reduce((total, [from, to]) => {
44470
+ const fromRank = ranks.get(from);
44471
+ const toRank = ranks.get(to);
44472
+ if (fromRank === void 0 || toRank === void 0) return total;
44473
+ return total + Math.abs(indexes.get(fromRank) - indexes.get(toRank));
44474
+ }, 0);
44475
+ }
44476
+ function compareNumberArrays(left, right) {
44477
+ for (let index2 = 0; index2 < Math.max(left.length, right.length); index2 += 1) {
44478
+ const difference = (left[index2] ?? 0) - (right[index2] ?? 0);
44479
+ if (difference !== 0) return difference;
44480
+ }
44481
+ return 0;
44482
+ }
44483
+ function canonicalTopologyStructuralColors(nodeIds, topology, originNodeId) {
44484
+ const nodeSet = new Set(nodeIds);
44485
+ const incomingNeighbors = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
44486
+ const outgoingNeighbors = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
44487
+ for (const [from, to] of topology.directionalLinks) {
44488
+ if (!nodeSet.has(from) || !nodeSet.has(to)) continue;
44489
+ outgoingNeighbors.get(from)?.add(to);
44490
+ incomingNeighbors.get(to)?.add(from);
44491
+ }
44492
+ const ambiguousNeighbors = new Map(
44493
+ nodeIds.map((nodeId) => {
44494
+ const directionalNeighbors = /* @__PURE__ */ new Set([
44495
+ ...incomingNeighbors.get(nodeId),
44496
+ ...outgoingNeighbors.get(nodeId)
44497
+ ]);
44498
+ return [
44499
+ nodeId,
44500
+ new Set(
44501
+ (topology.neighbors.get(nodeId) ?? []).filter(
44502
+ (neighbor) => nodeSet.has(neighbor) && !directionalNeighbors.has(neighbor)
44503
+ )
44504
+ )
44505
+ ];
44506
+ })
44507
+ );
44508
+ const assignColors = (descriptors) => {
44509
+ const colorByDescriptor = new Map(
44510
+ [...new Set(descriptors.values())].sort(stableCompare).map((descriptor, index2) => [descriptor, index2])
44511
+ );
44512
+ return new Map(
44513
+ nodeIds.map((nodeId) => [nodeId, colorByDescriptor.get(descriptors.get(nodeId))])
44514
+ );
44515
+ };
44516
+ let colors = assignColors(
44517
+ new Map(
44518
+ nodeIds.map((nodeId) => [
44519
+ nodeId,
44520
+ JSON.stringify([
44521
+ nodeId === originNodeId ? 1 : 0,
44522
+ incomingNeighbors.get(nodeId).size,
44523
+ outgoingNeighbors.get(nodeId).size,
44524
+ ambiguousNeighbors.get(nodeId).size
44525
+ ])
44526
+ ])
44527
+ )
44528
+ );
44529
+ for (let pass = 0; pass < nodeIds.length; pass += 1) {
44530
+ const next2 = assignColors(
44531
+ new Map(
44532
+ nodeIds.map((nodeId) => [
44533
+ nodeId,
44534
+ JSON.stringify([
44535
+ colors.get(nodeId),
44536
+ [...incomingNeighbors.get(nodeId)].map((neighbor) => colors.get(neighbor)).sort((left, right) => left - right),
44537
+ [...outgoingNeighbors.get(nodeId)].map((neighbor) => colors.get(neighbor)).sort((left, right) => left - right),
44538
+ [...ambiguousNeighbors.get(nodeId)].map((neighbor) => colors.get(neighbor)).sort((left, right) => left - right)
44539
+ ])
44540
+ ])
44541
+ )
44542
+ );
44543
+ if (nodeIds.every((nodeId) => next2.get(nodeId) === colors.get(nodeId))) break;
44544
+ colors = next2;
44545
+ }
44546
+ return colors;
44547
+ }
44548
+ function chooseRankProposal(nodeId, proposals, ranks, directionalLinks) {
44549
+ const scored = [...proposals].map((rank) => {
44550
+ const candidate = new Map(ranks).set(nodeId, rank);
44551
+ return {
44552
+ rank,
44553
+ nonForward: countNonForwardLinks(candidate, directionalLinks),
44554
+ occupiedColumns: new Set(candidate.values()).size,
44555
+ span: totalDirectionalSpan(candidate, directionalLinks)
44556
+ };
44557
+ });
44558
+ scored.sort(
44559
+ (left, right) => left.nonForward - right.nonForward || left.occupiedColumns - right.occupiedColumns || left.span - right.span || Math.abs(left.rank) - Math.abs(right.rank) || left.rank - right.rank
44560
+ );
44561
+ return scored[0].rank;
44562
+ }
44563
+ function applyForwardWaves(nodeSet, topology, ranks) {
44564
+ for (let pass = 0; pass < nodeSet.size; pass += 1) {
44565
+ const proposals = /* @__PURE__ */ new Map();
44566
+ for (const [from, to] of topology.directionalLinks) {
44567
+ if (!nodeSet.has(from) || !nodeSet.has(to) || ranks.has(to)) continue;
44568
+ const fromRank = ranks.get(from);
44569
+ if (fromRank === void 0) continue;
44570
+ const targetProposals = proposals.get(to) ?? /* @__PURE__ */ new Set();
44571
+ targetProposals.add(fromRank + 1);
44572
+ proposals.set(to, targetProposals);
44573
+ }
44574
+ if (proposals.size === 0) break;
44575
+ const accepted = [...proposals].map(
44576
+ ([nodeId, nodeProposals]) => [
44577
+ nodeId,
44578
+ chooseRankProposal(nodeId, nodeProposals, ranks, topology.directionalLinks)
44579
+ ]
44580
+ ).sort(([left], [right]) => stableCompare(left, right));
44581
+ for (const [nodeId, rank] of accepted) ranks.set(nodeId, rank);
44582
+ }
44583
+ }
44584
+ function stronglyConnectedComponents(nodeIds, directionalLinks) {
44585
+ const nodeSet = new Set(nodeIds);
44586
+ const outgoing = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
44587
+ for (const [from, to] of directionalLinks) {
44588
+ if (nodeSet.has(from) && nodeSet.has(to)) outgoing.get(from)?.add(to);
44589
+ }
44590
+ const indexes = /* @__PURE__ */ new Map();
44591
+ const lowLinks = /* @__PURE__ */ new Map();
44592
+ const stack = [];
44593
+ const onStack = /* @__PURE__ */ new Set();
44594
+ const components = [];
44595
+ let nextIndex = 0;
44596
+ const visit = (nodeId) => {
44597
+ const nodeIndex = nextIndex;
44598
+ nextIndex += 1;
44599
+ indexes.set(nodeId, nodeIndex);
44600
+ lowLinks.set(nodeId, nodeIndex);
44601
+ stack.push(nodeId);
44602
+ onStack.add(nodeId);
44603
+ for (const neighbor of [...outgoing.get(nodeId) ?? []].sort(stableCompare)) {
44604
+ if (!indexes.has(neighbor)) {
44605
+ visit(neighbor);
44606
+ lowLinks.set(nodeId, Math.min(lowLinks.get(nodeId), lowLinks.get(neighbor)));
44607
+ } else if (onStack.has(neighbor)) {
44608
+ lowLinks.set(nodeId, Math.min(lowLinks.get(nodeId), indexes.get(neighbor)));
44609
+ }
44610
+ }
44611
+ if (lowLinks.get(nodeId) !== indexes.get(nodeId)) return;
44612
+ const component = [];
44613
+ for (; ; ) {
44614
+ const member = stack.pop();
44615
+ onStack.delete(member);
44616
+ component.push(member);
44617
+ if (member === nodeId) break;
44618
+ }
44619
+ component.sort(stableCompare);
44620
+ components.push(component);
44621
+ };
44622
+ for (const nodeId of [...nodeIds].sort(stableCompare)) {
44623
+ if (!indexes.has(nodeId)) visit(nodeId);
44624
+ }
44625
+ return components;
44626
+ }
44627
+ function internalComponentRanks(nodeIds, directionalLinks, structuralColors, preferredAnchor, originIncomingBoundaryNodeIds, incomingBoundaryNodeIds, outgoingBoundaryNodeIds, attachmentBoundaryNodeIds) {
44628
+ if (nodeIds.length === 1) return /* @__PURE__ */ new Map([[nodeIds[0], 0]]);
44629
+ const nodeSet = new Set(nodeIds);
44630
+ const internalLinks = directionalLinks.filter(
44631
+ ([from, to]) => nodeSet.has(from) && nodeSet.has(to)
44632
+ );
44633
+ const neighborSets = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
44634
+ for (const [from, to] of internalLinks) {
44635
+ neighborSets.get(from)?.add(to);
44636
+ neighborSets.get(to)?.add(from);
44637
+ }
44638
+ const topology = {
44639
+ neighbors: new Map(
44640
+ [...neighborSets].map(([nodeId, neighbors]) => [nodeId, [...neighbors].sort(stableCompare)])
44641
+ ),
44642
+ links: internalLinks,
44643
+ directionalLinks: internalLinks
44644
+ };
44645
+ const ranksFromAnchor = (anchor) => {
44646
+ const rawRanks = /* @__PURE__ */ new Map([[anchor, 0]]);
44647
+ applyForwardWaves(nodeSet, topology, rawRanks);
44648
+ const rankIndexes = normalizedRankIndexes(rawRanks);
44649
+ return new Map(nodeIds.map((nodeId) => [nodeId, rankIndexes.get(rawRanks.get(nodeId)) ?? 0]));
44650
+ };
44651
+ if (preferredAnchor && nodeSet.has(preferredAnchor)) return ranksFromAnchor(preferredAnchor);
44652
+ const candidates = nodeIds.map((anchor) => {
44653
+ const ranks = ranksFromAnchor(anchor);
44654
+ const maxRank = Math.max(...ranks.values());
44655
+ const rankLoadProfile = Array.from(
44656
+ { length: maxRank + 1 },
44657
+ (_, rank) => nodeIds.filter((nodeId) => ranks.get(nodeId) === rank).length
44658
+ );
44659
+ const rankStructuralProfile = rankLoadProfile.flatMap((_, rank) => [
44660
+ -1,
44661
+ ...nodeIds.filter((nodeId) => ranks.get(nodeId) === rank).map((nodeId) => structuralColors.get(nodeId)).sort((left, right) => left - right)
44662
+ ]);
44663
+ return {
44664
+ anchor,
44665
+ ranks,
44666
+ nonForward: countNonForwardLinks(ranks, internalLinks),
44667
+ originIncomingBoundaryRanks: originIncomingBoundaryNodeIds.map((nodeId) => ranks.get(nodeId)).sort((left, right) => right - left),
44668
+ incomingBoundaryRanks: incomingBoundaryNodeIds.map((nodeId) => ranks.get(nodeId)).sort((left, right) => right - left),
44669
+ outgoingBoundaryDistances: outgoingBoundaryNodeIds.map((nodeId) => maxRank - ranks.get(nodeId)).sort((left, right) => right - left),
44670
+ attachmentBoundaryRanks: attachmentBoundaryNodeIds.map((nodeId) => ranks.get(nodeId)).sort((left, right) => right - left),
44671
+ width: new Set(ranks.values()).size,
44672
+ span: totalDirectionalSpan(ranks, internalLinks),
44673
+ rankLoadProfile,
44674
+ rankStructuralProfile,
44675
+ anchorStructuralColor: structuralColors.get(anchor)
44676
+ };
44677
+ });
44678
+ candidates.sort(
44679
+ (left, right) => left.nonForward - right.nonForward || compareNumberArrays(left.originIncomingBoundaryRanks, right.originIncomingBoundaryRanks) || compareNumberArrays(left.incomingBoundaryRanks, right.incomingBoundaryRanks) || compareNumberArrays(left.outgoingBoundaryDistances, right.outgoingBoundaryDistances) || compareNumberArrays(left.attachmentBoundaryRanks, right.attachmentBoundaryRanks) || left.width - right.width || left.span - right.span || compareNumberArrays(left.rankLoadProfile, right.rankLoadProfile) || compareNumberArrays(left.rankStructuralProfile, right.rankStructuralProfile) || left.anchorStructuralColor - right.anchorStructuralColor || stableCompare(left.anchor, right.anchor)
44680
+ );
44681
+ return candidates[0].ranks;
44682
+ }
44683
+ function directionalCondensationRanks(nodeIds, topology, originNodeId) {
44684
+ const nodeSet = new Set(nodeIds);
44685
+ const directionalLinks = topology.directionalLinks.filter(
44686
+ ([from, to]) => nodeSet.has(from) && nodeSet.has(to)
44687
+ );
44688
+ const structuralColors = canonicalTopologyStructuralColors(nodeIds, topology, originNodeId);
44689
+ const components = stronglyConnectedComponents(nodeIds, directionalLinks);
44690
+ const componentByNodeId = /* @__PURE__ */ new Map();
44691
+ components.forEach((component, componentIndex) => {
44692
+ for (const nodeId of component) componentByNodeId.set(nodeId, componentIndex);
44693
+ });
44694
+ const outgoing = new Map(components.map((_, index2) => [index2, /* @__PURE__ */ new Set()]));
44695
+ const incoming = new Map(components.map((_, index2) => [index2, /* @__PURE__ */ new Set()]));
44696
+ for (const [from, to] of directionalLinks) {
44697
+ const fromComponent = componentByNodeId.get(from);
44698
+ const toComponent = componentByNodeId.get(to);
44699
+ if (fromComponent === toComponent) continue;
44700
+ outgoing.get(fromComponent).add(toComponent);
44701
+ incoming.get(toComponent).add(fromComponent);
44702
+ }
44703
+ const originComponent = componentByNodeId.get(originNodeId);
44704
+ const directionalGroups = [];
44705
+ const directionalGroupByComponent = /* @__PURE__ */ new Map();
44706
+ for (let first = 0; first < components.length; first += 1) {
44707
+ if (directionalGroupByComponent.has(first)) continue;
44708
+ const groupIndex = directionalGroups.length;
44709
+ const group = [];
44710
+ const queue = [first];
44711
+ directionalGroupByComponent.set(first, groupIndex);
44712
+ for (let index2 = 0; index2 < queue.length; index2 += 1) {
44713
+ const current = queue[index2];
44714
+ group.push(current);
44715
+ for (const neighbor of [...outgoing.get(current), ...incoming.get(current)]) {
44716
+ if (directionalGroupByComponent.has(neighbor)) continue;
44717
+ directionalGroupByComponent.set(neighbor, groupIndex);
44718
+ queue.push(neighbor);
44719
+ }
44720
+ }
44721
+ directionalGroups.push(group);
44722
+ }
44723
+ const directionalGroupByNodeId = /* @__PURE__ */ new Map();
44724
+ for (const [nodeId, componentIndex] of componentByNodeId) {
44725
+ directionalGroupByNodeId.set(nodeId, directionalGroupByComponent.get(componentIndex));
44726
+ }
44727
+ const attachmentBoundaryNodeIds = new Map(
44728
+ components.map((_, componentIndex) => [componentIndex, []])
44729
+ );
44730
+ for (const [left, right] of topology.links) {
44731
+ if (directionalGroupByNodeId.get(left) === directionalGroupByNodeId.get(right)) continue;
44732
+ attachmentBoundaryNodeIds.get(componentByNodeId.get(left)).push(left);
44733
+ attachmentBoundaryNodeIds.get(componentByNodeId.get(right)).push(right);
44734
+ }
44735
+ const compareComponents = (left, right) => stableCompare(components[left][0], components[right][0]);
44736
+ const localRanks = /* @__PURE__ */ new Map();
44737
+ const componentWidths = /* @__PURE__ */ new Map();
44738
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex += 1) {
44739
+ const component = components[componentIndex];
44740
+ const ranks2 = internalComponentRanks(
44741
+ component,
44742
+ directionalLinks,
44743
+ structuralColors,
44744
+ component.includes(originNodeId) ? originNodeId : null,
44745
+ directionalLinks.flatMap(
44746
+ ([from, to]) => componentByNodeId.get(from) === originComponent && componentByNodeId.get(to) === componentIndex && componentIndex !== originComponent ? [to] : []
44747
+ ),
44748
+ directionalLinks.flatMap(
44749
+ ([from, to]) => componentByNodeId.get(to) === componentIndex && componentByNodeId.get(from) !== componentIndex ? [to] : []
44750
+ ),
44751
+ directionalLinks.flatMap(
44752
+ ([from, to]) => componentByNodeId.get(from) === componentIndex && componentByNodeId.get(to) !== componentIndex ? [from] : []
44753
+ ),
44754
+ attachmentBoundaryNodeIds.get(componentIndex)
44755
+ );
44756
+ localRanks.set(componentIndex, ranks2);
44757
+ componentWidths.set(componentIndex, Math.max(...ranks2.values()) + 1);
44758
+ }
44759
+ const remainingIncoming = new Map(
44760
+ components.map((_, componentIndex) => [componentIndex, incoming.get(componentIndex).size])
44761
+ );
44762
+ const componentStarts = /* @__PURE__ */ new Map();
44763
+ const ready = components.map((_, componentIndex) => componentIndex).filter((componentIndex) => remainingIncoming.get(componentIndex) === 0).sort(compareComponents);
44764
+ for (const componentIndex of ready) componentStarts.set(componentIndex, 0);
44765
+ while (ready.length > 0) {
44766
+ const current = ready.shift();
44767
+ for (const target of [...outgoing.get(current)].sort(compareComponents)) {
44768
+ componentStarts.set(
44769
+ target,
44770
+ Math.max(
44771
+ componentStarts.get(target) ?? 0,
44772
+ componentStarts.get(current) + componentWidths.get(current)
44773
+ )
44774
+ );
44775
+ const unresolved = remainingIncoming.get(target) - 1;
44776
+ remainingIncoming.set(target, unresolved);
44777
+ if (unresolved === 0) {
44778
+ ready.push(target);
44779
+ ready.sort(compareComponents);
44780
+ }
44781
+ }
44782
+ }
44783
+ const unshiftedRanks = /* @__PURE__ */ new Map();
44784
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex += 1) {
44785
+ for (const [nodeId, localRank] of localRanks.get(componentIndex)) {
44786
+ unshiftedRanks.set(nodeId, componentStarts.get(componentIndex) + localRank);
44787
+ }
44788
+ }
44789
+ const originGroup = directionalGroupByComponent.get(originComponent);
44790
+ const groupOffsets = /* @__PURE__ */ new Map([[originGroup, -unshiftedRanks.get(originNodeId)]]);
44791
+ while (groupOffsets.size < directionalGroups.length) {
44792
+ const proposals = /* @__PURE__ */ new Map();
44793
+ for (const [left, right] of topology.links) {
44794
+ const leftGroup = directionalGroupByNodeId.get(left);
44795
+ const rightGroup = directionalGroupByNodeId.get(right);
44796
+ if (leftGroup === rightGroup) continue;
44797
+ const leftOffset = groupOffsets.get(leftGroup);
44798
+ const rightOffset = groupOffsets.get(rightGroup);
44799
+ if (leftOffset !== void 0 && rightOffset === void 0) {
44800
+ const offsets = proposals.get(rightGroup) ?? /* @__PURE__ */ new Set();
44801
+ offsets.add(unshiftedRanks.get(left) + leftOffset + 1 - unshiftedRanks.get(right));
44802
+ proposals.set(rightGroup, offsets);
44803
+ }
44804
+ if (leftOffset === void 0 && rightOffset !== void 0) {
44805
+ const offsets = proposals.get(leftGroup) ?? /* @__PURE__ */ new Set();
44806
+ offsets.add(unshiftedRanks.get(right) + rightOffset + 1 - unshiftedRanks.get(left));
44807
+ proposals.set(leftGroup, offsets);
44808
+ }
44809
+ }
44810
+ if (proposals.size === 0) break;
44811
+ const knownGroupOffsets = new Map(groupOffsets);
44812
+ const rankedValues = [...unshiftedRanks].flatMap(([nodeId, rank]) => {
44813
+ const offset = knownGroupOffsets.get(directionalGroupByNodeId.get(nodeId));
44814
+ return offset === void 0 ? [] : [rank + offset];
44815
+ });
44816
+ const acceptedOffsets = /* @__PURE__ */ new Map();
44817
+ for (const [groupIndex, proposedOffsets] of proposals) {
44818
+ const candidates = [...proposedOffsets].map((offset) => {
44819
+ const crossGroupSpan = topology.links.reduce((total, [left, right]) => {
44820
+ const leftGroup = directionalGroupByNodeId.get(left);
44821
+ const rightGroup = directionalGroupByNodeId.get(right);
44822
+ const leftOffset = leftGroup === groupIndex ? offset : knownGroupOffsets.get(leftGroup);
44823
+ const rightOffset = rightGroup === groupIndex ? offset : knownGroupOffsets.get(rightGroup);
44824
+ if (leftOffset === void 0 || rightOffset === void 0) return total;
44825
+ return total + Math.abs(
44826
+ unshiftedRanks.get(left) + leftOffset - unshiftedRanks.get(right) - rightOffset
44827
+ );
44828
+ }, 0);
44829
+ const addedRanks = directionalGroups[groupIndex].flatMap(
44830
+ (componentIndex) => components[componentIndex].map((nodeId) => unshiftedRanks.get(nodeId) + offset)
44831
+ );
44832
+ return {
44833
+ offset,
44834
+ crossGroupSpan,
44835
+ occupiedColumns: (/* @__PURE__ */ new Set([...rankedValues, ...addedRanks])).size
44836
+ };
44837
+ });
44838
+ candidates.sort(
44839
+ (left, right) => left.crossGroupSpan - right.crossGroupSpan || left.occupiedColumns - right.occupiedColumns || Math.abs(left.offset) - Math.abs(right.offset) || left.offset - right.offset
44840
+ );
44841
+ acceptedOffsets.set(groupIndex, candidates[0].offset);
44842
+ }
44843
+ for (const [groupIndex, offset] of acceptedOffsets) groupOffsets.set(groupIndex, offset);
44844
+ }
44845
+ const ranks = /* @__PURE__ */ new Map();
44846
+ for (const [nodeId, rank] of unshiftedRanks) {
44847
+ const groupOffset = groupOffsets.get(directionalGroupByNodeId.get(nodeId));
44848
+ ranks.set(nodeId, rank + (groupOffset ?? 0));
44849
+ }
44850
+ return ranks;
44851
+ }
44852
+ function componentRanks(nodeIds, topology, entrypointId) {
44853
+ const nodeSet = new Set(nodeIds);
44854
+ const entrypoint = entrypointId && nodeSet.has(entrypointId) ? entrypointId : null;
44855
+ if (!entrypoint) {
44856
+ const root3 = topologyRoot(nodeIds, topology);
44857
+ const ranks2 = /* @__PURE__ */ new Map();
44858
+ ranks2.set(root3, 0);
44859
+ const queue = [root3];
44860
+ for (let index2 = 0; index2 < queue.length; index2 += 1) {
44861
+ const current = queue[index2];
44862
+ for (const neighbor of topology.neighbors.get(current) ?? []) {
44863
+ if (!nodeSet.has(neighbor) || ranks2.has(neighbor)) continue;
44864
+ ranks2.set(neighbor, ranks2.get(current) + 1);
44865
+ queue.push(neighbor);
44866
+ }
44867
+ }
44868
+ return ranks2;
44869
+ }
44870
+ const ranks = directionalCondensationRanks(nodeIds, topology, entrypoint);
44871
+ const rankedQueue = [...ranks].sort(
44872
+ ([leftId, leftRank], [rightId, rightRank]) => leftRank - rightRank || stableCompare(leftId, rightId)
44873
+ ).map(([nodeId]) => nodeId);
44874
+ for (let index2 = 0; index2 < rankedQueue.length; index2 += 1) {
44875
+ const current = rankedQueue[index2];
44876
+ for (const neighbor of topology.neighbors.get(current) ?? []) {
44877
+ if (!nodeSet.has(neighbor) || ranks.has(neighbor)) continue;
44878
+ ranks.set(neighbor, ranks.get(current) + 1);
44879
+ rankedQueue.push(neighbor);
44880
+ }
44881
+ }
44882
+ return ranks;
44883
+ }
44884
+ function orderRankGroups(groups, topology) {
44885
+ const rankValues = [...groups.keys()].sort((left, right) => left - right);
44886
+ const result = new Map(
44887
+ rankValues.map((rank) => [rank, [...groups.get(rank)].sort(stableCompare)])
44888
+ );
44889
+ const sortAgainst = (rank, adjacentRank) => {
44890
+ const nodes = result.get(rank);
44891
+ const adjacent = result.get(adjacentRank);
44892
+ const adjacentOrder = new Map(adjacent.map((nodeId, index2) => [nodeId, index2]));
44893
+ const barycenter = (nodeId) => {
44894
+ const indexes = (topology.neighbors.get(nodeId) ?? []).flatMap((neighbor) => {
44895
+ const index2 = adjacentOrder.get(neighbor);
44896
+ return index2 === void 0 ? [] : [index2];
44897
+ });
44898
+ return indexes.length === 0 ? null : indexes.reduce((total, index2) => total + index2, 0) / indexes.length;
44899
+ };
44900
+ nodes.sort((left, right) => {
44901
+ const leftCenter = barycenter(left);
44902
+ const rightCenter = barycenter(right);
44903
+ if (leftCenter === null && rightCenter === null) return stableCompare(left, right);
44904
+ if (leftCenter === null) return 1;
44905
+ if (rightCenter === null) return -1;
44906
+ return leftCenter - rightCenter || stableCompare(left, right);
44907
+ });
44908
+ };
44909
+ for (let pass = 0; pass < 6; pass += 1) {
44910
+ for (let index2 = 1; index2 < rankValues.length; index2 += 1) {
44911
+ sortAgainst(rankValues[index2], rankValues[index2 - 1]);
44912
+ }
44913
+ for (let index2 = rankValues.length - 2; index2 >= 0; index2 -= 1) {
44914
+ sortAgainst(rankValues[index2], rankValues[index2 + 1]);
44915
+ }
44916
+ }
44917
+ return result;
44918
+ }
44919
+ var PRESENTATION_ROW_STRIDE = STRUCTURE_NODE_HEIGHT + 72;
44920
+ var PRESENTATION_COLUMN_STRIDE = STRUCTURE_NODE_WIDTH + 64;
44921
+ var PRESENTATION_TARGET_ASPECT_RATIO = 5 / 3;
44922
+ function layoutTopologyComponent(nodeIds, topology, entrypointId) {
44923
+ const ranks = componentRanks(nodeIds, topology, entrypointId);
44924
+ const rawGroups = /* @__PURE__ */ new Map();
44925
+ for (const nodeId of nodeIds) {
44926
+ const rank = ranks.get(nodeId) ?? 0;
44927
+ const group = rawGroups.get(rank) ?? [];
44928
+ group.push(nodeId);
44929
+ rawGroups.set(rank, group);
44930
+ }
44931
+ const groups = orderRankGroups(rawGroups, topology);
44932
+ const rankValues = [...groups.keys()].sort((left, right) => left - right);
44933
+ const rankStride = STRUCTURE_NODE_WIDTH + 192;
44934
+ const rowStride = STRUCTURE_NODE_HEIGHT + 72;
44935
+ const maxRows = Math.max(...[...groups.values()].map((nodes) => nodes.length));
44936
+ const centerY = (maxRows - 1) * rowStride / 2;
44937
+ const positions = /* @__PURE__ */ new Map();
44938
+ rankValues.forEach((rank, rankIndex) => {
44939
+ const group = groups.get(rank);
44940
+ const top = centerY - (group.length - 1) * rowStride / 2;
44941
+ group.forEach((nodeId, rowIndex) => {
44942
+ positions.set(nodeId, { x: rankIndex * rankStride, y: top + rowIndex * rowStride });
44943
+ });
44944
+ });
44945
+ return {
44946
+ nodeIds,
44947
+ positions,
44948
+ ranks,
44949
+ width: (rankValues.length - 1) * rankStride + STRUCTURE_NODE_WIDTH,
44950
+ height: (maxRows - 1) * rowStride + STRUCTURE_NODE_HEIGHT
44951
+ };
44952
+ }
44953
+ function projectTopologyStructureWithTopology(structure, topology) {
44954
+ if (structure.nodes.length === 0) {
44955
+ const diagnostics2 = {
44956
+ columnCount: 0,
44957
+ rowsPerColumn: [],
44958
+ maxRows: 0,
44959
+ directionalLinkCount: topology.directionalLinks.length,
44960
+ nonForwardDirectionalLinkCount: 0,
44961
+ nonForwardDirectionalLinkRatio: 0,
44962
+ originOutgoingDirectionalLinkCount: 0
44963
+ };
44964
+ return {
44965
+ positionsByNodeId: /* @__PURE__ */ new Map(),
44966
+ rankByNodeId: /* @__PURE__ */ new Map(),
44967
+ columnIndexByNodeId: /* @__PURE__ */ new Map(),
44968
+ columns: [],
44969
+ directionalLinks: topology.directionalLinks,
44970
+ diagnostics: diagnostics2
44971
+ };
44972
+ }
44973
+ const componentGap = 180;
44974
+ const outerPadding = 64;
44975
+ const components = topologyComponents(topology).map(
44976
+ (nodeIds) => layoutTopologyComponent(
44977
+ nodeIds,
44978
+ topology,
44979
+ nodeIds.includes(structure.originNodeId) ? structure.originNodeId : null
44980
+ )
44981
+ );
44982
+ const totalArea = components.reduce(
44983
+ (sum, component) => sum + (component.width + componentGap) * (component.height + componentGap),
44984
+ 0
44985
+ );
44986
+ const targetRowWidth = Math.max(960, Math.sqrt(totalArea) * 1.45);
44987
+ const positions = /* @__PURE__ */ new Map();
44988
+ const ranks = /* @__PURE__ */ new Map();
44989
+ let cursorX = outerPadding;
44990
+ let cursorY = outerPadding;
44991
+ let rowHeight = 0;
44992
+ for (const component of components) {
44993
+ if (cursorX > outerPadding && cursorX + component.width > targetRowWidth) {
44994
+ cursorX = outerPadding;
44995
+ cursorY += rowHeight + componentGap;
44996
+ rowHeight = 0;
44997
+ }
44998
+ for (const nodeId of component.nodeIds) {
44999
+ const point5 = component.positions.get(nodeId);
45000
+ positions.set(nodeId, { x: cursorX + point5.x, y: cursorY + point5.y });
45001
+ ranks.set(nodeId, component.ranks.get(nodeId) ?? 0);
45002
+ }
45003
+ cursorX += component.width + componentGap;
45004
+ rowHeight = Math.max(rowHeight, component.height);
45005
+ }
45006
+ const xValues = [...new Set([...positions.values()].map(({ x }) => x))].sort(
45007
+ (left, right) => left - right
45008
+ );
45009
+ const columnIndexByX = new Map(xValues.map((x, index2) => [x, index2]));
45010
+ const columns = xValues.map(
45011
+ (x) => [...positions].filter(([, point5]) => point5.x === x).sort(([, left], [, right]) => left.y - right.y).map(([nodeId]) => nodeId)
45012
+ );
45013
+ const columnIndexByNodeId = new Map(
45014
+ [...positions].map(([nodeId, point5]) => [nodeId, columnIndexByX.get(point5.x)])
45015
+ );
45016
+ const nonForwardDirectionalLinkCount = topology.directionalLinks.reduce(
45017
+ (count, [from, to]) => count + (columnIndexByNodeId.has(from) && columnIndexByNodeId.has(to) && columnIndexByNodeId.get(from) >= columnIndexByNodeId.get(to) ? 1 : 0),
45018
+ 0
45019
+ );
45020
+ const directionalLinkCount = topology.directionalLinks.length;
45021
+ const rowsPerColumn = columns.map((column) => column.length);
45022
+ const diagnostics = {
45023
+ columnCount: columns.length,
45024
+ rowsPerColumn,
45025
+ maxRows: Math.max(0, ...rowsPerColumn),
45026
+ directionalLinkCount,
45027
+ nonForwardDirectionalLinkCount,
45028
+ nonForwardDirectionalLinkRatio: directionalLinkCount === 0 ? 0 : nonForwardDirectionalLinkCount / directionalLinkCount,
45029
+ originOutgoingDirectionalLinkCount: topology.directionalLinks.filter(
45030
+ ([from]) => from === structure.originNodeId
45031
+ ).length
45032
+ };
45033
+ return {
45034
+ positionsByNodeId: positions,
45035
+ rankByNodeId: ranks,
45036
+ columnIndexByNodeId,
45037
+ columns,
45038
+ directionalLinks: topology.directionalLinks,
45039
+ diagnostics
45040
+ };
45041
+ }
45042
+ function projectTopologyStructure(structure) {
45043
+ return projectTopologyStructureWithTopology(structure, simpleStructureTopology(structure));
45044
+ }
45045
+ function structureAuthoringWarnings(diagnostics) {
45046
+ const warnings = [];
45047
+ if (diagnostics.originOutgoingDirectionalLinkCount === 0) {
45048
+ warnings.push({
45049
+ code: "STRUCTURE_ORIGIN_NO_OUTGOING_DIRECTIONAL_RELATION",
45050
+ message: "origin has no outgoing unambiguous directed relation; verify that it is the factual code entrypoint for this behavior. A terminal or intermediate origin may still be valid. Do not change factual relation direction solely to remove this warning."
45051
+ });
45052
+ }
45053
+ if (diagnostics.maxRows >= 8) {
45054
+ warnings.push({
45055
+ code: "STRUCTURE_LAYOUT_MAX_ROWS_HIGH",
45056
+ message: "the neutral topology diagnostic has a tall column; reconsider the factual origin, node granularity, overlapping or nested claims, whether multiple behaviors are mixed, and the subject boundary."
45057
+ });
45058
+ }
45059
+ if (diagnostics.nonForwardDirectionalLinkRatio >= 0.25) {
45060
+ warnings.push({
45061
+ code: "STRUCTURE_LAYOUT_NON_FORWARD_DIRECTIONAL_LINK_RATIO_HIGH",
45062
+ message: "many unambiguous directed relations are non-forward in the neutral topology diagnostic; reconsider the factual origin, node granularity, behavior boundary, and subject boundary. A factual graph may remain above this threshold. Do not change edge direction solely to improve the score."
45063
+ });
45064
+ }
45065
+ return warnings;
45066
+ }
45067
+
43808
45068
  // src/infrastructure/skills/skill-installer.ts
43809
45069
  import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
43810
45070
  import {
@@ -43824,6 +45084,7 @@ import path7 from "node:path";
43824
45084
  import { fileURLToPath as fileURLToPath4 } from "node:url";
43825
45085
  var skillNames = [
43826
45086
  "rvw",
45087
+ "rvw-review-compose",
43827
45088
  "rvw-walkthrough",
43828
45089
  "rvw-structure",
43829
45090
  "rvw-watch-comments"
@@ -47648,6 +48909,8 @@ var nullableCommentLine = external_exports.number().int().positive().nullable().
47648
48909
  var idempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS).optional();
47649
48910
  var requiredIdempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS);
47650
48911
  var expectedUpdatedAt = external_exports.string().min(1).max(100);
48912
+ var commentWatchTaskFenceSchema = external_exports.object({ taskId: external_exports.uuid(), generation: external_exports.number().int().positive() }).strict();
48913
+ var commentWatchLeaseSchema = commentWatchTaskFenceSchema.extend({ leaseId: external_exports.uuid() }).strict();
47651
48914
  var commentTargetInputSchema = external_exports.union([
47652
48915
  external_exports.object({ kind: external_exports.literal("pull-request") }).strict(),
47653
48916
  external_exports.object({
@@ -47735,14 +48998,16 @@ var commentReplyInputSchema = external_exports.object({
47735
48998
  authorLabel: external_exports.string().max(MAX_AUTHOR_LABEL_CHARACTERS).nullable().optional(),
47736
48999
  relatedCommitOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN).nullable().optional(),
47737
49000
  references: optionalCodeReferences,
47738
- idempotencyKey
49001
+ idempotencyKey,
49002
+ watchTask: commentWatchTaskFenceSchema.optional()
47739
49003
  }).strict().superRefine(requireRelatedCommitForReferences);
47740
49004
  var commentPostEditInputSchema = external_exports.object({
47741
49005
  body: external_exports.string().min(1).refine((value) => Buffer.byteLength(value, "utf8") <= MAX_COMMENT_BODY_BYTES, {
47742
49006
  message: `body\u306FUTF-8\u3067${MAX_COMMENT_BODY_BYTES} bytes\uFF0864 KiB\uFF09\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
47743
49007
  }),
47744
49008
  relatedCommitOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN).nullable().optional(),
47745
- references: optionalCodeReferences
49009
+ references: optionalCodeReferences,
49010
+ watchTask: commentWatchTaskFenceSchema.optional()
47746
49011
  }).strict();
47747
49012
  var pullRequestSyncInputSchema = external_exports.object({
47748
49013
  pullRequest: nonEmptyString,
@@ -47802,13 +49067,29 @@ var structureEdgeInputSchema = external_exports.object({
47802
49067
  directed: external_exports.boolean(),
47803
49068
  anchors: external_exports.array(sourceAnchorInputSchema).max(MAX_STRUCTURE_EDGE_ANCHORS).optional().default([])
47804
49069
  }).strict();
49070
+ var structurePresentationInputSchema = external_exports.object({
49071
+ thesis: external_exports.string().transform((value) => value.trim()).pipe(external_exports.string().min(1).max(MAX_STRUCTURE_PRESENTATION_THESIS_CHARACTERS)),
49072
+ startNodeId: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
49073
+ primaryBackbone: external_exports.object({
49074
+ edgeIds: external_exports.array(external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN)).min(1).max(MAX_STRUCTURE_PRIMARY_BACKBONE_EDGES).transform(canonicalStructureBackboneEdgeIds)
49075
+ }).strict().nullable(),
49076
+ regions: external_exports.array(
49077
+ external_exports.object({
49078
+ id: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
49079
+ label: external_exports.string().transform((value) => value.trim()).pipe(external_exports.string().min(1).max(MAX_STRUCTURE_PRESENTATION_REGION_LABEL_CHARACTERS)),
49080
+ summary: external_exports.string().transform((value) => value.trim()).pipe(external_exports.string().min(1).max(MAX_STRUCTURE_PRESENTATION_REGION_SUMMARY_CHARACTERS)),
49081
+ nodeIds: external_exports.array(external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN)).min(1).max(MAX_STRUCTURE_NODES).transform(canonicalStructureRegionNodeIds)
49082
+ }).strict()
49083
+ ).max(MAX_STRUCTURE_PRESENTATION_REGIONS).transform(canonicalStructurePresentationRegions)
49084
+ }).strict();
47805
49085
  var structureContentShape = {
47806
49086
  sourceOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
47807
49087
  title: external_exports.string().min(1).max(MAX_STRUCTURE_TITLE_CHARACTERS),
47808
49088
  scope: external_exports.string().min(1).max(MAX_STRUCTURE_SCOPE_CHARACTERS),
47809
49089
  originNodeId: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47810
49090
  nodes: external_exports.array(structureNodeInputSchema).min(1).max(MAX_STRUCTURE_NODES),
47811
- edges: external_exports.array(structureEdgeInputSchema).max(MAX_STRUCTURE_EDGES)
49091
+ edges: external_exports.array(structureEdgeInputSchema).max(MAX_STRUCTURE_EDGES),
49092
+ presentation: structurePresentationInputSchema.nullable()
47812
49093
  };
47813
49094
  function refineStructureContent(value, context) {
47814
49095
  const nodeIds = /* @__PURE__ */ new Set();
@@ -47887,6 +49168,107 @@ function refineStructureContent(value, context) {
47887
49168
  });
47888
49169
  }
47889
49170
  }
49171
+ if (value.presentation !== null) {
49172
+ if (!nodeIds.has(value.presentation.startNodeId)) {
49173
+ context.addIssue({
49174
+ code: "custom",
49175
+ path: ["presentation", "startNodeId"],
49176
+ message: "startNodeId\u306ENode\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
49177
+ });
49178
+ }
49179
+ const primaryBackbone = value.presentation.primaryBackbone;
49180
+ if (primaryBackbone !== null) {
49181
+ const selectedEdgeIds = /* @__PURE__ */ new Set();
49182
+ const edgeById = new Map(value.edges.map((edge) => [edge.id, edge]));
49183
+ const selectedEdges = [];
49184
+ let allEdgeIdsValid = true;
49185
+ for (const [index2, edgeId] of primaryBackbone.edgeIds.entries()) {
49186
+ if (selectedEdgeIds.has(edgeId)) {
49187
+ context.addIssue({
49188
+ code: "custom",
49189
+ path: ["presentation", "primaryBackbone", "edgeIds", index2],
49190
+ message: "primaryBackbone\u306EEdge ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
49191
+ });
49192
+ allEdgeIdsValid = false;
49193
+ continue;
49194
+ }
49195
+ selectedEdgeIds.add(edgeId);
49196
+ const edge = edgeById.get(edgeId);
49197
+ if (edge === void 0) {
49198
+ context.addIssue({
49199
+ code: "custom",
49200
+ path: ["presentation", "primaryBackbone", "edgeIds", index2],
49201
+ message: "primaryBackbone\u306EEdge\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
49202
+ });
49203
+ allEdgeIdsValid = false;
49204
+ continue;
49205
+ }
49206
+ selectedEdges.push(edge);
49207
+ }
49208
+ if (allEdgeIdsValid) {
49209
+ const backboneNodeIds = structureBackboneNodeIds(selectedEdges);
49210
+ if (backboneNodeIds.size < 2 || backboneNodeIds.size > MAX_STRUCTURE_PRIMARY_BACKBONE_NODES) {
49211
+ context.addIssue({
49212
+ code: "custom",
49213
+ path: ["presentation", "primaryBackbone", "edgeIds"],
49214
+ message: `primaryBackbone\u306F2\u301C${MAX_STRUCTURE_PRIMARY_BACKBONE_NODES}\u4EF6\u306ENode\u3092\u7D50\u3076\u3088\u3046\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
49215
+ });
49216
+ }
49217
+ if (!backboneNodeIds.has(value.presentation.startNodeId)) {
49218
+ context.addIssue({
49219
+ code: "custom",
49220
+ path: ["presentation", "startNodeId"],
49221
+ message: "startNodeId\u306FprimaryBackbone\u306EEdge endpoint\u306B\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
49222
+ });
49223
+ } else if (!isStructureBackboneWeaklyConnected(selectedEdges, value.presentation.startNodeId)) {
49224
+ context.addIssue({
49225
+ code: "custom",
49226
+ path: ["presentation", "primaryBackbone", "edgeIds"],
49227
+ message: "primaryBackbone\u306FstartNodeId\u304B\u3089\u8FBF\u308C\u308B\u4E00\u3064\u306Econnected graph\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
49228
+ });
49229
+ }
49230
+ }
49231
+ }
49232
+ const regionIds = /* @__PURE__ */ new Set();
49233
+ const regionByNodeId = /* @__PURE__ */ new Map();
49234
+ for (const [regionIndex, region] of value.presentation.regions.entries()) {
49235
+ if (regionIds.has(region.id)) {
49236
+ context.addIssue({
49237
+ code: "custom",
49238
+ path: ["presentation", "regions", regionIndex, "id"],
49239
+ message: "region ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
49240
+ });
49241
+ }
49242
+ regionIds.add(region.id);
49243
+ const currentRegionNodeIds = /* @__PURE__ */ new Set();
49244
+ for (const [nodeIndex, nodeId] of region.nodeIds.entries()) {
49245
+ if (currentRegionNodeIds.has(nodeId)) {
49246
+ context.addIssue({
49247
+ code: "custom",
49248
+ path: ["presentation", "regions", regionIndex, "nodeIds", nodeIndex],
49249
+ message: "\u540C\u3058region\u5185\u3067Node ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
49250
+ });
49251
+ }
49252
+ currentRegionNodeIds.add(nodeId);
49253
+ if (!nodeIds.has(nodeId)) {
49254
+ context.addIssue({
49255
+ code: "custom",
49256
+ path: ["presentation", "regions", regionIndex, "nodeIds", nodeIndex],
49257
+ message: "region\u306ENode\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
49258
+ });
49259
+ }
49260
+ const assignedRegionId = regionByNodeId.get(nodeId);
49261
+ if (assignedRegionId !== void 0 && assignedRegionId !== region.id) {
49262
+ context.addIssue({
49263
+ code: "custom",
49264
+ path: ["presentation", "regions", regionIndex, "nodeIds", nodeIndex],
49265
+ message: "\u4E00\u3064\u306ENode\u3092\u8907\u6570region\u3078\u6240\u5C5E\u3055\u305B\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093\u3002"
49266
+ });
49267
+ }
49268
+ if (assignedRegionId === void 0) regionByNodeId.set(nodeId, region.id);
49269
+ }
49270
+ }
49271
+ }
47890
49272
  const anchorCount = value.nodes.filter((node2) => node2.anchor !== null).length + value.edges.reduce((count, edge) => count + edge.anchors.length, 0);
47891
49273
  if (anchorCount < 1) {
47892
49274
  context.addIssue({
@@ -47900,7 +49282,12 @@ function refineStructureContent(value, context) {
47900
49282
  message: `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
47901
49283
  });
47902
49284
  }
47903
- const graph = { originNodeId: value.originNodeId, nodes: value.nodes, edges: value.edges };
49285
+ const graph = {
49286
+ originNodeId: value.originNodeId,
49287
+ nodes: value.nodes,
49288
+ edges: value.edges,
49289
+ presentation: value.presentation
49290
+ };
47904
49291
  if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
47905
49292
  context.addIssue({
47906
49293
  code: "custom",
@@ -47935,6 +49322,10 @@ var agentCommandInputSchemas = {
47935
49322
  cursor: external_exports.string().min(1).max(512).optional(),
47936
49323
  limit: external_exports.number().int().min(1).max(MAX_COMMENT_WATCH_LIMIT)
47937
49324
  }).strict(),
49325
+ "comment.watch.activate": external_exports.object({ taskId: external_exports.uuid() }).strict(),
49326
+ "comment.watch.verify": commentWatchTaskFenceSchema,
49327
+ "comment.watch.reserveWrite": commentWatchLeaseSchema.extend({ writeKey: external_exports.string().regex(/^[^/\s]+\/[^/\s]+$/) }).strict(),
49328
+ "comment.watch.releaseWrite": commentWatchLeaseSchema,
47938
49329
  "comment.create": commentCreateInputSchema,
47939
49330
  "comment.get": external_exports.object({ uri: commentUri, live: external_exports.boolean().default(false) }).strict(),
47940
49331
  "comment.reply": external_exports.object({ uri: commentUri, reply: commentReplyInputSchema }).strict(),
@@ -48790,7 +50181,22 @@ async function startServer(service, options = {}) {
48790
50181
  });
48791
50182
  }
48792
50183
  );
48793
- server.on("error", reject);
50184
+ server.on("error", (error51) => {
50185
+ if (error51.code !== "EADDRINUSE" || (options.port ?? 0) === 0) {
50186
+ reject(error51);
50187
+ return;
50188
+ }
50189
+ reject(
50190
+ new RvwError("PROCESS_FAILED", `port ${options.port}\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002`, {
50191
+ cause: error51,
50192
+ details: { port: options.port, reason: "address-in-use" },
50193
+ suggestions: [
50194
+ "\u8D77\u52D5\u4E2D\u306Eprocess\u3092\u505C\u6B62\u3059\u308B\u304B\u3001\u5225\u306E--port\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
50195
+ "\u4E00\u6642\u7684\u306B\u7A7A\u304Dport\u3092\u81EA\u52D5\u9078\u629E\u3059\u308B\u5834\u5408\u306F--port 0\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
50196
+ ]
50197
+ })
50198
+ );
50199
+ });
48794
50200
  });
48795
50201
  }
48796
50202
 
@@ -48811,7 +50217,7 @@ import os4 from "node:os";
48811
50217
  import path9 from "node:path";
48812
50218
  var MAX_CLI_STDIN_BYTES = 40 * 1024 * 1024;
48813
50219
  var MAX_AGENT_MESSAGE_BYTES = MAX_CLI_STDIN_BYTES + 64 * 1024;
48814
- var AGENT_SOCKET_PROTOCOL_VERSION = 4;
50220
+ var AGENT_SOCKET_PROTOCOL_VERSION = 5;
48815
50221
  var RUNTIME_VIEWER_OPEN_OPERATION = "viewer.open";
48816
50222
  var AGENT_SOCKET_RESTART_SUGGESTION = "\u8D77\u52D5\u4E2D\u306Ervw viewer\u3092\u505C\u6B62\u3057\u3001\u66F4\u65B0\u5F8C\u306Ervw open\u3067\u518D\u8D77\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
48817
50223
  var DEFAULT_CONNECT_TIMEOUT_MS = 2e3;
@@ -48976,6 +50382,27 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
48976
50382
  const input = parseOperationInput("comment.watch", request.input);
48977
50383
  return service.listCommentPostEvents(input.cursor, input.limit);
48978
50384
  }
50385
+ case "comment.watch.activate": {
50386
+ const input = parseOperationInput("comment.watch.activate", request.input);
50387
+ return service.activateCommentWatchTask(input.taskId);
50388
+ }
50389
+ case "comment.watch.verify": {
50390
+ const input = parseOperationInput("comment.watch.verify", request.input);
50391
+ return service.verifyCommentWatchTask(input.taskId, input.generation);
50392
+ }
50393
+ case "comment.watch.reserveWrite": {
50394
+ const input = parseOperationInput("comment.watch.reserveWrite", request.input);
50395
+ return service.reserveCommentWatchWriter(
50396
+ input.taskId,
50397
+ input.generation,
50398
+ input.leaseId,
50399
+ input.writeKey
50400
+ );
50401
+ }
50402
+ case "comment.watch.releaseWrite": {
50403
+ const input = parseOperationInput("comment.watch.releaseWrite", request.input);
50404
+ return service.releaseCommentWatchWriter(input.taskId, input.generation, input.leaseId);
50405
+ }
48979
50406
  case "comment.create": {
48980
50407
  const input = parseOperationInput("comment.create", request.input);
48981
50408
  return await service.createCommentForReference(
@@ -48999,6 +50426,7 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
48999
50426
  body: input.edit.body,
49000
50427
  ...input.edit.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.edit.relatedCommitOid },
49001
50428
  ...input.edit.references === void 0 ? {} : { references: input.edit.references },
50429
+ ...input.edit.watchTask === void 0 ? {} : { watchTask: input.edit.watchTask },
49002
50430
  lastModifiedBy: "agent"
49003
50431
  });
49004
50432
  }
@@ -50080,6 +51508,10 @@ function writeJson(value) {
50080
51508
  process.stdout.write(`${JSON.stringify(value)}
50081
51509
  `);
50082
51510
  }
51511
+ function structureAuthoringFeedback(structure) {
51512
+ const layout = projectTopologyStructure(structure).diagnostics;
51513
+ return { layout, warnings: structureAuthoringWarnings(layout) };
51514
+ }
50083
51515
  function writeJsonSequence(value) {
50084
51516
  process.stdout.write(`${JSON.stringify(value)}
50085
51517
  `);
@@ -50284,12 +51716,12 @@ function staticDirectory() {
50284
51716
  ];
50285
51717
  return candidates.find((candidate) => existsSync6(candidate)) ?? candidates[0];
50286
51718
  }
50287
- function forkOpenWorker(reference, port) {
51719
+ function forkOpenWorker(reference, port, requestedPort) {
50288
51720
  const modulePath = process.argv[1];
50289
51721
  if (!modulePath) {
50290
51722
  throw new RvwError("PROCESS_FAILED", "rvw CLI\u306E\u5B9F\u884Cpath\u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002");
50291
51723
  }
50292
- const args = ["__open-worker", "--port", String(port)];
51724
+ const args = ["__open-worker", "--port", String(port), "--requested-port", String(requestedPort)];
50293
51725
  if (reference !== void 0) args.push("--reference", reference);
50294
51726
  return fork(path10.resolve(modulePath), args, {
50295
51727
  cwd: process.cwd(),
@@ -50299,10 +51731,12 @@ function forkOpenWorker(reference, port) {
50299
51731
  });
50300
51732
  }
50301
51733
  async function startBackgroundOpen(reference, port, dependencies = {}) {
51734
+ const startupPort = port ?? DEFAULT_VIEWER_PORT;
51735
+ const requestedPort = port ?? 0;
50302
51736
  const input = {
50303
51737
  ...reference === void 0 ? {} : { reference },
50304
51738
  cwd: process.cwd(),
50305
- requestedPort: port
51739
+ requestedPort
50306
51740
  };
50307
51741
  const launchBrowser = dependencies.launchBrowser ?? (async (url2) => await open_default(url2));
50308
51742
  let existing = null;
@@ -50322,7 +51756,7 @@ async function startBackgroundOpen(reference, port, dependencies = {}) {
50322
51756
  { details: existing.details }
50323
51757
  );
50324
51758
  }
50325
- const child = (dependencies.forkWorker ?? forkOpenWorker)(reference, port);
51759
+ const child = (dependencies.forkWorker ?? forkOpenWorker)(reference, startupPort, requestedPort);
50326
51760
  return await completeBackgroundOpen(child, launchBrowser);
50327
51761
  }
50328
51762
  async function sendOpenWorkerMessage(message) {
@@ -50515,6 +51949,8 @@ async function waitForWorkerParentDisconnect() {
50515
51949
  await new Promise((resolve) => process.once("disconnect", resolve));
50516
51950
  }
50517
51951
  async function runOpenServer(runtimeFactory, reference, port, openAutomatically, useAgentSocket, reuseExisting) {
51952
+ const startupPort = port ?? DEFAULT_VIEWER_PORT;
51953
+ const requestedPort = port ?? 0;
50518
51954
  let activeRuntime;
50519
51955
  let running;
50520
51956
  let agentSocket;
@@ -50523,7 +51959,7 @@ async function runOpenServer(runtimeFactory, reference, port, openAutomatically,
50523
51959
  const databaseFilePath = databasePathConfiguration().filePath;
50524
51960
  if (reuseExisting) {
50525
51961
  const startup = await acquireRuntimeOrReuseExisting(
50526
- runtimeViewerOpenInput(reference, process.cwd(), port),
51962
+ runtimeViewerOpenInput(reference, process.cwd(), requestedPort),
50527
51963
  databaseFilePath
50528
51964
  );
50529
51965
  if (startup.kind === "reused") {
@@ -50552,7 +51988,7 @@ async function runOpenServer(runtimeFactory, reference, port, openAutomatically,
50552
51988
  activeRuntime = runtimeFactory();
50553
51989
  const opened = await activeRuntime.service.openPullRequest(reference, process.cwd());
50554
51990
  running = await startServer(activeRuntime.service, {
50555
- port,
51991
+ port: startupPort,
50556
51992
  staticDirectory: staticDirectory(),
50557
51993
  autoCloseWhenNoViewers: openAutomatically
50558
51994
  });
@@ -50570,7 +52006,7 @@ async function runOpenServer(runtimeFactory, reference, port, openAutomatically,
50570
52006
  await closeOwnedRuntime(agentSocket, running, activeRuntime);
50571
52007
  }
50572
52008
  }
50573
- async function runOpenWorker(runtimeFactory, reference, port) {
52009
+ async function runOpenWorker(runtimeFactory, reference, port, requestedPort) {
50574
52010
  let activeRuntime;
50575
52011
  let running;
50576
52012
  let agentSocket;
@@ -50580,7 +52016,7 @@ async function runOpenWorker(runtimeFactory, reference, port) {
50580
52016
  }
50581
52017
  const databaseFilePath = databasePathConfiguration().filePath;
50582
52018
  const startup = await acquireRuntimeOrReuseExisting(
50583
- runtimeViewerOpenInput(reference, process.cwd(), port),
52019
+ runtimeViewerOpenInput(reference, process.cwd(), requestedPort),
50584
52020
  databaseFilePath
50585
52021
  );
50586
52022
  if (startup.kind === "reused") {
@@ -50678,6 +52114,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50678
52114
  "comment.create",
50679
52115
  "comment.list",
50680
52116
  "comment.watch",
52117
+ "comment.watchOwnership",
50681
52118
  "comment.read",
50682
52119
  "comment.reply",
50683
52120
  "comment.edit",
@@ -50687,6 +52124,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50687
52124
  "pullRequest.sync",
50688
52125
  "structure.list",
50689
52126
  "structure.read",
52127
+ "structure.preview",
52128
+ "structure.presentation",
50690
52129
  "structure.publish",
50691
52130
  "structure.update",
50692
52131
  "structure.delete",
@@ -50716,7 +52155,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50716
52155
  writeOutput(options, { ok: ok2, ...status }, formatAgentTransportStatus(status));
50717
52156
  if (!ok2) process.exitCode = 2;
50718
52157
  });
50719
- program2.command("open").argument("[pull-request]", "PR URL\u307E\u305F\u306F\u756A\u53F7").option("--no-open", "\u30D6\u30E9\u30A6\u30B6\u3092\u958B\u304B\u306A\u3044").option("--foreground", "terminal\u306B\u63A5\u7D9A\u3057\u305F\u307E\u307Eviewer\u3092\u5B9F\u884C").option("--port <port>", "listen port\uFF080\u306F\u81EA\u52D5\uFF09", parsePort, 0).description("Pull Request\u3092\u958B\u3044\u3066\u30ED\u30FC\u30AB\u30EBviewer\u3092\u8D77\u52D5").action(
52158
+ program2.command("open").argument("[pull-request]", "PR URL\u307E\u305F\u306F\u756A\u53F7").option("--no-open", "\u30D6\u30E9\u30A6\u30B6\u3092\u958B\u304B\u306A\u3044").option("--foreground", "terminal\u306B\u63A5\u7D9A\u3057\u305F\u307E\u307Eviewer\u3092\u5B9F\u884C").option("--port <port>", `listen port\uFF08\u65E2\u5B9A${DEFAULT_VIEWER_PORT}\u30010\u306F\u81EA\u52D5\uFF09`, parsePort).description("Pull Request\u3092\u958B\u3044\u3066\u30ED\u30FC\u30AB\u30EBviewer\u3092\u8D77\u52D5").action(
50720
52159
  async (reference, options) => {
50721
52160
  if (options.open && !options.foreground && useAgentSocket) {
50722
52161
  const url2 = await startBackgroundOpen(reference, options.port);
@@ -50734,9 +52173,9 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50734
52173
  );
50735
52174
  }
50736
52175
  );
50737
- program2.command("__open-worker", { hidden: true }).option("--reference <pull-request>").requiredOption("--port <port>", "listen port", parsePort).action(async (options) => {
52176
+ program2.command("__open-worker", { hidden: true }).option("--reference <pull-request>").requiredOption("--port <port>", "listen port", parsePort).requiredOption("--requested-port <port>", "requested listen port", parsePort).action(async (options) => {
50738
52177
  try {
50739
- await runOpenWorker(getRuntime, options.reference, options.port);
52178
+ await runOpenWorker(getRuntime, options.reference, options.port, options.requestedPort);
50740
52179
  } catch (error51) {
50741
52180
  const rvwError = asRvwError(error51);
50742
52181
  const sent = await sendOpenWorkerMessage({
@@ -50825,6 +52264,10 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50825
52264
  });
50826
52265
  const comment3 = program2.command("comment").description("\u4FDD\u5B58\u6E08\u307F\u30B3\u30E1\u30F3\u30C8\u3092\u64CD\u4F5C");
50827
52266
  const structure = program2.command("structure").description("source anchor\u4ED8\u304DStructure\u3092\u7BA1\u7406");
52267
+ structure.command("preview").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure content\u3092\u4FDD\u5B58\u305B\u305Acanonical initial layout\u3067\u691C\u67FB").action(async () => {
52268
+ const content3 = structureContentInputSchema.parse(await readStdinJson());
52269
+ writeJson({ ok: true, ...structureAuthoringFeedback(content3) });
52270
+ });
50828
52271
  structure.command("publish").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u3092\u767B\u9332\uFF08viewer\u306F\u958B\u304B\u305Anavigation\u3082\u5909\u66F4\u3057\u306A\u3044\uFF09").action(async () => {
50829
52272
  const input = structurePublishInputSchema.parse(await readStdinJson());
50830
52273
  const published = await callService(
@@ -50832,7 +52275,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50832
52275
  input,
50833
52276
  async () => await getRuntime().service.publishStructure(input)
50834
52277
  );
50835
- writeJson({ ok: true, structure: published });
52278
+ const warnings = structureAuthoringFeedback(published).warnings;
52279
+ writeJson({ ok: true, structure: published, ...warnings.length > 0 ? { warnings } : {} });
50836
52280
  });
50837
52281
  structure.command("get").argument("<structure-uri>").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u306E\u73FE\u5728\u5185\u5BB9\u3092\u53D6\u5F97").action(async (uri) => {
50838
52282
  const result = await callService(
@@ -50857,7 +52301,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50857
52301
  { uri, content: content3 },
50858
52302
  async () => await getRuntime().service.updateStructure(uri, content3)
50859
52303
  );
50860
- writeJson({ ok: true, structure: updated });
52304
+ const warnings = structureAuthoringFeedback(updated).warnings;
52305
+ writeJson({ ok: true, structure: updated, ...warnings.length > 0 ? { warnings } : {} });
50861
52306
  });
50862
52307
  structure.command("delete").argument("<structure-uri>").option("--yes", "\u4E0D\u53EF\u9006\u306A\u524A\u9664\u3092\u78BA\u8A8D").option("--expected-updated-at <timestamp>", "preview\u3067\u53D6\u5F97\u3057\u305FupdatedAt").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u3092\u524A\u9664").action(
50863
52308
  async (uri, options) => {
@@ -51018,6 +52463,66 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51018
52463
  process.off("SIGTERM", stop);
51019
52464
  }
51020
52465
  });
52466
+ const watchTask = comment3.command("watch-task").description("comment watch task\u306Eshared ownership\u3092\u7BA1\u7406");
52467
+ watchTask.command("activate").requiredOption("--task-id <task-id>", "\u65B0\u3057\u3044logical watch task ID").requiredOption("--json", "JSON\u3067\u51FA\u529B").action(async (options) => {
52468
+ const input = { taskId: external_exports.uuid().parse(options.taskId) };
52469
+ const result = await callService(
52470
+ "comment.watch.activate",
52471
+ input,
52472
+ () => getRuntime().service.activateCommentWatchTask(input.taskId)
52473
+ );
52474
+ writeJson({ ok: true, ...result });
52475
+ });
52476
+ watchTask.command("verify").requiredOption("--task-id <task-id>", "logical watch task ID").requiredOption("--generation <generation>", "watch generation").requiredOption("--json", "JSON\u3067\u51FA\u529B").action(async (options) => {
52477
+ const input = {
52478
+ taskId: external_exports.uuid().parse(options.taskId),
52479
+ generation: external_exports.coerce.number().int().positive().parse(options.generation)
52480
+ };
52481
+ const result = await callService(
52482
+ "comment.watch.verify",
52483
+ input,
52484
+ () => getRuntime().service.verifyCommentWatchTask(input.taskId, input.generation)
52485
+ );
52486
+ writeJson({ ok: true, ...result });
52487
+ });
52488
+ watchTask.command("reserve-write").requiredOption("--task-id <task-id>", "logical watch task ID").requiredOption("--generation <generation>", "watch generation").requiredOption("--lease-id <lease-id>", "task-local batch lease ID").requiredOption("--write-key <owner-repository>", "\u5171\u6709\u3059\u308Bowner/repository writer key").requiredOption("--json", "JSON\u3067\u51FA\u529B").action(
52489
+ async (options) => {
52490
+ const input = {
52491
+ taskId: external_exports.uuid().parse(options.taskId),
52492
+ generation: external_exports.coerce.number().int().positive().parse(options.generation),
52493
+ leaseId: external_exports.uuid().parse(options.leaseId),
52494
+ writeKey: external_exports.string().regex(/^[^/\s]+\/[^/\s]+$/).parse(options.writeKey.toLowerCase())
52495
+ };
52496
+ const result = await callService(
52497
+ "comment.watch.reserveWrite",
52498
+ input,
52499
+ () => getRuntime().service.reserveCommentWatchWriter(
52500
+ input.taskId,
52501
+ input.generation,
52502
+ input.leaseId,
52503
+ input.writeKey
52504
+ )
52505
+ );
52506
+ writeJson({ ok: true, ...result });
52507
+ }
52508
+ );
52509
+ watchTask.command("release-write").requiredOption("--task-id <task-id>", "logical watch task ID").requiredOption("--generation <generation>", "watch generation").requiredOption("--lease-id <lease-id>", "task-local batch lease ID").requiredOption("--json", "JSON\u3067\u51FA\u529B").action(async (options) => {
52510
+ const input = {
52511
+ taskId: external_exports.uuid().parse(options.taskId),
52512
+ generation: external_exports.coerce.number().int().positive().parse(options.generation),
52513
+ leaseId: external_exports.uuid().parse(options.leaseId)
52514
+ };
52515
+ const result = await callService(
52516
+ "comment.watch.releaseWrite",
52517
+ input,
52518
+ () => getRuntime().service.releaseCommentWatchWriter(
52519
+ input.taskId,
52520
+ input.generation,
52521
+ input.leaseId
52522
+ )
52523
+ );
52524
+ writeJson({ ok: true, ...result });
52525
+ });
51021
52526
  comment3.command("create").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("\u767B\u9332\u6E08\u307FPR\u3078\u672A\u89E3\u6C7A\u30B3\u30E1\u30F3\u30C8\u3092\u4E00\u4EF6\u4F5C\u6210").action(async () => {
51022
52527
  const input = commentCreateInputSchema.parse(await readStdinJson());
51023
52528
  const request = {
@@ -51069,7 +52574,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51069
52574
  ...input.authorLabel === void 0 ? {} : { authorLabel: input.authorLabel },
51070
52575
  ...input.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.relatedCommitOid },
51071
52576
  ...input.references === void 0 ? {} : { references: input.references },
51072
- ...input.idempotencyKey === void 0 ? {} : { idempotencyKey: input.idempotencyKey }
52577
+ ...input.idempotencyKey === void 0 ? {} : { idempotencyKey: input.idempotencyKey },
52578
+ ...input.watchTask === void 0 ? {} : { watchTask: input.watchTask }
51073
52579
  };
51074
52580
  const post = await callService(
51075
52581
  "comment.reply",
@@ -51086,7 +52592,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51086
52592
  const edit = {
51087
52593
  body: input.body,
51088
52594
  ...input.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.relatedCommitOid },
51089
- ...input.references === void 0 ? {} : { references: input.references }
52595
+ ...input.references === void 0 ? {} : { references: input.references },
52596
+ ...input.watchTask === void 0 ? {} : { watchTask: input.watchTask }
51090
52597
  };
51091
52598
  const post = await callService(
51092
52599
  "comment.edit",