@a9n-shoji/rvw 0.5.0 → 0.7.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 +67 -0
  2. package/README.md +162 -42
  3. package/dist/cli.mjs +898 -87
  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-CXtsUfYG.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-CbUV8NfH.css +1 -0
  10. package/dist/web/assets/index-zyXQjX9Z.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 +6 -2
  17. package/skills/rvw-review-compose/SKILL.md +226 -0
  18. package/skills/rvw-review-compose/agents/openai.yaml +4 -0
  19. package/skills/rvw-review-compose/references/review-composition.md +463 -0
  20. package/skills/rvw-structure/SKILL.md +124 -37
  21. package/skills/rvw-structure/agents/openai.yaml +2 -2
  22. package/skills/rvw-structure/references/structure-authoring.md +306 -75
  23. package/skills/rvw-walkthrough/SKILL.md +77 -15
  24. package/skills/rvw-walkthrough/agents/openai.yaml +2 -2
  25. package/skills/rvw-walkthrough/references/walkthrough-authoring.md +168 -30
  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-RiN3cqvU.js +0 -5
  32. package/dist/web/assets/index-CUAdTtdU.css +0 -1
  33. package/dist/web/assets/index-D0LsMDeu.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.7.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.5.0";
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
  }
@@ -44346,6 +44916,9 @@ function orderRankGroups(groups, topology) {
44346
44916
  }
44347
44917
  return result;
44348
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;
44349
44922
  function layoutTopologyComponent(nodeIds, topology, entrypointId) {
44350
44923
  const ranks = componentRanks(nodeIds, topology, entrypointId);
44351
44924
  const rawGroups = /* @__PURE__ */ new Map();
@@ -44377,8 +44950,7 @@ function layoutTopologyComponent(nodeIds, topology, entrypointId) {
44377
44950
  height: (maxRows - 1) * rowStride + STRUCTURE_NODE_HEIGHT
44378
44951
  };
44379
44952
  }
