@a9n-shoji/rvw 0.3.2 → 0.4.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.
package/dist/cli.mjs CHANGED
@@ -6582,11 +6582,11 @@ var $ZodCIDRv6 = /* @__PURE__ */ $constructor("$ZodCIDRv6", (inst, def) => {
6582
6582
  try {
6583
6583
  if (parts.length !== 2)
6584
6584
  throw new Error();
6585
- const [address, prefix2] = parts;
6586
- if (!prefix2)
6585
+ const [address, prefix3] = parts;
6586
+ if (!prefix3)
6587
6587
  throw new Error();
6588
- const prefixNum = Number(prefix2);
6589
- if (`${prefixNum}` !== prefix2)
6588
+ const prefixNum = Number(prefix3);
6589
+ if (`${prefixNum}` !== prefix3)
6590
6590
  throw new Error();
6591
6591
  if (prefixNum < 0 || prefixNum > 128)
6592
6592
  throw new Error();
@@ -14994,12 +14994,12 @@ function _includes(includes, params) {
14994
14994
  });
14995
14995
  }
14996
14996
  // @__NO_SIDE_EFFECTS__
14997
- function _startsWith(prefix2, params) {
14997
+ function _startsWith(prefix3, params) {
14998
14998
  return new $ZodCheckStartsWith({
14999
14999
  check: "string_format",
15000
15000
  format: "starts_with",
15001
15001
  ...normalizeParams(params),
15002
- prefix: prefix2
15002
+ prefix: prefix3
15003
15003
  });
15004
15004
  }
15005
15005
  // @__NO_SIDE_EFFECTS__
@@ -18514,6 +18514,19 @@ config(en_default());
18514
18514
 
18515
18515
  // src/infrastructure/db/database.ts
18516
18516
  import { createHash, randomUUID } from "node:crypto";
18517
+
18518
+ // src/domain/models.ts
18519
+ var STRUCTURE_NODE_NOTATIONS = [
18520
+ "plain",
18521
+ "class",
18522
+ "database",
18523
+ "interface",
18524
+ "component",
18525
+ "external",
18526
+ "concept"
18527
+ ];
18528
+
18529
+ // src/infrastructure/db/database.ts
18517
18530
  import {
18518
18531
  chmodSync,
18519
18532
  closeSync,
@@ -18668,17 +18681,34 @@ function parseCommentUri(uri) {
18668
18681
  return match2[1];
18669
18682
  }
18670
18683
 
18684
+ // src/domain/structure-uri.ts
18685
+ var prefix = "rvw://structure/";
18686
+ var structureIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
18687
+ function formatStructureUri(id) {
18688
+ return `${prefix}${id}`;
18689
+ }
18690
+ function parseStructureUri(uri) {
18691
+ if (!uri.startsWith(prefix)) {
18692
+ throw new RvwError("INVALID_INPUT", "structure URI\u304C\u4E0D\u6B63\u3067\u3059\u3002");
18693
+ }
18694
+ const id = uri.slice(prefix.length);
18695
+ if (!structureIdPattern.test(id)) {
18696
+ throw new RvwError("INVALID_INPUT", "structure URI\u304C\u4E0D\u6B63\u3067\u3059\u3002");
18697
+ }
18698
+ return id;
18699
+ }
18700
+
18671
18701
  // src/domain/walkthrough-uri.ts
18672
- var prefix = "rvw://walkthrough/";
18702
+ var prefix2 = "rvw://walkthrough/";
18673
18703
  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;
18674
18704
  function formatWalkthroughUri(id) {
18675
- return `${prefix}${id}`;
18705
+ return `${prefix2}${id}`;
18676
18706
  }
18677
18707
  function parseWalkthroughUri(uri) {
18678
- if (!uri.startsWith(prefix)) {
18708
+ if (!uri.startsWith(prefix2)) {
18679
18709
  throw new RvwError("INVALID_INPUT", "walkthrough URI\u304C\u4E0D\u6B63\u3067\u3059\u3002");
18680
18710
  }
18681
- const id = uri.slice(prefix.length);
18711
+ const id = uri.slice(prefix2.length);
18682
18712
  if (!walkthroughIdPattern.test(id)) {
18683
18713
  throw new RvwError("INVALID_INPUT", "walkthrough URI\u304C\u4E0D\u6B63\u3067\u3059\u3002");
18684
18714
  }
@@ -18746,6 +18776,37 @@ function stringRecordValue(row, key2) {
18746
18776
  throw new RvwError("DATABASE_ERROR", `DB\u5217 ${key2} \u304C\u4E0D\u6B63\u3067\u3059\u3002`, { cause: error51 });
18747
18777
  }
18748
18778
  }
18779
+ function isRecord(value) {
18780
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18781
+ }
18782
+ function isNullableString(value) {
18783
+ return value === null || typeof value === "string";
18784
+ }
18785
+ function isSourceAnchor(value) {
18786
+ return isRecord(value) && typeof value.path === "string" && (value.startLine === null || typeof value.startLine === "number") && (value.endLine === null || typeof value.endLine === "number");
18787
+ }
18788
+ function isStructureNode(value) {
18789
+ return isRecord(value) && typeof value.id === "string" && typeof value.label === "string" && isNullableString(value.description) && isNullableString(value.kind) && (value.notation === void 0 || STRUCTURE_NODE_NOTATIONS.some((notation) => notation === value.notation)) && (value.anchor === null || isSourceAnchor(value.anchor));
18790
+ }
18791
+ function isStructureEdge(value) {
18792
+ 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
+ }
18794
+ function structureGraphValue(row) {
18795
+ 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)) {
18799
+ throw new Error("invalid Structure graph");
18800
+ }
18801
+ return {
18802
+ originNodeId,
18803
+ nodes: value.nodes.map((node2) => ({ ...node2, notation: node2.notation ?? "plain" })),
18804
+ edges: value.edges
18805
+ };
18806
+ } catch (error51) {
18807
+ throw new RvwError("DATABASE_ERROR", "Structure graph_json\u304C\u4E0D\u6B63\u3067\u3059\u3002", { cause: error51 });
18808
+ }
18809
+ }
18749
18810
  function mapPullRequest(row) {
18750
18811
  return {
18751
18812
  id: stringValue(row, "id"),
@@ -19130,6 +19191,11 @@ var RvwDatabase = class {
19130
19191
  FROM walkthroughs
19131
19192
  JOIN page ON page.id = walkthroughs.pull_request_id
19132
19193
  GROUP BY walkthroughs.pull_request_id
19194
+ ), structure_counts AS (
19195
+ SELECT structures.pull_request_id, COUNT(*) AS structure_count
19196
+ FROM structures
19197
+ JOIN page ON page.id = structures.pull_request_id
19198
+ GROUP BY structures.pull_request_id
19133
19199
  )
19134
19200
  SELECT
19135
19201
  pr.id AS pull_request_id,
@@ -19143,10 +19209,12 @@ var RvwDatabase = class {
19143
19209
  pr.github_is_draft,
19144
19210
  COALESCE(comment_counts.unresolved_count, 0) AS unresolved_comment_count,
19145
19211
  COALESCE(comment_counts.resolved_count, 0) AS resolved_comment_count,
19146
- COALESCE(walkthrough_counts.walkthrough_count, 0) AS walkthrough_count
19212
+ COALESCE(walkthrough_counts.walkthrough_count, 0) AS walkthrough_count,
19213
+ COALESCE(structure_counts.structure_count, 0) AS structure_count
19147
19214
  FROM page AS pr
19148
19215
  LEFT JOIN comment_counts ON comment_counts.pull_request_id = pr.id
19149
19216
  LEFT JOIN walkthrough_counts ON walkthrough_counts.pull_request_id = pr.id
19217
+ LEFT JOIN structure_counts ON structure_counts.pull_request_id = pr.id
19150
19218
  ORDER BY pr.github_updated_at DESC, pr.id DESC`
19151
19219
  ).all(hideClosedOrMergedValue, limit, offset);
19152
19220
  const totalRow = this.database.prepare(
@@ -19166,7 +19234,8 @@ var RvwDatabase = class {
19166
19234
  githubIsDraft: nullableBoolean(row, "github_is_draft"),
19167
19235
  unresolvedCommentCount: numberValue(row, "unresolved_comment_count"),
19168
19236
  resolvedCommentCount: numberValue(row, "resolved_comment_count"),
19169
- walkthroughCount: numberValue(row, "walkthrough_count")
19237
+ walkthroughCount: numberValue(row, "walkthrough_count"),
19238
+ structureCount: numberValue(row, "structure_count")
19170
19239
  })),
19171
19240
  total: numberValue(totalRow, "total")
19172
19241
  };
@@ -19322,6 +19391,7 @@ var RvwDatabase = class {
19322
19391
  const walkthroughReferences = this.database.prepare(
19323
19392
  "SELECT count(*) AS count FROM walkthrough_references WHERE walkthrough_id IN (SELECT id FROM walkthroughs WHERE pull_request_id = ?)"
19324
19393
  ).get(pullRequestId);
19394
+ const structures = this.database.prepare("SELECT count(*) AS count FROM structures WHERE pull_request_id = ?").get(pullRequestId);
19325
19395
  return {
19326
19396
  comments: numberValue(comments, "count"),
19327
19397
  posts: numberValue(posts, "count"),
@@ -19329,12 +19399,234 @@ var RvwDatabase = class {
19329
19399
  targets: numberValue(targets, "count"),
19330
19400
  walkthroughs: numberValue(walkthroughs, "count"),
19331
19401
  walkthroughReferences: numberValue(walkthroughReferences, "count"),
19402
+ structures: numberValue(structures, "count"),
19332
19403
  gitRefs
19333
19404
  };
19334
19405
  }
19335
19406
  deletePullRequestHistory(pullRequestId) {
19336
19407
  this.database.prepare("DELETE FROM comments WHERE pull_request_id = ?").run(pullRequestId);
19337
19408
  this.database.prepare("DELETE FROM walkthroughs WHERE pull_request_id = ?").run(pullRequestId);
19409
+ this.database.prepare(
19410
+ `DELETE FROM structure_publish_idempotency
19411
+ WHERE structure_id IN (SELECT id FROM structures WHERE pull_request_id = ?)`
19412
+ ).run(pullRequestId);
19413
+ this.database.prepare("DELETE FROM structures WHERE pull_request_id = ?").run(pullRequestId);
19414
+ }
19415
+ mapStructure(row) {
19416
+ const id = stringValue(row, "id");
19417
+ return {
19418
+ id,
19419
+ ref: formatStructureUri(id),
19420
+ pullRequestId: stringValue(row, "pull_request_id"),
19421
+ sourceOid: stringValue(row, "source_oid"),
19422
+ title: stringValue(row, "title"),
19423
+ scope: stringValue(row, "scope"),
19424
+ ...structureGraphValue(row),
19425
+ createdAt: stringValue(row, "created_at"),
19426
+ updatedAt: stringValue(row, "updated_at")
19427
+ };
19428
+ }
19429
+ getStructure(id) {
19430
+ const row = this.database.prepare("SELECT * FROM structures WHERE id = ?").get(id);
19431
+ return row ? this.mapStructure(row) : null;
19432
+ }
19433
+ listStructures(pullRequestId) {
19434
+ return this.database.prepare(
19435
+ `SELECT id, pull_request_id, source_oid, title, scope, created_at, updated_at
19436
+ FROM structures
19437
+ WHERE pull_request_id = ?
19438
+ ORDER BY created_at DESC, id DESC`
19439
+ ).all(pullRequestId).map((row) => ({
19440
+ id: stringValue(row, "id"),
19441
+ ref: formatStructureUri(stringValue(row, "id")),
19442
+ pullRequestId: stringValue(row, "pull_request_id"),
19443
+ sourceOid: stringValue(row, "source_oid"),
19444
+ title: stringValue(row, "title"),
19445
+ scope: stringValue(row, "scope"),
19446
+ createdAt: stringValue(row, "created_at"),
19447
+ updatedAt: stringValue(row, "updated_at")
19448
+ }));
19449
+ }
19450
+ createStructure(input) {
19451
+ let structureId;
19452
+ const graphJson = JSON.stringify({
19453
+ originNodeId: input.originNodeId,
19454
+ nodes: input.nodes,
19455
+ edges: input.edges
19456
+ });
19457
+ this.immediateTransaction(() => {
19458
+ const keyHash = hashIdempotencyKey(input.idempotencyKey);
19459
+ const existingRow = this.database.prepare("SELECT * FROM structure_publish_idempotency WHERE key_hash = ?").get(keyHash);
19460
+ if (existingRow) {
19461
+ if (stringValue(existingRow, "request_hash") !== input.idempotencyRequestHash) {
19462
+ throw new RvwError(
19463
+ "IDEMPOTENCY_CONFLICT",
19464
+ "\u540C\u3058idempotencyKey\u304C\u5225\u306EStructure publish\u306B\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059\u3002"
19465
+ );
19466
+ }
19467
+ const existingId = stringValue(existingRow, "structure_id");
19468
+ if (!this.getStructure(existingId)) {
19469
+ throw new RvwError(
19470
+ "IDEMPOTENCY_RESULT_DELETED",
19471
+ "\u3053\u306EidempotencyKey\u3067\u4F5C\u6210\u3057\u305FStructure\u306F\u65E2\u306B\u524A\u9664\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
19472
+ { details: { structureId: existingId } }
19473
+ );
19474
+ }
19475
+ structureId = existingId;
19476
+ return;
19477
+ }
19478
+ const id = randomUUID();
19479
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19480
+ this.database.prepare(
19481
+ `INSERT INTO structures(
19482
+ id, pull_request_id, source_oid, title, scope, graph_json, created_at, updated_at
19483
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
19484
+ ).run(
19485
+ id,
19486
+ input.pullRequestId,
19487
+ input.sourceOid,
19488
+ input.title,
19489
+ input.scope,
19490
+ graphJson,
19491
+ now,
19492
+ now
19493
+ );
19494
+ this.database.prepare(
19495
+ `INSERT INTO structure_publish_idempotency(
19496
+ key_hash, request_hash, structure_id, created_at
19497
+ ) VALUES (?, ?, ?, ?)`
19498
+ ).run(keyHash, input.idempotencyRequestHash, id, now);
19499
+ this.incrementChangeSequence();
19500
+ structureId = id;
19501
+ });
19502
+ if (!structureId) {
19503
+ throw new RvwError("DATABASE_ERROR", "\u4FDD\u5B58\u3057\u305FStructure ID\u3092\u78BA\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002");
19504
+ }
19505
+ const structure = this.getStructure(structureId);
19506
+ if (!structure) throw new RvwError("DATABASE_ERROR", "\u4FDD\u5B58\u3057\u305FStructure\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
19507
+ return structure;
19508
+ }
19509
+ updateStructure(id, expectedUpdatedAt2, input) {
19510
+ const currentUpdatedAt = Date.parse(expectedUpdatedAt2);
19511
+ const observedNow = Date.now();
19512
+ const now = new Date(
19513
+ Number.isNaN(currentUpdatedAt) ? observedNow : Math.max(observedNow, currentUpdatedAt + 1)
19514
+ ).toISOString();
19515
+ const graphJson = JSON.stringify({
19516
+ originNodeId: input.originNodeId,
19517
+ nodes: input.nodes,
19518
+ edges: input.edges
19519
+ });
19520
+ this.immediateTransaction(() => {
19521
+ const current = this.getStructure(id);
19522
+ if (!current) {
19523
+ throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
19524
+ }
19525
+ if (current.updatedAt !== expectedUpdatedAt2) {
19526
+ throw new RvwError(
19527
+ "STRUCTURE_CONFLICT",
19528
+ "Structure\u304C\u53D6\u5F97\u5F8C\u306B\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u73FE\u5728\u5024\u3092\u8AAD\u307F\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
19529
+ {
19530
+ status: 409,
19531
+ details: { expectedUpdatedAt: expectedUpdatedAt2, currentUpdatedAt: current.updatedAt }
19532
+ }
19533
+ );
19534
+ }
19535
+ const retiredNodeIds = new Set(
19536
+ this.database.prepare("SELECT node_id FROM structure_retired_node_ids WHERE structure_id = ?").all(id).map((row) => stringValue(row, "node_id"))
19537
+ );
19538
+ const retiredEdgeIds = new Set(
19539
+ this.database.prepare("SELECT edge_id FROM structure_retired_edge_ids WHERE structure_id = ?").all(id).map((row) => stringValue(row, "edge_id"))
19540
+ );
19541
+ const reusedNode = input.nodes.find((node2) => retiredNodeIds.has(node2.id));
19542
+ if (reusedNode) {
19543
+ throw new RvwError(
19544
+ "INVALID_INPUT",
19545
+ `\u524A\u9664\u6E08\u307F\u306EStructure Node ID\u306F\u518D\u5229\u7528\u3067\u304D\u307E\u305B\u3093: ${reusedNode.id}`
19546
+ );
19547
+ }
19548
+ const reusedEdge = input.edges.find((edge) => retiredEdgeIds.has(edge.id));
19549
+ if (reusedEdge) {
19550
+ throw new RvwError(
19551
+ "INVALID_INPUT",
19552
+ `\u524A\u9664\u6E08\u307F\u306EStructure Edge ID\u306F\u518D\u5229\u7528\u3067\u304D\u307E\u305B\u3093: ${reusedEdge.id}`
19553
+ );
19554
+ }
19555
+ const nextNodeIds = new Set(input.nodes.map((node2) => node2.id));
19556
+ const nextEdgeIds = new Set(input.edges.map((edge) => edge.id));
19557
+ const retiredAtThisUpdate = current.nodes.map((node2) => node2.id).filter((nodeId) => !nextNodeIds.has(nodeId));
19558
+ const retiredEdgesAtThisUpdate = current.edges.map((edge) => edge.id).filter((edgeId) => !nextEdgeIds.has(edgeId));
19559
+ const result = this.database.prepare(
19560
+ `UPDATE structures
19561
+ SET source_oid = ?, title = ?, scope = ?, graph_json = ?, updated_at = ?
19562
+ WHERE id = ? AND updated_at = ?`
19563
+ ).run(input.sourceOid, input.title, input.scope, graphJson, now, id, expectedUpdatedAt2);
19564
+ if (Number(result.changes) === 0) {
19565
+ throw new RvwError(
19566
+ "STRUCTURE_CONFLICT",
19567
+ "Structure\u304C\u53D6\u5F97\u5F8C\u306B\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u73FE\u5728\u5024\u3092\u8AAD\u307F\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
19568
+ {
19569
+ status: 409,
19570
+ details: { expectedUpdatedAt: expectedUpdatedAt2, currentUpdatedAt: current.updatedAt }
19571
+ }
19572
+ );
19573
+ }
19574
+ const retireNode = this.database.prepare(
19575
+ "INSERT OR IGNORE INTO structure_retired_node_ids(structure_id, node_id, retired_at) VALUES (?, ?, ?)"
19576
+ );
19577
+ for (const nodeId of retiredAtThisUpdate) retireNode.run(id, nodeId, now);
19578
+ const retireEdge = this.database.prepare(
19579
+ "INSERT OR IGNORE INTO structure_retired_edge_ids(structure_id, edge_id, retired_at) VALUES (?, ?, ?)"
19580
+ );
19581
+ for (const edgeId of retiredEdgesAtThisUpdate) retireEdge.run(id, edgeId, now);
19582
+ this.incrementChangeSequence();
19583
+ });
19584
+ const structure = this.getStructure(id);
19585
+ if (!structure) throw new RvwError("DATABASE_ERROR", "\u66F4\u65B0\u3057\u305FStructure\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
19586
+ return structure;
19587
+ }
19588
+ getStructureDeleteCounts(id) {
19589
+ const structure = this.getStructure(id);
19590
+ if (!structure) throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
19591
+ return {
19592
+ nodes: structure.nodes.length,
19593
+ edges: structure.edges.length,
19594
+ anchors: structure.nodes.filter((node2) => node2.anchor !== null).length + structure.edges.reduce((count, edge) => count + edge.anchors.length, 0)
19595
+ };
19596
+ }
19597
+ deleteStructure(id, expectedUpdatedAt2) {
19598
+ return this.immediateTransaction(() => {
19599
+ const structure = this.getStructure(id);
19600
+ if (!structure) {
19601
+ throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
19602
+ }
19603
+ if (structure.updatedAt !== expectedUpdatedAt2) {
19604
+ throw new RvwError(
19605
+ "STRUCTURE_CONFLICT",
19606
+ "Structure\u304Cpreview\u5F8C\u306B\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u73FE\u5728\u5024\u3092\u8AAD\u307F\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
19607
+ {
19608
+ status: 409,
19609
+ details: { expectedUpdatedAt: expectedUpdatedAt2, currentUpdatedAt: structure.updatedAt }
19610
+ }
19611
+ );
19612
+ }
19613
+ const counts = this.getStructureDeleteCounts(id);
19614
+ const result = this.database.prepare("DELETE FROM structures WHERE id = ? AND updated_at = ?").run(id, expectedUpdatedAt2);
19615
+ if (Number(result.changes) === 0) {
19616
+ throw new RvwError(
19617
+ "STRUCTURE_CONFLICT",
19618
+ "Structure\u304Cpreview\u5F8C\u306B\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u73FE\u5728\u5024\u3092\u8AAD\u307F\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
19619
+ { status: 409, details: { expectedUpdatedAt: expectedUpdatedAt2 } }
19620
+ );
19621
+ }
19622
+ this.incrementChangeSequence();
19623
+ return {
19624
+ id: structure.id,
19625
+ ref: structure.ref,
19626
+ pullRequestId: structure.pullRequestId,
19627
+ counts
19628
+ };
19629
+ });
19338
19630
  }
19339
19631
  codeReferenceStorage(kind) {
19340
19632
  return kind === "comment-post" ? { table: "comment_post_references", ownerColumn: "post_id" } : { table: "walkthrough_references", ownerColumn: "walkthrough_id" };
@@ -19895,7 +20187,7 @@ import path5 from "node:path";
19895
20187
  import { TextDecoder as TextDecoder2 } from "node:util";
19896
20188
 
19897
20189
  // src/shared/constants.ts
19898
- var APP_VERSION = "0.3.2";
20190
+ var APP_VERSION = "0.4.0";
19899
20191
  var PROTOCOL_VERSION = 4;
19900
20192
  var VIEWER_ID_HEADER = "x-rvw-viewer-id";
19901
20193
  var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
@@ -19924,6 +20216,18 @@ var MAX_CODE_REFERENCE_PATH_CHARACTERS = 4096;
19924
20216
  var MAX_CODE_REFERENCE_DESCRIPTION_CHARACTERS = 1e3;
19925
20217
  var MAX_WALKTHROUGH_BODY_BYTES = 256 * 1024;
19926
20218
  var MAX_WALKTHROUGH_TITLE_CHARACTERS = 200;
20219
+ var MAX_STRUCTURE_PAYLOAD_BYTES = 2 * 1024 * 1024;
20220
+ var MAX_STRUCTURE_TITLE_CHARACTERS = 200;
20221
+ var MAX_STRUCTURE_SCOPE_CHARACTERS = 4e3;
20222
+ var MAX_STRUCTURE_NODES = 50;
20223
+ var MAX_STRUCTURE_EDGES = 200;
20224
+ var MAX_STRUCTURE_EDGE_ANCHORS = 20;
20225
+ var MAX_STRUCTURE_SOURCE_ANCHORS = 400;
20226
+ var MAX_STRUCTURE_ID_CHARACTERS = 64;
20227
+ var STRUCTURE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
20228
+ var MAX_STRUCTURE_LABEL_CHARACTERS = 200;
20229
+ var MAX_STRUCTURE_DESCRIPTION_CHARACTERS = 2e3;
20230
+ var MAX_STRUCTURE_KIND_CHARACTERS = 100;
19927
20231
  var MAX_SEARCH_QUERY_BYTES = 1024;
19928
20232
  var MAX_SEARCH_RESULTS = 500;
19929
20233
  var MAX_SEARCH_STDOUT_BYTES = 8 * 1024 * 1024;
@@ -20232,10 +20536,10 @@ var GitClient = class {
20232
20536
  }
20233
20537
  async ensurePullRequestObjects(input) {
20234
20538
  const operationId = randomUUID2();
20235
- const prefix2 = `refs/rvw/tmp/${operationId}`;
20539
+ const prefix3 = `refs/rvw/tmp/${operationId}`;
20236
20540
  try {
20237
20541
  if (!await this.hasObject(input.cwd, input.headOid)) {
20238
- const temporaryRef = `${prefix2}/head`;
20542
+ const temporaryRef = `${prefix3}/head`;
20239
20543
  await runProcess(
20240
20544
  "git",
20241
20545
  [
@@ -20260,7 +20564,7 @@ var GitClient = class {
20260
20564
  }
20261
20565
  }
20262
20566
  if (!await this.hasObject(input.cwd, input.baseOid)) {
20263
- const temporaryRef = `${prefix2}/base-tip`;
20567
+ const temporaryRef = `${prefix3}/base-tip`;
20264
20568
  await runProcess(
20265
20569
  "git",
20266
20570
  [
@@ -20285,7 +20589,7 @@ var GitClient = class {
20285
20589
  }
20286
20590
  }
20287
20591
  } finally {
20288
- await this.deleteRefsByPrefix(input.cwd, `${prefix2}/`).catch(() => void 0);
20592
+ await this.deleteRefsByPrefix(input.cwd, `${prefix3}/`).catch(() => void 0);
20289
20593
  }
20290
20594
  }
20291
20595
  async mergeBase(cwd, baseOid, headOid) {
@@ -20350,14 +20654,14 @@ var GitClient = class {
20350
20654
  return false;
20351
20655
  }
20352
20656
  }
20353
- async listRefsByPrefix(cwd, prefix2) {
20354
- const output = await runProcess("git", ["for-each-ref", "--format=%(refname)%00", prefix2], {
20657
+ async listRefsByPrefix(cwd, prefix3) {
20658
+ const output = await runProcess("git", ["for-each-ref", "--format=%(refname)%00", prefix3], {
20355
20659
  cwd
20356
20660
  });
20357
20661
  return output.stdout.toString("utf8").split("\0").map((value) => value.trim()).filter(Boolean);
20358
20662
  }
20359
- async deleteRefsByPrefix(cwd, prefix2) {
20360
- const refs = await this.listRefsByPrefix(cwd, prefix2);
20663
+ async deleteRefsByPrefix(cwd, prefix3) {
20664
+ const refs = await this.listRefsByPrefix(cwd, prefix3);
20361
20665
  if (refs.length === 0) return 0;
20362
20666
  const input = ["start", ...refs.map((ref) => `delete ${ref}`), "prepare", "commit", ""].join(
20363
20667
  "\n"
@@ -20366,8 +20670,8 @@ var GitClient = class {
20366
20670
  return refs.length;
20367
20671
  }
20368
20672
  async replacePullRequestRefsForReset(cwd, number5, headOid) {
20369
- const prefix2 = `refs/rvw/pr/${number5}/`;
20370
- const existing = await this.listRefsByPrefix(cwd, prefix2);
20673
+ const prefix3 = `refs/rvw/pr/${number5}/`;
20674
+ const existing = await this.listRefsByPrefix(cwd, prefix3);
20371
20675
  const ref = this.commitRef(number5, headOid);
20372
20676
  const commands = ["start"];
20373
20677
  for (const ref2 of existing) {
@@ -23204,14 +23508,14 @@ function factorySpace(effects, ok2, type, max) {
23204
23508
  function start(code) {
23205
23509
  if (markdownSpace(code)) {
23206
23510
  effects.enter(type);
23207
- return prefix2(code);
23511
+ return prefix3(code);
23208
23512
  }
23209
23513
  return ok2(code);
23210
23514
  }
23211
- function prefix2(code) {
23515
+ function prefix3(code) {
23212
23516
  if (markdownSpace(code) && size++ < limit) {
23213
23517
  effects.consume(code);
23214
- return prefix2;
23518
+ return prefix3;
23215
23519
  }
23216
23520
  effects.exit(type);
23217
23521
  return ok2(code);
@@ -28162,10 +28466,17 @@ function placeMutableDocumentComment(anchor, currentText) {
28162
28466
  return range ? { outdated: false, range } : { outdated: true, range: null };
28163
28467
  }
28164
28468
 
28165
- // src/domain/walkthrough-reference.ts
28166
- function walkthroughReferenceFingerprint(sourceOid, reference) {
28469
+ // src/domain/source-reference.ts
28470
+ function sourceAnchorFingerprint(sourceOid, reference) {
28167
28471
  return JSON.stringify([sourceOid, reference.path, reference.startLine, reference.endLine]);
28168
28472
  }
28473
+ function structureSourceAnchor(structure, locator) {
28474
+ if (locator.kind === "node") {
28475
+ return structure.nodes.find((node2) => node2.id === locator.nodeId)?.anchor ?? null;
28476
+ }
28477
+ const edge = structure.edges.find((candidate) => candidate.id === locator.edgeId);
28478
+ return edge?.anchors[locator.anchorIndex] ?? null;
28479
+ }
28169
28480
 
28170
28481
  // src/domain/source-excerpt.ts
28171
28482
  var SOURCE_EXCERPT_CONTEXT_LINES = 20;
@@ -34510,7 +34821,7 @@ var LIMITED_QUIRKS_WITH_SYSTEM_ID_PUBLIC_ID_PREFIXES = [
34510
34821
  "-//w3c//dtd html 4.01 transitional//"
34511
34822
  ];
34512
34823
  function hasPrefix(publicId, prefixes) {
34513
- return prefixes.some((prefix2) => publicId.startsWith(prefix2));
34824
+ return prefixes.some((prefix3) => publicId.startsWith(prefix3));
34514
34825
  }
34515
34826
  function isConforming(token) {
34516
34827
  return token.name === VALID_DOCTYPE_NAME && token.publicId === null && (token.systemId === null || token.systemId === VALID_SYSTEM_ID);
@@ -42194,6 +42505,296 @@ var RvwService = class {
42194
42505
  this.getPullRequest(pullRequestId);
42195
42506
  return this.database.listWalkthroughs(pullRequestId);
42196
42507
  }
42508
+ listStructures(pullRequestId) {
42509
+ this.getPullRequest(pullRequestId);
42510
+ return this.database.listStructures(pullRequestId);
42511
+ }
42512
+ listStructuresByReference(reference) {
42513
+ const pullRequest = this.resolveStoredPullRequest(reference);
42514
+ return { pullRequest, structures: this.database.listStructures(pullRequest.id) };
42515
+ }
42516
+ getStructure(pullRequestId, structureId) {
42517
+ this.getPullRequest(pullRequestId);
42518
+ const structure = this.database.getStructure(structureId);
42519
+ if (!structure || structure.pullRequestId !== pullRequestId) {
42520
+ throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
42521
+ }
42522
+ return structure;
42523
+ }
42524
+ getStructureByUri(uri) {
42525
+ const structure = this.database.getStructure(parseStructureUri(uri));
42526
+ if (!structure) {
42527
+ throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
42528
+ }
42529
+ return {
42530
+ pullRequest: this.getPullRequest(structure.pullRequestId),
42531
+ structure
42532
+ };
42533
+ }
42534
+ async validateSourceAnchor(pullRequest, sourceOid, anchor, subject, documents) {
42535
+ assertCodeReferencePath(anchor.path);
42536
+ const startLine = anchor.startLine ?? null;
42537
+ const endLine = anchor.endLine ?? null;
42538
+ assertLinePair(startLine, endLine);
42539
+ let contentPromise = documents.get(anchor.path);
42540
+ if (!contentPromise) {
42541
+ contentPromise = this.getDocument({
42542
+ kind: "repository-file",
42543
+ pullRequestId: pullRequest.id,
42544
+ sourceOid,
42545
+ path: anchor.path
42546
+ });
42547
+ documents.set(anchor.path, contentPromise);
42548
+ }
42549
+ const content3 = await contentPromise;
42550
+ if (content3.availability !== "available") {
42551
+ throw new RvwError("INVALID_INPUT", `${subject}\u306Esource\u3092\u8868\u793A\u3067\u304D\u307E\u305B\u3093: ${anchor.path}`);
42552
+ }
42553
+ this.validateLineRange(content3.text ?? "", startLine, endLine, subject);
42554
+ return { path: anchor.path, startLine, endLine };
42555
+ }
42556
+ assertStructureText(value, maximum, subject) {
42557
+ const normalized = value.trim();
42558
+ if (normalized.length === 0 || value.length > maximum) {
42559
+ throw new RvwError("INVALID_INPUT", `${subject}\u306F1\u301C${maximum}\u6587\u5B57\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
42560
+ }
42561
+ return normalized;
42562
+ }
42563
+ assertStructureId(value, subject) {
42564
+ if (!STRUCTURE_ID_PATTERN.test(value)) {
42565
+ throw new RvwError(
42566
+ "INVALID_INPUT",
42567
+ `${subject}\u306F\u82F1\u5B57\u3067\u59CB\u307E\u308B\u82F1\u6570\u5B57\u30FB_\u30FB-\u306E1\u301C${MAX_STRUCTURE_ID_CHARACTERS}\u6587\u5B57\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42568
+ );
42569
+ }
42570
+ return value;
42571
+ }
42572
+ normalizeOptionalStructureText(value, maximum, subject) {
42573
+ if (value === null || value === void 0) return null;
42574
+ if (value.length > maximum) {
42575
+ throw new RvwError("INVALID_INPUT", `${subject}\u304C\u9577\u3059\u304E\u307E\u3059\u3002`);
42576
+ }
42577
+ return value.trim() || null;
42578
+ }
42579
+ async validateStructureContent(pullRequest, input) {
42580
+ await this.assertCommitAvailable(pullRequest, input.sourceOid);
42581
+ const title = this.assertStructureText(
42582
+ input.title,
42583
+ MAX_STRUCTURE_TITLE_CHARACTERS,
42584
+ "Structure title"
42585
+ );
42586
+ const scope = this.assertStructureText(
42587
+ input.scope,
42588
+ MAX_STRUCTURE_SCOPE_CHARACTERS,
42589
+ "Structure scope"
42590
+ );
42591
+ if (input.nodes.length < 1 || input.nodes.length > MAX_STRUCTURE_NODES) {
42592
+ throw new RvwError(
42593
+ "INVALID_INPUT",
42594
+ `Structure Node\u306F1\u301C${MAX_STRUCTURE_NODES}\u4EF6\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42595
+ );
42596
+ }
42597
+ if (input.edges.length > MAX_STRUCTURE_EDGES) {
42598
+ throw new RvwError(
42599
+ "INVALID_INPUT",
42600
+ `Structure Edge\u306F${MAX_STRUCTURE_EDGES}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42601
+ );
42602
+ }
42603
+ const documents = /* @__PURE__ */ new Map();
42604
+ const nodeIds = /* @__PURE__ */ new Set();
42605
+ const nodes = [];
42606
+ for (const node2 of input.nodes) {
42607
+ const id = this.assertStructureId(node2.id, "Structure Node ID");
42608
+ if (nodeIds.has(id)) {
42609
+ throw new RvwError("INVALID_INPUT", `Structure Node ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id}`);
42610
+ }
42611
+ nodeIds.add(id);
42612
+ const label = this.assertStructureText(
42613
+ node2.label,
42614
+ MAX_STRUCTURE_LABEL_CHARACTERS,
42615
+ `Structure Node ${id} label`
42616
+ );
42617
+ const notation = node2.notation ?? "plain";
42618
+ if (!STRUCTURE_NODE_NOTATIONS.includes(notation)) {
42619
+ throw new RvwError("INVALID_INPUT", `Structure Node ${id} notation\u304C\u4E0D\u6B63\u3067\u3059\u3002`);
42620
+ }
42621
+ nodes.push({
42622
+ id,
42623
+ label,
42624
+ description: this.normalizeOptionalStructureText(
42625
+ node2.description,
42626
+ MAX_STRUCTURE_DESCRIPTION_CHARACTERS,
42627
+ `Structure Node ${id} description`
42628
+ ),
42629
+ kind: this.normalizeOptionalStructureText(
42630
+ node2.kind,
42631
+ MAX_STRUCTURE_KIND_CHARACTERS,
42632
+ `Structure Node ${id} kind`
42633
+ ),
42634
+ notation,
42635
+ anchor: node2.anchor ? await this.validateSourceAnchor(
42636
+ pullRequest,
42637
+ input.sourceOid,
42638
+ node2.anchor,
42639
+ `Structure Node ${id}`,
42640
+ documents
42641
+ ) : null
42642
+ });
42643
+ }
42644
+ const edgeIds = /* @__PURE__ */ new Set();
42645
+ const edges = [];
42646
+ for (const edge of input.edges) {
42647
+ const id = this.assertStructureId(edge.id, "Structure Edge ID");
42648
+ if (edgeIds.has(id)) {
42649
+ throw new RvwError("INVALID_INPUT", `Structure Edge ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id}`);
42650
+ }
42651
+ edgeIds.add(id);
42652
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
42653
+ throw new RvwError(
42654
+ "INVALID_INPUT",
42655
+ `Structure Edge ${id} \u306Eendpoint\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${edge.from} \u2192 ${edge.to}`
42656
+ );
42657
+ }
42658
+ if (typeof edge.directed !== "boolean") {
42659
+ throw new RvwError("INVALID_INPUT", `Structure Edge ${id} \u306Edirected\u304C\u5FC5\u8981\u3067\u3059\u3002`);
42660
+ }
42661
+ const anchors = edge.anchors ?? [];
42662
+ if (anchors.length > MAX_STRUCTURE_EDGE_ANCHORS) {
42663
+ throw new RvwError(
42664
+ "INVALID_INPUT",
42665
+ `Structure Edge ${id} \u306Eanchor\u306F${MAX_STRUCTURE_EDGE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42666
+ );
42667
+ }
42668
+ edges.push({
42669
+ id,
42670
+ from: edge.from,
42671
+ to: edge.to,
42672
+ label: this.assertStructureText(
42673
+ edge.label,
42674
+ MAX_STRUCTURE_LABEL_CHARACTERS,
42675
+ `Structure Edge ${id} label`
42676
+ ),
42677
+ directed: edge.directed,
42678
+ anchors: await Promise.all(
42679
+ anchors.map(
42680
+ (anchor, index2) => this.validateSourceAnchor(
42681
+ pullRequest,
42682
+ input.sourceOid,
42683
+ anchor,
42684
+ `Structure Edge ${id} anchor ${index2 + 1}`,
42685
+ documents
42686
+ )
42687
+ )
42688
+ )
42689
+ });
42690
+ }
42691
+ if (typeof input.originNodeId !== "string") {
42692
+ throw new RvwError("INVALID_INPUT", "originNodeId\u304C\u5FC5\u8981\u3067\u3059\u3002");
42693
+ }
42694
+ const originNodeId = this.assertStructureId(input.originNodeId, "Structure originNodeId");
42695
+ if (!nodeIds.has(originNodeId)) {
42696
+ throw new RvwError("INVALID_INPUT", `originNodeId Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${originNodeId}`);
42697
+ }
42698
+ const originNode = nodes.find((node2) => node2.id === originNodeId);
42699
+ if (originNode.anchor === null) {
42700
+ throw new RvwError("INVALID_INPUT", "Structure origin Node\u306B\u306Fsource anchor\u304C\u5FC5\u8981\u3067\u3059\u3002");
42701
+ }
42702
+ const neighbors = new Map(nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
42703
+ for (const edge of edges) {
42704
+ neighbors.get(edge.from).add(edge.to);
42705
+ neighbors.get(edge.to).add(edge.from);
42706
+ }
42707
+ const reached = /* @__PURE__ */ new Set([originNodeId]);
42708
+ const queue = [originNodeId];
42709
+ while (queue.length > 0) {
42710
+ const current = queue.shift();
42711
+ for (const neighbor of neighbors.get(current) ?? []) {
42712
+ if (reached.has(neighbor)) continue;
42713
+ reached.add(neighbor);
42714
+ queue.push(neighbor);
42715
+ }
42716
+ }
42717
+ const disconnected = nodes.filter((node2) => !reached.has(node2.id)).map((node2) => node2.id);
42718
+ if (disconnected.length > 0) {
42719
+ throw new RvwError(
42720
+ "INVALID_INPUT",
42721
+ `Structure\u306E\u5168Node\u3092origin\u304B\u3089\u8FBF\u308C\u308B\u95A2\u4FC2graph\u306B\u3057\u3066\u304F\u3060\u3055\u3044: ${disconnected.join(", ")}`
42722
+ );
42723
+ }
42724
+ const anchorCount = nodes.filter((node2) => node2.anchor !== null).length + edges.reduce((count, edge) => count + edge.anchors.length, 0);
42725
+ if (anchorCount < 1) {
42726
+ throw new RvwError(
42727
+ "INVALID_INPUT",
42728
+ "Structure\u306B\u306Fsource anchor\u3092\u5C11\u306A\u304F\u3068\u30821\u4EF6\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
42729
+ );
42730
+ }
42731
+ if (anchorCount > MAX_STRUCTURE_SOURCE_ANCHORS) {
42732
+ throw new RvwError(
42733
+ "INVALID_INPUT",
42734
+ `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42735
+ );
42736
+ }
42737
+ const graph = { originNodeId, nodes, edges };
42738
+ if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
42739
+ throw new RvwError(
42740
+ "INVALID_INPUT",
42741
+ `Structure payload\u306F${MAX_STRUCTURE_PAYLOAD_BYTES} UTF-8 bytes\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42742
+ );
42743
+ }
42744
+ return { sourceOid: input.sourceOid, title, scope, ...graph };
42745
+ }
42746
+ async publishStructure(input) {
42747
+ if (typeof input.idempotencyKey !== "string") {
42748
+ throw new RvwError("INVALID_INPUT", "idempotencyKey\u304C\u5FC5\u8981\u3067\u3059\u3002");
42749
+ }
42750
+ assertIdempotencyKey(input.idempotencyKey);
42751
+ const pullRequest = this.resolveStoredPullRequest(input.pullRequest);
42752
+ const content3 = await this.validateStructureContent(pullRequest, input);
42753
+ return await this.writeWithRetainedCommit(
42754
+ pullRequest,
42755
+ content3.sourceOid,
42756
+ "Structure",
42757
+ () => this.database.createStructure({
42758
+ pullRequestId: pullRequest.id,
42759
+ ...content3,
42760
+ idempotencyKey: input.idempotencyKey,
42761
+ idempotencyRequestHash: idempotencyRequestHash({
42762
+ operation: "structure.publish",
42763
+ pullRequestId: pullRequest.id,
42764
+ content: content3
42765
+ })
42766
+ })
42767
+ );
42768
+ }
42769
+ async updateStructure(uri, input) {
42770
+ const { pullRequest, structure } = this.getStructureByUri(uri);
42771
+ if (typeof input.expectedUpdatedAt !== "string" || input.expectedUpdatedAt.length === 0) {
42772
+ throw new RvwError("INVALID_INPUT", "expectedUpdatedAt\u304C\u5FC5\u8981\u3067\u3059\u3002");
42773
+ }
42774
+ const content3 = await this.validateStructureContent(pullRequest, input);
42775
+ return await this.writeWithRetainedCommit(
42776
+ pullRequest,
42777
+ content3.sourceOid,
42778
+ "Structure",
42779
+ () => this.database.updateStructure(structure.id, input.expectedUpdatedAt, content3)
42780
+ );
42781
+ }
42782
+ getStructureDeletePreview(uri) {
42783
+ const { structure } = this.getStructureByUri(uri);
42784
+ return {
42785
+ structure,
42786
+ counts: this.database.getStructureDeleteCounts(structure.id),
42787
+ confirmationRequired: true
42788
+ };
42789
+ }
42790
+ deleteStructureByUri(uri, expectedUpdatedAt2) {
42791
+ const { structure } = this.getStructureByUri(uri);
42792
+ return this.database.deleteStructure(structure.id, expectedUpdatedAt2);
42793
+ }
42794
+ deleteStructure(pullRequestId, structureId, expectedUpdatedAt2) {
42795
+ this.getStructure(pullRequestId, structureId);
42796
+ return this.database.deleteStructure(structureId, expectedUpdatedAt2);
42797
+ }
42197
42798
  getWalkthrough(pullRequestId, walkthroughId) {
42198
42799
  this.getPullRequest(pullRequestId);
42199
42800
  const walkthrough = this.database.getWalkthrough(walkthroughId);
@@ -42202,7 +42803,7 @@ var RvwService = class {
42202
42803
  }
42203
42804
  return walkthrough;
42204
42805
  }
42205
- async walkthroughReferenceFallbackTarget(pullRequest, sourceOid, filePath) {
42806
+ async sourceReferenceFallbackTarget(pullRequest, sourceOid, filePath) {
42206
42807
  const diffBaseOid = await this.git.firstParent(pullRequest.localRepositoryPath, sourceOid);
42207
42808
  if (!diffBaseOid) {
42208
42809
  return {
@@ -42233,22 +42834,47 @@ var RvwService = class {
42233
42834
  status: 404
42234
42835
  });
42235
42836
  }
42236
- const referenceFingerprint = walkthroughReferenceFingerprint(walkthrough.sourceOid, reference);
42837
+ const referenceFingerprint = sourceAnchorFingerprint(walkthrough.sourceOid, reference);
42838
+ return await this.resolveSourceAnchor(
42839
+ pullRequest,
42840
+ walkthrough.sourceOid,
42841
+ reference,
42842
+ referenceFingerprint
42843
+ );
42844
+ }
42845
+ async resolveStructureSource(pullRequestId, structureId, locator) {
42846
+ const pullRequest = this.getPullRequest(pullRequestId);
42847
+ const structure = this.getStructure(pullRequestId, structureId);
42848
+ const anchor = structureSourceAnchor(structure, locator);
42849
+ if (!anchor) {
42850
+ throw new RvwError("NOT_FOUND", "Structure\u306E\u53C2\u7167\u5143claim\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", {
42851
+ status: 404
42852
+ });
42853
+ }
42854
+ const resolution = await this.resolveSourceAnchor(
42855
+ pullRequest,
42856
+ structure.sourceOid,
42857
+ anchor,
42858
+ sourceAnchorFingerprint(structure.sourceOid, anchor)
42859
+ );
42860
+ return { ...resolution, resolvedAnchor: anchor };
42861
+ }
42862
+ async resolveSourceAnchor(pullRequest, sourceOid, reference, referenceFingerprint) {
42237
42863
  const sourceDocument = await this.getDocument({
42238
42864
  kind: "repository-file",
42239
- pullRequestId,
42240
- sourceOid: walkthrough.sourceOid,
42865
+ pullRequestId: pullRequest.id,
42866
+ sourceOid,
42241
42867
  path: reference.path
42242
42868
  });
42243
42869
  const latestHeadOid = pullRequest.latestHeadOid;
42244
42870
  let latestPath = reference.path;
42245
42871
  let latestDocument;
42246
- if (walkthrough.sourceOid === latestHeadOid) {
42872
+ if (sourceOid === latestHeadOid) {
42247
42873
  latestDocument = sourceDocument;
42248
42874
  } else {
42249
42875
  latestDocument = await this.getDocument({
42250
42876
  kind: "repository-file",
42251
- pullRequestId,
42877
+ pullRequestId: pullRequest.id,
42252
42878
  sourceOid: latestHeadOid,
42253
42879
  path: reference.path
42254
42880
  });
@@ -42257,7 +42883,7 @@ var RvwService = class {
42257
42883
  ...new Set(
42258
42884
  (await this.git.changedFilesWithCopies(
42259
42885
  pullRequest.localRepositoryPath,
42260
- walkthrough.sourceOid,
42886
+ sourceOid,
42261
42887
  latestHeadOid
42262
42888
  )).filter(
42263
42889
  (candidate) => (candidate.status.startsWith("R") || candidate.status.startsWith("C")) && candidate.oldPath === reference.path && candidate.newPath !== null
@@ -42267,7 +42893,7 @@ var RvwService = class {
42267
42893
  latestPath = successorPaths.length === 1 ? successorPaths[0] : null;
42268
42894
  latestDocument = latestPath === null ? null : await this.getDocument({
42269
42895
  kind: "repository-file",
42270
- pullRequestId,
42896
+ pullRequestId: pullRequest.id,
42271
42897
  sourceOid: latestHeadOid,
42272
42898
  path: latestPath
42273
42899
  });
@@ -42285,7 +42911,7 @@ var RvwService = class {
42285
42911
  if (resolvedToLatest && latestPath !== null && latestDocument !== null) {
42286
42912
  return {
42287
42913
  outcome: "latest",
42288
- anchorSourceOid: walkthrough.sourceOid,
42914
+ anchorSourceOid: sourceOid,
42289
42915
  latestHeadOid,
42290
42916
  referenceFingerprint,
42291
42917
  target: {
@@ -42302,9 +42928,9 @@ var RvwService = class {
42302
42928
  document: latestDocument
42303
42929
  };
42304
42930
  }
42305
- const targetFile = await this.walkthroughReferenceFallbackTarget(
42931
+ const targetFile = await this.sourceReferenceFallbackTarget(
42306
42932
  pullRequest,
42307
- walkthrough.sourceOid,
42933
+ sourceOid,
42308
42934
  reference.path
42309
42935
  );
42310
42936
  const latestFile = latestFileExists && latestPath !== null ? {
@@ -42317,7 +42943,7 @@ var RvwService = class {
42317
42943
  } : null;
42318
42944
  return {
42319
42945
  outcome: "source-fallback",
42320
- anchorSourceOid: walkthrough.sourceOid,
42946
+ anchorSourceOid: sourceOid,
42321
42947
  latestHeadOid,
42322
42948
  referenceFingerprint,
42323
42949
  target: {
@@ -42768,6 +43394,7 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
42768
43394
  var skillNames = [
42769
43395
  "rvw",
42770
43396
  "rvw-walkthrough",
43397
+ "rvw-structure",
42771
43398
  "rvw-watch-comments"
42772
43399
  ];
42773
43400
  var INSTALL_METADATA_FILE = ".rvw-install.json";
@@ -46585,8 +47212,11 @@ var Hono2 = class extends Hono {
46585
47212
  var nonEmptyString = external_exports.string().min(1);
46586
47213
  var commentUri = external_exports.string().regex(/^rvw:\/\/comment\//);
46587
47214
  var walkthroughUri = external_exports.string().regex(/^rvw:\/\/walkthrough\//);
47215
+ var structureUri = external_exports.string().regex(/^rvw:\/\/structure\//);
46588
47216
  var nullableCommentLine = external_exports.number().int().positive().nullable().optional().default(null);
46589
47217
  var idempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS).optional();
47218
+ var requiredIdempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS);
47219
+ var expectedUpdatedAt = external_exports.string().min(1).max(100);
46590
47220
  var commentTargetInputSchema = external_exports.union([
46591
47221
  external_exports.object({ kind: external_exports.literal("pull-request") }).strict(),
46592
47222
  external_exports.object({
@@ -46709,6 +47339,151 @@ var walkthroughPublishInputSchema = walkthroughContentInputSchema.extend({
46709
47339
  pullRequest: nonEmptyString
46710
47340
  });
46711
47341
  var walkthroughUpdateInputSchema = walkthroughContentInputSchema;
47342
+ var sourceAnchorInputSchema = external_exports.object({
47343
+ path: external_exports.string().min(1).max(MAX_CODE_REFERENCE_PATH_CHARACTERS),
47344
+ startLine: external_exports.number().int().positive().nullable().optional().default(null),
47345
+ endLine: external_exports.number().int().positive().nullable().optional().default(null)
47346
+ }).strict().superRefine((anchor, context) => {
47347
+ if (anchor.startLine === null !== (anchor.endLine === null)) {
47348
+ context.addIssue({
47349
+ code: "custom",
47350
+ message: "startLine\u3068endLine\u306F\u4E21\u65B9\u6307\u5B9A\u3059\u308B\u304B\u3001\u4E21\u65B9\u7701\u7565\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
47351
+ });
47352
+ return;
47353
+ }
47354
+ if (anchor.startLine !== null && anchor.endLine !== null && anchor.endLine < anchor.startLine) {
47355
+ context.addIssue({ code: "custom", message: "endLine\u306FstartLine\u4EE5\u4E0A\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002" });
47356
+ }
47357
+ });
47358
+ var structureNodeInputSchema = external_exports.object({
47359
+ id: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47360
+ label: external_exports.string().min(1).max(MAX_STRUCTURE_LABEL_CHARACTERS),
47361
+ description: external_exports.string().max(MAX_STRUCTURE_DESCRIPTION_CHARACTERS).nullable().optional().default(null),
47362
+ kind: external_exports.string().max(MAX_STRUCTURE_KIND_CHARACTERS).nullable().optional().default(null),
47363
+ notation: external_exports.enum(STRUCTURE_NODE_NOTATIONS).optional().default("plain"),
47364
+ anchor: sourceAnchorInputSchema.nullable().optional().default(null)
47365
+ }).strict();
47366
+ var structureEdgeInputSchema = external_exports.object({
47367
+ id: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47368
+ from: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47369
+ to: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47370
+ label: external_exports.string().min(1).max(MAX_STRUCTURE_LABEL_CHARACTERS),
47371
+ directed: external_exports.boolean(),
47372
+ anchors: external_exports.array(sourceAnchorInputSchema).max(MAX_STRUCTURE_EDGE_ANCHORS).optional().default([])
47373
+ }).strict();
47374
+ var structureContentShape = {
47375
+ sourceOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
47376
+ title: external_exports.string().min(1).max(MAX_STRUCTURE_TITLE_CHARACTERS),
47377
+ scope: external_exports.string().min(1).max(MAX_STRUCTURE_SCOPE_CHARACTERS),
47378
+ originNodeId: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47379
+ nodes: external_exports.array(structureNodeInputSchema).min(1).max(MAX_STRUCTURE_NODES),
47380
+ edges: external_exports.array(structureEdgeInputSchema).max(MAX_STRUCTURE_EDGES)
47381
+ };
47382
+ function refineStructureContent(value, context) {
47383
+ const nodeIds = /* @__PURE__ */ new Set();
47384
+ for (const [index2, node2] of value.nodes.entries()) {
47385
+ if (nodeIds.has(node2.id)) {
47386
+ context.addIssue({
47387
+ code: "custom",
47388
+ path: ["nodes", index2, "id"],
47389
+ message: "Node ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
47390
+ });
47391
+ }
47392
+ nodeIds.add(node2.id);
47393
+ }
47394
+ const edgeIds = /* @__PURE__ */ new Set();
47395
+ for (const [index2, edge] of value.edges.entries()) {
47396
+ if (edgeIds.has(edge.id)) {
47397
+ context.addIssue({
47398
+ code: "custom",
47399
+ path: ["edges", index2, "id"],
47400
+ message: "Edge ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
47401
+ });
47402
+ }
47403
+ edgeIds.add(edge.id);
47404
+ if (!nodeIds.has(edge.from)) {
47405
+ context.addIssue({
47406
+ code: "custom",
47407
+ path: ["edges", index2, "from"],
47408
+ message: "Edge\u306Efrom Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
47409
+ });
47410
+ }
47411
+ if (!nodeIds.has(edge.to)) {
47412
+ context.addIssue({
47413
+ code: "custom",
47414
+ path: ["edges", index2, "to"],
47415
+ message: "Edge\u306Eto Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
47416
+ });
47417
+ }
47418
+ }
47419
+ const originIndex = value.nodes.findIndex((node2) => node2.id === value.originNodeId);
47420
+ if (originIndex === -1) {
47421
+ context.addIssue({
47422
+ code: "custom",
47423
+ path: ["originNodeId"],
47424
+ message: "originNodeId Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
47425
+ });
47426
+ } else if (value.nodes[originIndex]?.anchor === null) {
47427
+ context.addIssue({
47428
+ code: "custom",
47429
+ path: ["nodes", originIndex, "anchor"],
47430
+ message: "origin Node\u306B\u306Fsource anchor\u304C\u5FC5\u8981\u3067\u3059\u3002"
47431
+ });
47432
+ }
47433
+ if (originIndex !== -1) {
47434
+ const neighbors = new Map(value.nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
47435
+ for (const edge of value.edges) {
47436
+ if (!neighbors.has(edge.from) || !neighbors.has(edge.to)) continue;
47437
+ neighbors.get(edge.from).add(edge.to);
47438
+ neighbors.get(edge.to).add(edge.from);
47439
+ }
47440
+ const reached = /* @__PURE__ */ new Set([value.originNodeId]);
47441
+ const queue = [value.originNodeId];
47442
+ while (queue.length > 0) {
47443
+ const current = queue.shift();
47444
+ for (const neighbor of neighbors.get(current) ?? []) {
47445
+ if (reached.has(neighbor)) continue;
47446
+ reached.add(neighbor);
47447
+ queue.push(neighbor);
47448
+ }
47449
+ }
47450
+ const disconnected = value.nodes.filter((node2) => !reached.has(node2.id)).map((node2) => node2.id);
47451
+ if (disconnected.length > 0) {
47452
+ context.addIssue({
47453
+ code: "custom",
47454
+ path: ["nodes"],
47455
+ message: `\u5168Node\u3092origin\u304B\u3089\u8FBF\u308C\u308B\u95A2\u4FC2graph\u306B\u3057\u3066\u304F\u3060\u3055\u3044: ${disconnected.join(", ")}`
47456
+ });
47457
+ }
47458
+ }
47459
+ const anchorCount = value.nodes.filter((node2) => node2.anchor !== null).length + value.edges.reduce((count, edge) => count + edge.anchors.length, 0);
47460
+ if (anchorCount < 1) {
47461
+ context.addIssue({
47462
+ code: "custom",
47463
+ message: "Structure\u306B\u306Fsource anchor\u3092\u5C11\u306A\u304F\u3068\u30821\u4EF6\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
47464
+ });
47465
+ }
47466
+ if (anchorCount > MAX_STRUCTURE_SOURCE_ANCHORS) {
47467
+ context.addIssue({
47468
+ code: "custom",
47469
+ message: `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
47470
+ });
47471
+ }
47472
+ const graph = { originNodeId: value.originNodeId, nodes: value.nodes, edges: value.edges };
47473
+ if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
47474
+ context.addIssue({
47475
+ code: "custom",
47476
+ message: `Structure payload\u306F${MAX_STRUCTURE_PAYLOAD_BYTES} UTF-8 bytes\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
47477
+ });
47478
+ }
47479
+ }
47480
+ var structureContentInputSchema = external_exports.object(structureContentShape).strict().superRefine(refineStructureContent);
47481
+ var structureUpdateInputSchema = external_exports.object({ expectedUpdatedAt, ...structureContentShape }).strict().superRefine(refineStructureContent);
47482
+ var structurePublishInputSchema = external_exports.object({
47483
+ pullRequest: nonEmptyString,
47484
+ idempotencyKey: requiredIdempotencyKey,
47485
+ ...structureContentShape
47486
+ }).strict().superRefine(refineStructureContent);
46712
47487
  var agentCommandInputSchemas = {
46713
47488
  doctor: external_exports.object({ cwd: nonEmptyString }).strict(),
46714
47489
  "pr.refresh": external_exports.object({ reference: nonEmptyString }).strict(),
@@ -46739,7 +47514,13 @@ var agentCommandInputSchemas = {
46739
47514
  "walkthrough.publish": walkthroughPublishInputSchema,
46740
47515
  "walkthrough.update": external_exports.object({ uri: walkthroughUri, content: walkthroughUpdateInputSchema }).strict(),
46741
47516
  "walkthrough.delete.preview": external_exports.object({ uri: walkthroughUri }).strict(),
46742
- "walkthrough.delete": external_exports.object({ uri: walkthroughUri, confirmed: external_exports.literal(true) }).strict()
47517
+ "walkthrough.delete": external_exports.object({ uri: walkthroughUri, confirmed: external_exports.literal(true) }).strict(),
47518
+ "structure.get": external_exports.object({ uri: structureUri }).strict(),
47519
+ "structure.list": external_exports.object({ reference: nonEmptyString }).strict(),
47520
+ "structure.publish": structurePublishInputSchema,
47521
+ "structure.update": external_exports.object({ uri: structureUri, content: structureUpdateInputSchema }).strict(),
47522
+ "structure.delete.preview": external_exports.object({ uri: structureUri }).strict(),
47523
+ "structure.delete": external_exports.object({ uri: structureUri, expectedUpdatedAt, confirmed: external_exports.literal(true) }).strict()
46743
47524
  };
46744
47525
 
46745
47526
  // src/server/schemas.ts
@@ -46772,6 +47553,7 @@ var openPullRequestSchema = external_exports.object({
46772
47553
  cwd: external_exports.string().min(1)
46773
47554
  });
46774
47555
  var resetSchema = external_exports.object({ yes: external_exports.boolean() });
47556
+ var structureDeleteSchema = external_exports.object({ expectedUpdatedAt: external_exports.string().min(1).max(100) });
46775
47557
  var viewerIdSchema = external_exports.uuid();
46776
47558
  var viewerReleaseSchema = external_exports.object({ viewerId: viewerIdSchema });
46777
47559
  var themePreferenceSchema = external_exports.object({ themePreference: external_exports.enum(themePreferences) });
@@ -46817,6 +47599,7 @@ var editCommentPostSchema = external_exports.object({
46817
47599
 
46818
47600
  // src/server/app.ts
46819
47601
  var oidSchema = external_exports.string().regex(GIT_OBJECT_ID_PATTERN);
47602
+ var nonnegativeIndexSchema = external_exports.coerce.number().int().nonnegative();
46820
47603
  var svgAssetContentSecurityPolicy = "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; sandbox";
46821
47604
  function requiredQuery(value, name) {
46822
47605
  if (!value) throw new RvwError("INVALID_INPUT", `${name} query\u304C\u5FC5\u8981\u3067\u3059\u3002`);
@@ -46825,6 +47608,23 @@ function requiredQuery(value, name) {
46825
47608
  function oidQuery(value, name) {
46826
47609
  return oidSchema.parse(requiredQuery(value, name));
46827
47610
  }
47611
+ function structureSourceLocator(query) {
47612
+ if (query.locatorKind === "node") {
47613
+ return { kind: "node", nodeId: requiredQuery(query.nodeId, "nodeId") };
47614
+ }
47615
+ if (query.locatorKind === "edge") {
47616
+ const anchorIndex = nonnegativeIndexSchema.safeParse(query.anchorIndex);
47617
+ if (!anchorIndex.success) {
47618
+ throw new RvwError("INVALID_INPUT", "anchorIndex query\u306F0\u4EE5\u4E0A\u306E\u6574\u6570\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
47619
+ }
47620
+ return {
47621
+ kind: "edge",
47622
+ edgeId: requiredQuery(query.edgeId, "edgeId"),
47623
+ anchorIndex: anchorIndex.data
47624
+ };
47625
+ }
47626
+ throw new RvwError("INVALID_INPUT", "locatorKind query\u306Fnode\u307E\u305F\u306Fedge\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
47627
+ }
46828
47628
  function isWriteMethod(method) {
46829
47629
  return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
46830
47630
  }
@@ -47109,6 +47909,46 @@ function createApp(service, options) {
47109
47909
  )
47110
47910
  })
47111
47911
  );
47912
+ app.get(
47913
+ "/api/pull-requests/:id/structures",
47914
+ (context) => context.json({
47915
+ ok: true,
47916
+ structures: service.listStructures(context.req.param("id"))
47917
+ })
47918
+ );
47919
+ app.get(
47920
+ "/api/pull-requests/:id/structures/:structureId",
47921
+ (context) => context.json({
47922
+ ok: true,
47923
+ structure: service.getStructure(context.req.param("id"), context.req.param("structureId"))
47924
+ })
47925
+ );
47926
+ app.get("/api/pull-requests/:id/structures/:structureId/anchors/resolve", async (context) => {
47927
+ return context.json({
47928
+ ok: true,
47929
+ resolution: await service.resolveStructureSource(
47930
+ context.req.param("id"),
47931
+ context.req.param("structureId"),
47932
+ structureSourceLocator({
47933
+ locatorKind: context.req.query("locatorKind"),
47934
+ nodeId: context.req.query("nodeId"),
47935
+ edgeId: context.req.query("edgeId"),
47936
+ anchorIndex: context.req.query("anchorIndex")
47937
+ })
47938
+ )
47939
+ });
47940
+ });
47941
+ app.delete("/api/pull-requests/:id/structures/:structureId", async (context) => {
47942
+ const input = structureDeleteSchema.parse(await context.req.json());
47943
+ return context.json({
47944
+ ok: true,
47945
+ deleted: service.deleteStructure(
47946
+ context.req.param("id"),
47947
+ context.req.param("structureId"),
47948
+ input.expectedUpdatedAt
47949
+ )
47950
+ });
47951
+ });
47112
47952
  app.post("/api/comments", async (context) => {
47113
47953
  const input = createCommentSchema.parse(await context.req.json());
47114
47954
  return context.json(
@@ -47690,6 +48530,30 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
47690
48530
  const input = parseOperationInput("walkthrough.delete", request.input);
47691
48531
  return service.deleteWalkthroughByUri(input.uri);
47692
48532
  }
48533
+ case "structure.get": {
48534
+ const input = parseOperationInput("structure.get", request.input);
48535
+ return service.getStructureByUri(input.uri);
48536
+ }
48537
+ case "structure.list": {
48538
+ const input = parseOperationInput("structure.list", request.input);
48539
+ return service.listStructuresByReference(input.reference);
48540
+ }
48541
+ case "structure.publish": {
48542
+ const input = parseOperationInput("structure.publish", request.input);
48543
+ return await service.publishStructure(input);
48544
+ }
48545
+ case "structure.update": {
48546
+ const input = parseOperationInput("structure.update", request.input);
48547
+ return await service.updateStructure(input.uri, input.content);
48548
+ }
48549
+ case "structure.delete.preview": {
48550
+ const input = parseOperationInput("structure.delete.preview", request.input);
48551
+ return service.getStructureDeletePreview(input.uri);
48552
+ }
48553
+ case "structure.delete": {
48554
+ const input = parseOperationInput("structure.delete", request.input);
48555
+ return service.deleteStructureByUri(input.uri, input.expectedUpdatedAt);
48556
+ }
47693
48557
  }
47694
48558
  }
47695
48559
  function parseResponse(value) {
@@ -47706,8 +48570,11 @@ function uncertainOutcome(operation, cause, timedOut = false) {
47706
48570
  {
47707
48571
  cause,
47708
48572
  details: { agentSocketOutcomeUncertain: true, operation },
47709
- suggestions: [
47710
- "\u73FE\u5728\u306E\u30B3\u30E1\u30F3\u30C8\u30FBWalkthrough\u30FBPR\u540C\u671F\u72B6\u614B\u3092\u8AAD\u307F\u76F4\u3057\u3001\u672A\u53CD\u6620\u306E\u5834\u5408\u3060\u3051\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
48573
+ suggestions: operation === "structure.publish" ? [
48574
+ "\u540C\u3058idempotencyKey\u3068\u540C\u3058payload\u3067publish\u3092\u518D\u5B9F\u884C\u3059\u308B\u3068\u3001\u65E2\u5B58\u306EStructure\u3078\u53CE\u675F\u3057\u307E\u3059\u3002",
48575
+ "rvw structure list <PR> --json\u3067\u3082stable URI\u3092\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002"
48576
+ ] : [
48577
+ "\u73FE\u5728\u306E\u30B3\u30E1\u30F3\u30C8\u30FBWalkthrough\u30FBStructure\u30FBPR\u540C\u671F\u72B6\u614B\u3092\u8AAD\u307F\u76F4\u3057\u3001\u672A\u53CD\u6620\u306E\u5834\u5408\u3060\u3051\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
47711
48578
  ]
47712
48579
  }
47713
48580
  );
@@ -49313,6 +50180,11 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
49313
50180
  "comment.resolve",
49314
50181
  "comment.reopen",
49315
50182
  "pullRequest.sync",
50183
+ "structure.list",
50184
+ "structure.read",
50185
+ "structure.publish",
50186
+ "structure.update",
50187
+ "structure.delete",
49316
50188
  "walkthrough.read",
49317
50189
  "walkthrough.publish",
49318
50190
  "walkthrough.update",
@@ -49429,7 +50301,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
49429
50301
  writeOutput(
49430
50302
  options,
49431
50303
  result2,
49432
- `\u524A\u9664\u5BFE\u8C61: \u30B3\u30E1\u30F3\u30C8${preview.counts.comments}\u3001\u8FD4\u4FE1${preview.counts.posts}\u3001\u30B3\u30E1\u30F3\u30C8\u5185\u30B3\u30FC\u30C9\u53C2\u7167${preview.counts.commentReferences}\u3001\u5BFE\u8C61${preview.counts.targets}\u3001Walkthrough${preview.counts.walkthroughs}\u3001Walkthrough\u30B3\u30FC\u30C9\u53C2\u7167${preview.counts.walkthroughReferences}\u3001Git ref${preview.counts.gitRefs}
50304
+ `\u524A\u9664\u5BFE\u8C61: \u30B3\u30E1\u30F3\u30C8${preview.counts.comments}\u3001\u8FD4\u4FE1${preview.counts.posts}\u3001\u30B3\u30E1\u30F3\u30C8\u5185\u30B3\u30FC\u30C9\u53C2\u7167${preview.counts.commentReferences}\u3001\u5BFE\u8C61${preview.counts.targets}\u3001Walkthrough${preview.counts.walkthroughs}\u3001Walkthrough\u30B3\u30FC\u30C9\u53C2\u7167${preview.counts.walkthroughReferences}\u3001Structure${preview.counts.structures}\u3001Git ref${preview.counts.gitRefs}
49433
50305
  \u7D9A\u884C\u3059\u308B\u306B\u306F --yes \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
49434
50306
  );
49435
50307
  process.exitCode = 2;
@@ -49447,6 +50319,78 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
49447
50319
  );
49448
50320
  });
49449
50321
  const comment3 = program2.command("comment").description("\u4FDD\u5B58\u6E08\u307F\u30B3\u30E1\u30F3\u30C8\u3092\u64CD\u4F5C");
50322
+ const structure = program2.command("structure").description("source anchor\u4ED8\u304DStructure\u3092\u7BA1\u7406");
50323
+ 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 () => {
50324
+ const input = structurePublishInputSchema.parse(await readStdinJson());
50325
+ const published = await callService(
50326
+ "structure.publish",
50327
+ input,
50328
+ async () => await getRuntime().service.publishStructure(input)
50329
+ );
50330
+ writeJson({ ok: true, structure: published });
50331
+ });
50332
+ 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) => {
50333
+ const result = await callService(
50334
+ "structure.get",
50335
+ { uri },
50336
+ () => getRuntime().service.getStructureByUri(uri)
50337
+ );
50338
+ writeJson({ ok: true, ...result });
50339
+ });
50340
+ structure.command("list").argument("<pull-request>").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Pull Request\u306EStructure\u4E00\u89A7\u3068stable URI\u3092\u53D6\u5F97").action(async (reference) => {
50341
+ const result = await callService(
50342
+ "structure.list",
50343
+ { reference },
50344
+ () => getRuntime().service.listStructuresByReference(reference)
50345
+ );
50346
+ writeJson({ ok: true, ...result });
50347
+ });
50348
+ structure.command("update").argument("<structure-uri>").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u3092\u540C\u3058\u53C2\u7167\u306E\u307E\u307E\u5B8C\u5168\u7F6E\u63DB").action(async (uri) => {
50349
+ const content3 = structureUpdateInputSchema.parse(await readStdinJson());
50350
+ const updated = await callService(
50351
+ "structure.update",
50352
+ { uri, content: content3 },
50353
+ async () => await getRuntime().service.updateStructure(uri, content3)
50354
+ );
50355
+ writeJson({ ok: true, structure: updated });
50356
+ });
50357
+ 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(
50358
+ async (uri, options) => {
50359
+ if (!options.yes) {
50360
+ const preview = await callService(
50361
+ "structure.delete.preview",
50362
+ { uri },
50363
+ () => getRuntime().service.getStructureDeletePreview(uri)
50364
+ );
50365
+ writeJson({
50366
+ ok: false,
50367
+ error: {
50368
+ code: "STRUCTURE_DELETE_CONFIRMATION_REQUIRED",
50369
+ message: "structure delete\u306B\u306F--yes\u304C\u5FC5\u8981\u3067\u3059\u3002",
50370
+ suggestions: [
50371
+ `rvw structure delete ${uri} --yes --expected-updated-at '${preview.structure.updatedAt}' --json`
50372
+ ]
50373
+ },
50374
+ ...preview
50375
+ });
50376
+ process.exitCode = 2;
50377
+ return;
50378
+ }
50379
+ if (!options.expectedUpdatedAt) {
50380
+ throw new RvwError(
50381
+ "INVALID_INPUT",
50382
+ "confirmed structure delete\u306B\u306Fpreview\u306EexpectedUpdatedAt\u304C\u5FC5\u8981\u3067\u3059\u3002",
50383
+ { suggestions: [`rvw structure delete ${uri} --json \u3067\u73FE\u5728\u5024\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`] }
50384
+ );
50385
+ }
50386
+ const deleted = await callService(
50387
+ "structure.delete",
50388
+ { uri, expectedUpdatedAt: options.expectedUpdatedAt, confirmed: true },
50389
+ () => getRuntime().service.deleteStructureByUri(uri, options.expectedUpdatedAt)
50390
+ );
50391
+ writeJson({ ok: true, deleted });
50392
+ }
50393
+ );
49450
50394
  const walkthrough = program2.command("walkthrough").description("\u30B3\u30FC\u30C9\u53C2\u7167\u4ED8\u304Dwalkthrough\u3092\u7BA1\u7406");
49451
50395
  walkthrough.command("publish").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("walkthrough\u3092\u767B\u9332\uFF08viewer\u306F\u958B\u304B\u305Anavigation\u3082\u5909\u66F4\u3057\u306A\u3044\uFF09").action(async () => {
49452
50396
  const input = walkthroughPublishInputSchema.parse(await readStdinJson());