44380
- function projectStructure(structure) {
44381
- const topology = simpleStructureTopology(structure);
44953
+ function projectTopologyStructureWithTopology(structure, topology) {
44382
44954
  if (structure.nodes.length === 0) {
44383
44955
  const diagnostics2 = {
44384
44956
  columnCount: 0,
@@ -44467,24 +45039,27 @@ function projectStructure(structure) {
44467
45039
  diagnostics
44468
45040
  };
44469
45041
  }
45042
+ function projectTopologyStructure(structure) {
45043
+ return projectTopologyStructureWithTopology(structure, simpleStructureTopology(structure));
45044
+ }
44470
45045
  function structureAuthoringWarnings(diagnostics) {
44471
45046
  const warnings = [];
44472
45047
  if (diagnostics.originOutgoingDirectionalLinkCount === 0) {
44473
45048
  warnings.push({
44474
45049
  code: "STRUCTURE_ORIGIN_NO_OUTGOING_DIRECTIONAL_RELATION",
44475
- 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."
45050
+ message: "origin has no outgoing unambiguous directed relation; verify that it is the factual source-verification start for this Structure. For a behavior Structure, recheck the code entrypoint. For a file map, a terminal, intermediate, or single-file origin may still be valid. Do not change factual relation direction solely to remove this warning."
44476
45051
  });
44477
45052
  }
44478
45053
  if (diagnostics.maxRows >= 8) {
44479
45054
  warnings.push({
44480
45055
  code: "STRUCTURE_LAYOUT_MAX_ROWS_HIGH",
44481
- message: "the initial projection has a tall column; reconsider the factual origin, node granularity, overlapping or nested claims, whether multiple behaviors are mixed, and the subject boundary."
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."
44482
45057
  });
44483
45058
  }
44484
45059
  if (diagnostics.nonForwardDirectionalLinkRatio >= 0.25) {
44485
45060
  warnings.push({
44486
45061
  code: "STRUCTURE_LAYOUT_NON_FORWARD_DIRECTIONAL_LINK_RATIO_HIGH",
44487
- message: "many unambiguous directed relations are non-forward in the initial projection; 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."
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."
44488
45063
  });
44489
45064
  }
44490
45065
  return warnings;
@@ -44509,6 +45084,7 @@ import path7 from "node:path";
44509
45084
  import { fileURLToPath as fileURLToPath4 } from "node:url";
44510
45085
  var skillNames = [
44511
45086
  "rvw",
45087
+ "rvw-review-compose",
44512
45088
  "rvw-walkthrough",
44513
45089
  "rvw-structure",
44514
45090
  "rvw-watch-comments"
@@ -48333,6 +48909,8 @@ var nullableCommentLine = external_exports.number().int().positive().nullable().
48333
48909
  var idempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS).optional();
48334
48910
  var requiredIdempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS);
48335
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();
48336
48914
  var commentTargetInputSchema = external_exports.union([
48337
48915
  external_exports.object({ kind: external_exports.literal("pull-request") }).strict(),
48338
48916
  external_exports.object({
@@ -48420,14 +48998,16 @@ var commentReplyInputSchema = external_exports.object({
48420
48998
  authorLabel: external_exports.string().max(MAX_AUTHOR_LABEL_CHARACTERS).nullable().optional(),
48421
48999
  relatedCommitOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN).nullable().optional(),
48422
49000
  references: optionalCodeReferences,
48423
- idempotencyKey
49001
+ idempotencyKey,
49002
+ watchTask: commentWatchTaskFenceSchema.optional()
48424
49003
  }).strict().superRefine(requireRelatedCommitForReferences);
48425
49004
  var commentPostEditInputSchema = external_exports.object({
48426
49005
  body: external_exports.string().min(1).refine((value) => Buffer.byteLength(value, "utf8") <= MAX_COMMENT_BODY_BYTES, {
48427
49006
  message: `body\u306FUTF-8\u3067${MAX_COMMENT_BODY_BYTES} bytes\uFF0864 KiB\uFF09\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
48428
49007
  }),
48429
49008
  relatedCommitOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN).nullable().optional(),
48430
- references: optionalCodeReferences
49009
+ references: optionalCodeReferences,
49010
+ watchTask: commentWatchTaskFenceSchema.optional()
48431
49011
  }).strict();
48432
49012
  var pullRequestSyncInputSchema = external_exports.object({
48433
49013
  pullRequest: nonEmptyString,
@@ -48487,13 +49067,29 @@ var structureEdgeInputSchema = external_exports.object({
48487
49067
  directed: external_exports.boolean(),
48488
49068
  anchors: external_exports.array(sourceAnchorInputSchema).max(MAX_STRUCTURE_EDGE_ANCHORS).optional().default([])
48489
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();
48490
49085
  var structureContentShape = {
48491
49086
  sourceOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
48492
49087
  title: external_exports.string().min(1).max(MAX_STRUCTURE_TITLE_CHARACTERS),
48493
49088
  scope: external_exports.string().min(1).max(MAX_STRUCTURE_SCOPE_CHARACTERS),
48494
49089
  originNodeId: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
48495
49090
  nodes: external_exports.array(structureNodeInputSchema).min(1).max(MAX_STRUCTURE_NODES),
48496
- edges: external_exports.array(structureEdgeInputSchema).max(MAX_STRUCTURE_EDGES)
49091
+ edges: external_exports.array(structureEdgeInputSchema).max(MAX_STRUCTURE_EDGES),
49092
+ presentation: structurePresentationInputSchema.nullable()
48497
49093
  };
48498
49094
  function refineStructureContent(value, context) {
48499
49095
  const nodeIds = /* @__PURE__ */ new Set();
@@ -48572,6 +49168,107 @@ function refineStructureContent(value, context) {
48572
49168
  });
48573
49169
  }
48574
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
+ }
48575
49272
  const anchorCount = value.nodes.filter((node2) => node2.anchor !== null).length + value.edges.reduce((count, edge) => count + edge.anchors.length, 0);
48576
49273
  if (anchorCount < 1) {
48577
49274
  context.addIssue({
@@ -48585,7 +49282,12 @@ function refineStructureContent(value, context) {
48585
49282
  message: `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
48586
49283
  });
48587
49284
  }
48588
- 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
+ };
48589
49291
  if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
48590
49292
  context.addIssue({
48591
49293
  code: "custom",
@@ -48620,6 +49322,10 @@ var agentCommandInputSchemas = {
48620
49322
  cursor: external_exports.string().min(1).max(512).optional(),
48621
49323
  limit: external_exports.number().int().min(1).max(MAX_COMMENT_WATCH_LIMIT)
48622
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,
48623
49329
  "comment.create": commentCreateInputSchema,
48624
49330
  "comment.get": external_exports.object({ uri: commentUri, live: external_exports.boolean().default(false) }).strict(),
48625
49331
  "comment.reply": external_exports.object({ uri: commentUri, reply: commentReplyInputSchema }).strict(),
@@ -49475,7 +50181,22 @@ async function startServer(service, options = {}) {
49475
50181
  });
49476
50182
  }
49477
50183
  );
49478
- 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
+ });
49479
50200
  });
49480
50201
  }
49481
50202
 
@@ -49496,7 +50217,7 @@ import os4 from "node:os";
49496
50217
  import path9 from "node:path";
49497
50218
  var MAX_CLI_STDIN_BYTES = 40 * 1024 * 1024;
49498
50219
  var MAX_AGENT_MESSAGE_BYTES = MAX_CLI_STDIN_BYTES + 64 * 1024;
49499
- var AGENT_SOCKET_PROTOCOL_VERSION = 4;
50220
+ var AGENT_SOCKET_PROTOCOL_VERSION = 5;
49500
50221
  var RUNTIME_VIEWER_OPEN_OPERATION = "viewer.open";
49501
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";
49502
50223
  var DEFAULT_CONNECT_TIMEOUT_MS = 2e3;
@@ -49661,6 +50382,27 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
49661
50382
  const input = parseOperationInput("comment.watch", request.input);
49662
50383
  return service.listCommentPostEvents(input.cursor, input.limit);
49663
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
+ }
49664
50406
  case "comment.create": {
49665
50407
  const input = parseOperationInput("comment.create", request.input);
49666
50408
  return await service.createCommentForReference(
@@ -49684,6 +50426,7 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
49684
50426
  body: input.edit.body,
49685
50427
  ...input.edit.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.edit.relatedCommitOid },
49686
50428
  ...input.edit.references === void 0 ? {} : { references: input.edit.references },
50429
+ ...input.edit.watchTask === void 0 ? {} : { watchTask: input.edit.watchTask },
49687
50430
  lastModifiedBy: "agent"
49688
50431
  });
49689
50432
  }
@@ -50766,7 +51509,7 @@ function writeJson(value) {
50766
51509
  `);
50767
51510
  }
50768
51511
  function structureAuthoringFeedback(structure) {
50769
- const layout = projectStructure(structure).diagnostics;
51512
+ const layout = projectTopologyStructure(structure).diagnostics;
50770
51513
  return { layout, warnings: structureAuthoringWarnings(layout) };
50771
51514
  }
50772
51515
  function writeJsonSequence(value) {
@@ -50973,12 +51716,12 @@ function staticDirectory() {
50973
51716
  ];
50974
51717
  return candidates.find((candidate) => existsSync6(candidate)) ?? candidates[0];
50975
51718
  }
50976
- function forkOpenWorker(reference, port) {
51719
+ function forkOpenWorker(reference, port, requestedPort) {
50977
51720
  const modulePath = process.argv[1];
50978
51721
  if (!modulePath) {
50979
51722
  throw new RvwError("PROCESS_FAILED", "rvw CLI\u306E\u5B9F\u884Cpath\u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002");
50980
51723
  }
50981
- const args = ["__open-worker", "--port", String(port)];
51724
+ const args = ["__open-worker", "--port", String(port), "--requested-port", String(requestedPort)];
50982
51725
  if (reference !== void 0) args.push("--reference", reference);
50983
51726
  return fork(path10.resolve(modulePath), args, {
50984
51727
  cwd: process.cwd(),
@@ -50988,10 +51731,12 @@ function forkOpenWorker(reference, port) {
50988
51731
  });
50989
51732
  }
50990
51733
  async function startBackgroundOpen(reference, port, dependencies = {}) {
51734
+ const startupPort = port ?? DEFAULT_VIEWER_PORT;
51735
+ const requestedPort = port ?? 0;
50991
51736
  const input = {
50992
51737
  ...reference === void 0 ? {} : { reference },
50993
51738
  cwd: process.cwd(),
50994
- requestedPort: port
51739
+ requestedPort
50995
51740
  };
50996
51741
  const launchBrowser = dependencies.launchBrowser ?? (async (url2) => await open_default(url2));
50997
51742
  let existing = null;
@@ -51011,7 +51756,7 @@ async function startBackgroundOpen(reference, port, dependencies = {}) {
51011
51756
  { details: existing.details }
51012
51757
  );
51013
51758
  }
51014
- const child = (dependencies.forkWorker ?? forkOpenWorker)(reference, port);
51759
+ const child = (dependencies.forkWorker ?? forkOpenWorker)(reference, startupPort, requestedPort);
51015
51760
  return await completeBackgroundOpen(child, launchBrowser);
51016
51761
  }
51017
51762
  async function sendOpenWorkerMessage(message) {
@@ -51204,6 +51949,8 @@ async function waitForWorkerParentDisconnect() {
51204
51949
  await new Promise((resolve) => process.once("disconnect", resolve));
51205
51950
  }
51206
51951
  async function runOpenServer(runtimeFactory, reference, port, openAutomatically, useAgentSocket, reuseExisting) {
51952
+ const startupPort = port ?? DEFAULT_VIEWER_PORT;
51953
+ const requestedPort = port ?? 0;
51207
51954
  let activeRuntime;
51208
51955
  let running;
51209
51956
  let agentSocket;
@@ -51212,7 +51959,7 @@ async function runOpenServer(runtimeFactory, reference, port, openAutomatically,
51212
51959
  const databaseFilePath = databasePathConfiguration().filePath;
51213
51960
  if (reuseExisting) {
51214
51961
  const startup = await acquireRuntimeOrReuseExisting(
51215
- runtimeViewerOpenInput(reference, process.cwd(), port),
51962
+ runtimeViewerOpenInput(reference, process.cwd(), requestedPort),
51216
51963
  databaseFilePath
51217
51964
  );
51218
51965
  if (startup.kind === "reused") {
@@ -51241,7 +51988,7 @@ async function runOpenServer(runtimeFactory, reference, port, openAutomatically,
51241
51988
  activeRuntime = runtimeFactory();
51242
51989
  const opened = await activeRuntime.service.openPullRequest(reference, process.cwd());
51243
51990
  running = await startServer(activeRuntime.service, {
51244
- port,
51991
+ port: startupPort,
51245
51992
  staticDirectory: staticDirectory(),
51246
51993
  autoCloseWhenNoViewers: openAutomatically
51247
51994
  });
@@ -51259,7 +52006,7 @@ async function runOpenServer(runtimeFactory, reference, port, openAutomatically,
51259
52006
  await closeOwnedRuntime(agentSocket, running, activeRuntime);
51260
52007
  }
51261
52008
  }
51262
- async function runOpenWorker(runtimeFactory, reference, port) {
52009
+ async function runOpenWorker(runtimeFactory, reference, port, requestedPort) {
51263
52010
  let activeRuntime;
51264
52011
  let running;
51265
52012
  let agentSocket;
@@ -51269,7 +52016,7 @@ async function runOpenWorker(runtimeFactory, reference, port) {
51269
52016
  }
51270
52017
  const databaseFilePath = databasePathConfiguration().filePath;
51271
52018
  const startup = await acquireRuntimeOrReuseExisting(
51272
- runtimeViewerOpenInput(reference, process.cwd(), port),
52019
+ runtimeViewerOpenInput(reference, process.cwd(), requestedPort),
51273
52020
  databaseFilePath
51274
52021
  );
51275
52022
  if (startup.kind === "reused") {
@@ -51367,6 +52114,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51367
52114
  "comment.create",
51368
52115
  "comment.list",
51369
52116
  "comment.watch",
52117
+ "comment.watchOwnership",
51370
52118
  "comment.read",
51371
52119
  "comment.reply",
51372
52120
  "comment.edit",
@@ -51377,6 +52125,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51377
52125
  "structure.list",
51378
52126
  "structure.read",
51379
52127
  "structure.preview",
52128
+ "structure.presentation",
51380
52129
  "structure.publish",
51381
52130
  "structure.update",
51382
52131
  "structure.delete",
@@ -51406,7 +52155,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51406
52155
  writeOutput(options, { ok: ok2, ...status }, formatAgentTransportStatus(status));
51407
52156
  if (!ok2) process.exitCode = 2;
51408
52157
  });
51409
- 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(
51410
52159
  async (reference, options) => {
51411
52160
  if (options.open && !options.foreground && useAgentSocket) {
51412
52161
  const url2 = await startBackgroundOpen(reference, options.port);
@@ -51424,9 +52173,9 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51424
52173
  );
51425
52174
  }
51426
52175
  );
51427
- 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) => {
51428
52177
  try {
51429
- await runOpenWorker(getRuntime, options.reference, options.port);
52178
+ await runOpenWorker(getRuntime, options.reference, options.port, options.requestedPort);
51430
52179
  } catch (error51) {
51431
52180
  const rvwError = asRvwError(error51);
51432
52181
  const sent = await sendOpenWorkerMessage({
@@ -51714,6 +52463,66 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51714
52463
  process.off("SIGTERM", stop);
51715
52464
  }
51716
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
+ });
51717
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 () => {
51718
52527
  const input = commentCreateInputSchema.parse(await readStdinJson());
51719
52528
  const request = {
@@ -51765,7 +52574,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51765
52574
  ...input.authorLabel === void 0 ? {} : { authorLabel: input.authorLabel },
51766
52575
  ...input.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.relatedCommitOid },
51767
52576
  ...input.references === void 0 ? {} : { references: input.references },
51768
- ...input.idempotencyKey === void 0 ? {} : { idempotencyKey: input.idempotencyKey }
52577
+ ...input.idempotencyKey === void 0 ? {} : { idempotencyKey: input.idempotencyKey },
52578
+ ...input.watchTask === void 0 ? {} : { watchTask: input.watchTask }
51769
52579
  };
51770
52580
  const post = await callService(
51771
52581
  "comment.reply",
@@ -51782,7 +52592,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
51782
52592
  const edit = {
51783
52593
  body: input.body,
51784
52594
  ...input.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.relatedCommitOid },
51785
- ...input.references === void 0 ? {} : { references: input.references }
52595
+ ...input.references === void 0 ? {} : { references: input.references },
52596
+ ...input.watchTask === void 0 ? {} : { watchTask: input.watchTask }
51786
52597
  };
51787
52598
  const post = await callService(
51788
52599
  "comment.edit",