@a9n-shoji/rvw 0.3.2 → 0.4.0-beta.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-beta.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);
@@ -34510,7 +34814,7 @@ var LIMITED_QUIRKS_WITH_SYSTEM_ID_PUBLIC_ID_PREFIXES = [
34510
34814
  "-//w3c//dtd html 4.01 transitional//"
34511
34815
  ];
34512
34816
  function hasPrefix(publicId, prefixes) {
34513
- return prefixes.some((prefix2) => publicId.startsWith(prefix2));
34817
+ return prefixes.some((prefix3) => publicId.startsWith(prefix3));
34514
34818
  }
34515
34819
  function isConforming(token) {
34516
34820
  return token.name === VALID_DOCTYPE_NAME && token.publicId === null && (token.systemId === null || token.systemId === VALID_SYSTEM_ID);
@@ -42194,6 +42498,296 @@ var RvwService = class {
42194
42498
  this.getPullRequest(pullRequestId);
42195
42499
  return this.database.listWalkthroughs(pullRequestId);
42196
42500
  }
42501
+ listStructures(pullRequestId) {
42502
+ this.getPullRequest(pullRequestId);
42503
+ return this.database.listStructures(pullRequestId);
42504
+ }
42505
+ listStructuresByReference(reference) {
42506
+ const pullRequest = this.resolveStoredPullRequest(reference);
42507
+ return { pullRequest, structures: this.database.listStructures(pullRequest.id) };
42508
+ }
42509
+ getStructure(pullRequestId, structureId) {
42510
+ this.getPullRequest(pullRequestId);
42511
+ const structure = this.database.getStructure(structureId);
42512
+ if (!structure || structure.pullRequestId !== pullRequestId) {
42513
+ throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
42514
+ }
42515
+ return structure;
42516
+ }
42517
+ getStructureByUri(uri) {
42518
+ const structure = this.database.getStructure(parseStructureUri(uri));
42519
+ if (!structure) {
42520
+ throw new RvwError("NOT_FOUND", "Structure\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
42521
+ }
42522
+ return {
42523
+ pullRequest: this.getPullRequest(structure.pullRequestId),
42524
+ structure
42525
+ };
42526
+ }
42527
+ async validateSourceAnchor(pullRequest, sourceOid, anchor, subject, documents) {
42528
+ assertCodeReferencePath(anchor.path);
42529
+ const startLine = anchor.startLine ?? null;
42530
+ const endLine = anchor.endLine ?? null;
42531
+ assertLinePair(startLine, endLine);
42532
+ let contentPromise = documents.get(anchor.path);
42533
+ if (!contentPromise) {
42534
+ contentPromise = this.getDocument({
42535
+ kind: "repository-file",
42536
+ pullRequestId: pullRequest.id,
42537
+ sourceOid,
42538
+ path: anchor.path
42539
+ });
42540
+ documents.set(anchor.path, contentPromise);
42541
+ }
42542
+ const content3 = await contentPromise;
42543
+ if (content3.availability !== "available") {
42544
+ throw new RvwError("INVALID_INPUT", `${subject}\u306Esource\u3092\u8868\u793A\u3067\u304D\u307E\u305B\u3093: ${anchor.path}`);
42545
+ }
42546
+ this.validateLineRange(content3.text ?? "", startLine, endLine, subject);
42547
+ return { path: anchor.path, startLine, endLine };
42548
+ }
42549
+ assertStructureText(value, maximum, subject) {
42550
+ const normalized = value.trim();
42551
+ if (normalized.length === 0 || value.length > maximum) {
42552
+ throw new RvwError("INVALID_INPUT", `${subject}\u306F1\u301C${maximum}\u6587\u5B57\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
42553
+ }
42554
+ return normalized;
42555
+ }
42556
+ assertStructureId(value, subject) {
42557
+ if (!STRUCTURE_ID_PATTERN.test(value)) {
42558
+ throw new RvwError(
42559
+ "INVALID_INPUT",
42560
+ `${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`
42561
+ );
42562
+ }
42563
+ return value;
42564
+ }
42565
+ normalizeOptionalStructureText(value, maximum, subject) {
42566
+ if (value === null || value === void 0) return null;
42567
+ if (value.length > maximum) {
42568
+ throw new RvwError("INVALID_INPUT", `${subject}\u304C\u9577\u3059\u304E\u307E\u3059\u3002`);
42569
+ }
42570
+ return value.trim() || null;
42571
+ }
42572
+ async validateStructureContent(pullRequest, input) {
42573
+ await this.assertCommitAvailable(pullRequest, input.sourceOid);
42574
+ const title = this.assertStructureText(
42575
+ input.title,
42576
+ MAX_STRUCTURE_TITLE_CHARACTERS,
42577
+ "Structure title"
42578
+ );
42579
+ const scope = this.assertStructureText(
42580
+ input.scope,
42581
+ MAX_STRUCTURE_SCOPE_CHARACTERS,
42582
+ "Structure scope"
42583
+ );
42584
+ if (input.nodes.length < 1 || input.nodes.length > MAX_STRUCTURE_NODES) {
42585
+ throw new RvwError(
42586
+ "INVALID_INPUT",
42587
+ `Structure Node\u306F1\u301C${MAX_STRUCTURE_NODES}\u4EF6\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42588
+ );
42589
+ }
42590
+ if (input.edges.length > MAX_STRUCTURE_EDGES) {
42591
+ throw new RvwError(
42592
+ "INVALID_INPUT",
42593
+ `Structure Edge\u306F${MAX_STRUCTURE_EDGES}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42594
+ );
42595
+ }
42596
+ const documents = /* @__PURE__ */ new Map();
42597
+ const nodeIds = /* @__PURE__ */ new Set();
42598
+ const nodes = [];
42599
+ for (const node2 of input.nodes) {
42600
+ const id = this.assertStructureId(node2.id, "Structure Node ID");
42601
+ if (nodeIds.has(id)) {
42602
+ throw new RvwError("INVALID_INPUT", `Structure Node ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id}`);
42603
+ }
42604
+ nodeIds.add(id);
42605
+ const label = this.assertStructureText(
42606
+ node2.label,
42607
+ MAX_STRUCTURE_LABEL_CHARACTERS,
42608
+ `Structure Node ${id} label`
42609
+ );
42610
+ const notation = node2.notation ?? "plain";
42611
+ if (!STRUCTURE_NODE_NOTATIONS.includes(notation)) {
42612
+ throw new RvwError("INVALID_INPUT", `Structure Node ${id} notation\u304C\u4E0D\u6B63\u3067\u3059\u3002`);
42613
+ }
42614
+ nodes.push({
42615
+ id,
42616
+ label,
42617
+ description: this.normalizeOptionalStructureText(
42618
+ node2.description,
42619
+ MAX_STRUCTURE_DESCRIPTION_CHARACTERS,
42620
+ `Structure Node ${id} description`
42621
+ ),
42622
+ kind: this.normalizeOptionalStructureText(
42623
+ node2.kind,
42624
+ MAX_STRUCTURE_KIND_CHARACTERS,
42625
+ `Structure Node ${id} kind`
42626
+ ),
42627
+ notation,
42628
+ anchor: node2.anchor ? await this.validateSourceAnchor(
42629
+ pullRequest,
42630
+ input.sourceOid,
42631
+ node2.anchor,
42632
+ `Structure Node ${id}`,
42633
+ documents
42634
+ ) : null
42635
+ });
42636
+ }
42637
+ const edgeIds = /* @__PURE__ */ new Set();
42638
+ const edges = [];
42639
+ for (const edge of input.edges) {
42640
+ const id = this.assertStructureId(edge.id, "Structure Edge ID");
42641
+ if (edgeIds.has(id)) {
42642
+ throw new RvwError("INVALID_INPUT", `Structure Edge ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id}`);
42643
+ }
42644
+ edgeIds.add(id);
42645
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
42646
+ throw new RvwError(
42647
+ "INVALID_INPUT",
42648
+ `Structure Edge ${id} \u306Eendpoint\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${edge.from} \u2192 ${edge.to}`
42649
+ );
42650
+ }
42651
+ if (typeof edge.directed !== "boolean") {
42652
+ throw new RvwError("INVALID_INPUT", `Structure Edge ${id} \u306Edirected\u304C\u5FC5\u8981\u3067\u3059\u3002`);
42653
+ }
42654
+ const anchors = edge.anchors ?? [];
42655
+ if (anchors.length > MAX_STRUCTURE_EDGE_ANCHORS) {
42656
+ throw new RvwError(
42657
+ "INVALID_INPUT",
42658
+ `Structure Edge ${id} \u306Eanchor\u306F${MAX_STRUCTURE_EDGE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42659
+ );
42660
+ }
42661
+ edges.push({
42662
+ id,
42663
+ from: edge.from,
42664
+ to: edge.to,
42665
+ label: this.assertStructureText(
42666
+ edge.label,
42667
+ MAX_STRUCTURE_LABEL_CHARACTERS,
42668
+ `Structure Edge ${id} label`
42669
+ ),
42670
+ directed: edge.directed,
42671
+ anchors: await Promise.all(
42672
+ anchors.map(
42673
+ (anchor, index2) => this.validateSourceAnchor(
42674
+ pullRequest,
42675
+ input.sourceOid,
42676
+ anchor,
42677
+ `Structure Edge ${id} anchor ${index2 + 1}`,
42678
+ documents
42679
+ )
42680
+ )
42681
+ )
42682
+ });
42683
+ }
42684
+ if (typeof input.originNodeId !== "string") {
42685
+ throw new RvwError("INVALID_INPUT", "originNodeId\u304C\u5FC5\u8981\u3067\u3059\u3002");
42686
+ }
42687
+ const originNodeId = this.assertStructureId(input.originNodeId, "Structure originNodeId");
42688
+ if (!nodeIds.has(originNodeId)) {
42689
+ throw new RvwError("INVALID_INPUT", `originNodeId Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${originNodeId}`);
42690
+ }
42691
+ const originNode = nodes.find((node2) => node2.id === originNodeId);
42692
+ if (originNode.anchor === null) {
42693
+ throw new RvwError("INVALID_INPUT", "Structure origin Node\u306B\u306Fsource anchor\u304C\u5FC5\u8981\u3067\u3059\u3002");
42694
+ }
42695
+ const neighbors = new Map(nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
42696
+ for (const edge of edges) {
42697
+ neighbors.get(edge.from).add(edge.to);
42698
+ neighbors.get(edge.to).add(edge.from);
42699
+ }
42700
+ const reached = /* @__PURE__ */ new Set([originNodeId]);
42701
+ const queue = [originNodeId];
42702
+ while (queue.length > 0) {
42703
+ const current = queue.shift();
42704
+ for (const neighbor of neighbors.get(current) ?? []) {
42705
+ if (reached.has(neighbor)) continue;
42706
+ reached.add(neighbor);
42707
+ queue.push(neighbor);
42708
+ }
42709
+ }
42710
+ const disconnected = nodes.filter((node2) => !reached.has(node2.id)).map((node2) => node2.id);
42711
+ if (disconnected.length > 0) {
42712
+ throw new RvwError(
42713
+ "INVALID_INPUT",
42714
+ `Structure\u306E\u5168Node\u3092origin\u304B\u3089\u8FBF\u308C\u308B\u95A2\u4FC2graph\u306B\u3057\u3066\u304F\u3060\u3055\u3044: ${disconnected.join(", ")}`
42715
+ );
42716
+ }
42717
+ const anchorCount = nodes.filter((node2) => node2.anchor !== null).length + edges.reduce((count, edge) => count + edge.anchors.length, 0);
42718
+ if (anchorCount < 1) {
42719
+ throw new RvwError(
42720
+ "INVALID_INPUT",
42721
+ "Structure\u306B\u306Fsource anchor\u3092\u5C11\u306A\u304F\u3068\u30821\u4EF6\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
42722
+ );
42723
+ }
42724
+ if (anchorCount > MAX_STRUCTURE_SOURCE_ANCHORS) {
42725
+ throw new RvwError(
42726
+ "INVALID_INPUT",
42727
+ `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42728
+ );
42729
+ }
42730
+ const graph = { originNodeId, nodes, edges };
42731
+ if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
42732
+ throw new RvwError(
42733
+ "INVALID_INPUT",
42734
+ `Structure payload\u306F${MAX_STRUCTURE_PAYLOAD_BYTES} UTF-8 bytes\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
42735
+ );
42736
+ }
42737
+ return { sourceOid: input.sourceOid, title, scope, ...graph };
42738
+ }
42739
+ async publishStructure(input) {
42740
+ if (typeof input.idempotencyKey !== "string") {
42741
+ throw new RvwError("INVALID_INPUT", "idempotencyKey\u304C\u5FC5\u8981\u3067\u3059\u3002");
42742
+ }
42743
+ assertIdempotencyKey(input.idempotencyKey);
42744
+ const pullRequest = this.resolveStoredPullRequest(input.pullRequest);
42745
+ const content3 = await this.validateStructureContent(pullRequest, input);
42746
+ return await this.writeWithRetainedCommit(
42747
+ pullRequest,
42748
+ content3.sourceOid,
42749
+ "Structure",
42750
+ () => this.database.createStructure({
42751
+ pullRequestId: pullRequest.id,
42752
+ ...content3,
42753
+ idempotencyKey: input.idempotencyKey,
42754
+ idempotencyRequestHash: idempotencyRequestHash({
42755
+ operation: "structure.publish",
42756
+ pullRequestId: pullRequest.id,
42757
+ content: content3
42758
+ })
42759
+ })
42760
+ );
42761
+ }
42762
+ async updateStructure(uri, input) {
42763
+ const { pullRequest, structure } = this.getStructureByUri(uri);
42764
+ if (typeof input.expectedUpdatedAt !== "string" || input.expectedUpdatedAt.length === 0) {
42765
+ throw new RvwError("INVALID_INPUT", "expectedUpdatedAt\u304C\u5FC5\u8981\u3067\u3059\u3002");
42766
+ }
42767
+ const content3 = await this.validateStructureContent(pullRequest, input);
42768
+ return await this.writeWithRetainedCommit(
42769
+ pullRequest,
42770
+ content3.sourceOid,
42771
+ "Structure",
42772
+ () => this.database.updateStructure(structure.id, input.expectedUpdatedAt, content3)
42773
+ );
42774
+ }
42775
+ getStructureDeletePreview(uri) {
42776
+ const { structure } = this.getStructureByUri(uri);
42777
+ return {
42778
+ structure,
42779
+ counts: this.database.getStructureDeleteCounts(structure.id),
42780
+ confirmationRequired: true
42781
+ };
42782
+ }
42783
+ deleteStructureByUri(uri, expectedUpdatedAt2) {
42784
+ const { structure } = this.getStructureByUri(uri);
42785
+ return this.database.deleteStructure(structure.id, expectedUpdatedAt2);
42786
+ }
42787
+ deleteStructure(pullRequestId, structureId, expectedUpdatedAt2) {
42788
+ this.getStructure(pullRequestId, structureId);
42789
+ return this.database.deleteStructure(structureId, expectedUpdatedAt2);
42790
+ }
42197
42791
  getWalkthrough(pullRequestId, walkthroughId) {
42198
42792
  this.getPullRequest(pullRequestId);
42199
42793
  const walkthrough = this.database.getWalkthrough(walkthroughId);
@@ -42768,6 +43362,7 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
42768
43362
  var skillNames = [
42769
43363
  "rvw",
42770
43364
  "rvw-walkthrough",
43365
+ "rvw-structure",
42771
43366
  "rvw-watch-comments"
42772
43367
  ];
42773
43368
  var INSTALL_METADATA_FILE = ".rvw-install.json";
@@ -46585,8 +47180,11 @@ var Hono2 = class extends Hono {
46585
47180
  var nonEmptyString = external_exports.string().min(1);
46586
47181
  var commentUri = external_exports.string().regex(/^rvw:\/\/comment\//);
46587
47182
  var walkthroughUri = external_exports.string().regex(/^rvw:\/\/walkthrough\//);
47183
+ var structureUri = external_exports.string().regex(/^rvw:\/\/structure\//);
46588
47184
  var nullableCommentLine = external_exports.number().int().positive().nullable().optional().default(null);
46589
47185
  var idempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS).optional();
47186
+ var requiredIdempotencyKey = external_exports.string().min(1).max(MAX_IDEMPOTENCY_KEY_CHARACTERS);
47187
+ var expectedUpdatedAt = external_exports.string().min(1).max(100);
46590
47188
  var commentTargetInputSchema = external_exports.union([
46591
47189
  external_exports.object({ kind: external_exports.literal("pull-request") }).strict(),
46592
47190
  external_exports.object({
@@ -46709,6 +47307,151 @@ var walkthroughPublishInputSchema = walkthroughContentInputSchema.extend({
46709
47307
  pullRequest: nonEmptyString
46710
47308
  });
46711
47309
  var walkthroughUpdateInputSchema = walkthroughContentInputSchema;
47310
+ var sourceAnchorInputSchema = external_exports.object({
47311
+ path: external_exports.string().min(1).max(MAX_CODE_REFERENCE_PATH_CHARACTERS),
47312
+ startLine: external_exports.number().int().positive().nullable().optional().default(null),
47313
+ endLine: external_exports.number().int().positive().nullable().optional().default(null)
47314
+ }).strict().superRefine((anchor, context) => {
47315
+ if (anchor.startLine === null !== (anchor.endLine === null)) {
47316
+ context.addIssue({
47317
+ code: "custom",
47318
+ message: "startLine\u3068endLine\u306F\u4E21\u65B9\u6307\u5B9A\u3059\u308B\u304B\u3001\u4E21\u65B9\u7701\u7565\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
47319
+ });
47320
+ return;
47321
+ }
47322
+ if (anchor.startLine !== null && anchor.endLine !== null && anchor.endLine < anchor.startLine) {
47323
+ context.addIssue({ code: "custom", message: "endLine\u306FstartLine\u4EE5\u4E0A\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002" });
47324
+ }
47325
+ });
47326
+ var structureNodeInputSchema = external_exports.object({
47327
+ id: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47328
+ label: external_exports.string().min(1).max(MAX_STRUCTURE_LABEL_CHARACTERS),
47329
+ description: external_exports.string().max(MAX_STRUCTURE_DESCRIPTION_CHARACTERS).nullable().optional().default(null),
47330
+ kind: external_exports.string().max(MAX_STRUCTURE_KIND_CHARACTERS).nullable().optional().default(null),
47331
+ notation: external_exports.enum(STRUCTURE_NODE_NOTATIONS).optional().default("plain"),
47332
+ anchor: sourceAnchorInputSchema.nullable().optional().default(null)
47333
+ }).strict();
47334
+ var structureEdgeInputSchema = external_exports.object({
47335
+ id: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47336
+ from: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47337
+ to: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47338
+ label: external_exports.string().min(1).max(MAX_STRUCTURE_LABEL_CHARACTERS),
47339
+ directed: external_exports.boolean(),
47340
+ anchors: external_exports.array(sourceAnchorInputSchema).max(MAX_STRUCTURE_EDGE_ANCHORS).optional().default([])
47341
+ }).strict();
47342
+ var structureContentShape = {
47343
+ sourceOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
47344
+ title: external_exports.string().min(1).max(MAX_STRUCTURE_TITLE_CHARACTERS),
47345
+ scope: external_exports.string().min(1).max(MAX_STRUCTURE_SCOPE_CHARACTERS),
47346
+ originNodeId: external_exports.string().max(MAX_STRUCTURE_ID_CHARACTERS).regex(STRUCTURE_ID_PATTERN),
47347
+ nodes: external_exports.array(structureNodeInputSchema).min(1).max(MAX_STRUCTURE_NODES),
47348
+ edges: external_exports.array(structureEdgeInputSchema).max(MAX_STRUCTURE_EDGES)
47349
+ };
47350
+ function refineStructureContent(value, context) {
47351
+ const nodeIds = /* @__PURE__ */ new Set();
47352
+ for (const [index2, node2] of value.nodes.entries()) {
47353
+ if (nodeIds.has(node2.id)) {
47354
+ context.addIssue({
47355
+ code: "custom",
47356
+ path: ["nodes", index2, "id"],
47357
+ message: "Node ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
47358
+ });
47359
+ }
47360
+ nodeIds.add(node2.id);
47361
+ }
47362
+ const edgeIds = /* @__PURE__ */ new Set();
47363
+ for (const [index2, edge] of value.edges.entries()) {
47364
+ if (edgeIds.has(edge.id)) {
47365
+ context.addIssue({
47366
+ code: "custom",
47367
+ path: ["edges", index2, "id"],
47368
+ message: "Edge ID\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002"
47369
+ });
47370
+ }
47371
+ edgeIds.add(edge.id);
47372
+ if (!nodeIds.has(edge.from)) {
47373
+ context.addIssue({
47374
+ code: "custom",
47375
+ path: ["edges", index2, "from"],
47376
+ message: "Edge\u306Efrom Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
47377
+ });
47378
+ }
47379
+ if (!nodeIds.has(edge.to)) {
47380
+ context.addIssue({
47381
+ code: "custom",
47382
+ path: ["edges", index2, "to"],
47383
+ message: "Edge\u306Eto Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
47384
+ });
47385
+ }
47386
+ }
47387
+ const originIndex = value.nodes.findIndex((node2) => node2.id === value.originNodeId);
47388
+ if (originIndex === -1) {
47389
+ context.addIssue({
47390
+ code: "custom",
47391
+ path: ["originNodeId"],
47392
+ message: "originNodeId Node\u304C\u5B58\u5728\u3057\u307E\u305B\u3093\u3002"
47393
+ });
47394
+ } else if (value.nodes[originIndex]?.anchor === null) {
47395
+ context.addIssue({
47396
+ code: "custom",
47397
+ path: ["nodes", originIndex, "anchor"],
47398
+ message: "origin Node\u306B\u306Fsource anchor\u304C\u5FC5\u8981\u3067\u3059\u3002"
47399
+ });
47400
+ }
47401
+ if (originIndex !== -1) {
47402
+ const neighbors = new Map(value.nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
47403
+ for (const edge of value.edges) {
47404
+ if (!neighbors.has(edge.from) || !neighbors.has(edge.to)) continue;
47405
+ neighbors.get(edge.from).add(edge.to);
47406
+ neighbors.get(edge.to).add(edge.from);
47407
+ }
47408
+ const reached = /* @__PURE__ */ new Set([value.originNodeId]);
47409
+ const queue = [value.originNodeId];
47410
+ while (queue.length > 0) {
47411
+ const current = queue.shift();
47412
+ for (const neighbor of neighbors.get(current) ?? []) {
47413
+ if (reached.has(neighbor)) continue;
47414
+ reached.add(neighbor);
47415
+ queue.push(neighbor);
47416
+ }
47417
+ }
47418
+ const disconnected = value.nodes.filter((node2) => !reached.has(node2.id)).map((node2) => node2.id);
47419
+ if (disconnected.length > 0) {
47420
+ context.addIssue({
47421
+ code: "custom",
47422
+ path: ["nodes"],
47423
+ message: `\u5168Node\u3092origin\u304B\u3089\u8FBF\u308C\u308B\u95A2\u4FC2graph\u306B\u3057\u3066\u304F\u3060\u3055\u3044: ${disconnected.join(", ")}`
47424
+ });
47425
+ }
47426
+ }
47427
+ const anchorCount = value.nodes.filter((node2) => node2.anchor !== null).length + value.edges.reduce((count, edge) => count + edge.anchors.length, 0);
47428
+ if (anchorCount < 1) {
47429
+ context.addIssue({
47430
+ code: "custom",
47431
+ message: "Structure\u306B\u306Fsource anchor\u3092\u5C11\u306A\u304F\u3068\u30821\u4EF6\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
47432
+ });
47433
+ }
47434
+ if (anchorCount > MAX_STRUCTURE_SOURCE_ANCHORS) {
47435
+ context.addIssue({
47436
+ code: "custom",
47437
+ message: `Structure\u306Esource anchor\u306F\u5408\u8A08${MAX_STRUCTURE_SOURCE_ANCHORS}\u4EF6\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
47438
+ });
47439
+ }
47440
+ const graph = { originNodeId: value.originNodeId, nodes: value.nodes, edges: value.edges };
47441
+ if (Buffer.byteLength(JSON.stringify(graph), "utf8") > MAX_STRUCTURE_PAYLOAD_BYTES) {
47442
+ context.addIssue({
47443
+ code: "custom",
47444
+ message: `Structure payload\u306F${MAX_STRUCTURE_PAYLOAD_BYTES} UTF-8 bytes\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
47445
+ });
47446
+ }
47447
+ }
47448
+ var structureContentInputSchema = external_exports.object(structureContentShape).strict().superRefine(refineStructureContent);
47449
+ var structureUpdateInputSchema = external_exports.object({ expectedUpdatedAt, ...structureContentShape }).strict().superRefine(refineStructureContent);
47450
+ var structurePublishInputSchema = external_exports.object({
47451
+ pullRequest: nonEmptyString,
47452
+ idempotencyKey: requiredIdempotencyKey,
47453
+ ...structureContentShape
47454
+ }).strict().superRefine(refineStructureContent);
46712
47455
  var agentCommandInputSchemas = {
46713
47456
  doctor: external_exports.object({ cwd: nonEmptyString }).strict(),
46714
47457
  "pr.refresh": external_exports.object({ reference: nonEmptyString }).strict(),
@@ -46739,7 +47482,13 @@ var agentCommandInputSchemas = {
46739
47482
  "walkthrough.publish": walkthroughPublishInputSchema,
46740
47483
  "walkthrough.update": external_exports.object({ uri: walkthroughUri, content: walkthroughUpdateInputSchema }).strict(),
46741
47484
  "walkthrough.delete.preview": external_exports.object({ uri: walkthroughUri }).strict(),
46742
- "walkthrough.delete": external_exports.object({ uri: walkthroughUri, confirmed: external_exports.literal(true) }).strict()
47485
+ "walkthrough.delete": external_exports.object({ uri: walkthroughUri, confirmed: external_exports.literal(true) }).strict(),
47486
+ "structure.get": external_exports.object({ uri: structureUri }).strict(),
47487
+ "structure.list": external_exports.object({ reference: nonEmptyString }).strict(),
47488
+ "structure.publish": structurePublishInputSchema,
47489
+ "structure.update": external_exports.object({ uri: structureUri, content: structureUpdateInputSchema }).strict(),
47490
+ "structure.delete.preview": external_exports.object({ uri: structureUri }).strict(),
47491
+ "structure.delete": external_exports.object({ uri: structureUri, expectedUpdatedAt, confirmed: external_exports.literal(true) }).strict()
46743
47492
  };
46744
47493
 
46745
47494
  // src/server/schemas.ts
@@ -46772,6 +47521,7 @@ var openPullRequestSchema = external_exports.object({
46772
47521
  cwd: external_exports.string().min(1)
46773
47522
  });
46774
47523
  var resetSchema = external_exports.object({ yes: external_exports.boolean() });
47524
+ var structureDeleteSchema = external_exports.object({ expectedUpdatedAt: external_exports.string().min(1).max(100) });
46775
47525
  var viewerIdSchema = external_exports.uuid();
46776
47526
  var viewerReleaseSchema = external_exports.object({ viewerId: viewerIdSchema });
46777
47527
  var themePreferenceSchema = external_exports.object({ themePreference: external_exports.enum(themePreferences) });
@@ -47109,6 +47859,31 @@ function createApp(service, options) {
47109
47859
  )
47110
47860
  })
47111
47861
  );
47862
+ app.get(
47863
+ "/api/pull-requests/:id/structures",
47864
+ (context) => context.json({
47865
+ ok: true,
47866
+ structures: service.listStructures(context.req.param("id"))
47867
+ })
47868
+ );
47869
+ app.get(
47870
+ "/api/pull-requests/:id/structures/:structureId",
47871
+ (context) => context.json({
47872
+ ok: true,
47873
+ structure: service.getStructure(context.req.param("id"), context.req.param("structureId"))
47874
+ })
47875
+ );
47876
+ app.delete("/api/pull-requests/:id/structures/:structureId", async (context) => {
47877
+ const input = structureDeleteSchema.parse(await context.req.json());
47878
+ return context.json({
47879
+ ok: true,
47880
+ deleted: service.deleteStructure(
47881
+ context.req.param("id"),
47882
+ context.req.param("structureId"),
47883
+ input.expectedUpdatedAt
47884
+ )
47885
+ });
47886
+ });
47112
47887
  app.post("/api/comments", async (context) => {
47113
47888
  const input = createCommentSchema.parse(await context.req.json());
47114
47889
  return context.json(
@@ -47690,6 +48465,30 @@ async function dispatchAgentSocketRequest(service, rawRequest) {
47690
48465
  const input = parseOperationInput("walkthrough.delete", request.input);
47691
48466
  return service.deleteWalkthroughByUri(input.uri);
47692
48467
  }
48468
+ case "structure.get": {
48469
+ const input = parseOperationInput("structure.get", request.input);
48470
+ return service.getStructureByUri(input.uri);
48471
+ }
48472
+ case "structure.list": {
48473
+ const input = parseOperationInput("structure.list", request.input);
48474
+ return service.listStructuresByReference(input.reference);
48475
+ }
48476
+ case "structure.publish": {
48477
+ const input = parseOperationInput("structure.publish", request.input);
48478
+ return await service.publishStructure(input);
48479
+ }
48480
+ case "structure.update": {
48481
+ const input = parseOperationInput("structure.update", request.input);
48482
+ return await service.updateStructure(input.uri, input.content);
48483
+ }
48484
+ case "structure.delete.preview": {
48485
+ const input = parseOperationInput("structure.delete.preview", request.input);
48486
+ return service.getStructureDeletePreview(input.uri);
48487
+ }
48488
+ case "structure.delete": {
48489
+ const input = parseOperationInput("structure.delete", request.input);
48490
+ return service.deleteStructureByUri(input.uri, input.expectedUpdatedAt);
48491
+ }
47693
48492
  }
47694
48493
  }
47695
48494
  function parseResponse(value) {
@@ -47706,8 +48505,11 @@ function uncertainOutcome(operation, cause, timedOut = false) {
47706
48505
  {
47707
48506
  cause,
47708
48507
  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"
48508
+ suggestions: operation === "structure.publish" ? [
48509
+ "\u540C\u3058idempotencyKey\u3068\u540C\u3058payload\u3067publish\u3092\u518D\u5B9F\u884C\u3059\u308B\u3068\u3001\u65E2\u5B58\u306EStructure\u3078\u53CE\u675F\u3057\u307E\u3059\u3002",
48510
+ "rvw structure list <PR> --json\u3067\u3082stable URI\u3092\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002"
48511
+ ] : [
48512
+ "\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
48513
  ]
47712
48514
  }
47713
48515
  );
@@ -49313,6 +50115,11 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
49313
50115
  "comment.resolve",
49314
50116
  "comment.reopen",
49315
50117
  "pullRequest.sync",
50118
+ "structure.list",
50119
+ "structure.read",
50120
+ "structure.publish",
50121
+ "structure.update",
50122
+ "structure.delete",
49316
50123
  "walkthrough.read",
49317
50124
  "walkthrough.publish",
49318
50125
  "walkthrough.update",
@@ -49429,7 +50236,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
49429
50236
  writeOutput(
49430
50237
  options,
49431
50238
  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}
50239
+ `\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
50240
  \u7D9A\u884C\u3059\u308B\u306B\u306F --yes \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
49434
50241
  );
49435
50242
  process.exitCode = 2;
@@ -49447,6 +50254,78 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
49447
50254
  );
49448
50255
  });
49449
50256
  const comment3 = program2.command("comment").description("\u4FDD\u5B58\u6E08\u307F\u30B3\u30E1\u30F3\u30C8\u3092\u64CD\u4F5C");
50257
+ const structure = program2.command("structure").description("source anchor\u4ED8\u304DStructure\u3092\u7BA1\u7406");
50258
+ 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 () => {
50259
+ const input = structurePublishInputSchema.parse(await readStdinJson());
50260
+ const published = await callService(
50261
+ "structure.publish",
50262
+ input,
50263
+ async () => await getRuntime().service.publishStructure(input)
50264
+ );
50265
+ writeJson({ ok: true, structure: published });
50266
+ });
50267
+ 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) => {
50268
+ const result = await callService(
50269
+ "structure.get",
50270
+ { uri },
50271
+ () => getRuntime().service.getStructureByUri(uri)
50272
+ );
50273
+ writeJson({ ok: true, ...result });
50274
+ });
50275
+ 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) => {
50276
+ const result = await callService(
50277
+ "structure.list",
50278
+ { reference },
50279
+ () => getRuntime().service.listStructuresByReference(reference)
50280
+ );
50281
+ writeJson({ ok: true, ...result });
50282
+ });
50283
+ 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) => {
50284
+ const content3 = structureUpdateInputSchema.parse(await readStdinJson());
50285
+ const updated = await callService(
50286
+ "structure.update",
50287
+ { uri, content: content3 },
50288
+ async () => await getRuntime().service.updateStructure(uri, content3)
50289
+ );
50290
+ writeJson({ ok: true, structure: updated });
50291
+ });
50292
+ 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(
50293
+ async (uri, options) => {
50294
+ if (!options.yes) {
50295
+ const preview = await callService(
50296
+ "structure.delete.preview",
50297
+ { uri },
50298
+ () => getRuntime().service.getStructureDeletePreview(uri)
50299
+ );
50300
+ writeJson({
50301
+ ok: false,
50302
+ error: {
50303
+ code: "STRUCTURE_DELETE_CONFIRMATION_REQUIRED",
50304
+ message: "structure delete\u306B\u306F--yes\u304C\u5FC5\u8981\u3067\u3059\u3002",
50305
+ suggestions: [
50306
+ `rvw structure delete ${uri} --yes --expected-updated-at '${preview.structure.updatedAt}' --json`
50307
+ ]
50308
+ },
50309
+ ...preview
50310
+ });
50311
+ process.exitCode = 2;
50312
+ return;
50313
+ }
50314
+ if (!options.expectedUpdatedAt) {
50315
+ throw new RvwError(
50316
+ "INVALID_INPUT",
50317
+ "confirmed structure delete\u306B\u306Fpreview\u306EexpectedUpdatedAt\u304C\u5FC5\u8981\u3067\u3059\u3002",
50318
+ { suggestions: [`rvw structure delete ${uri} --json \u3067\u73FE\u5728\u5024\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`] }
50319
+ );
50320
+ }
50321
+ const deleted = await callService(
50322
+ "structure.delete",
50323
+ { uri, expectedUpdatedAt: options.expectedUpdatedAt, confirmed: true },
50324
+ () => getRuntime().service.deleteStructureByUri(uri, options.expectedUpdatedAt)
50325
+ );
50326
+ writeJson({ ok: true, deleted });
50327
+ }
50328
+ );
49450
50329
  const walkthrough = program2.command("walkthrough").description("\u30B3\u30FC\u30C9\u53C2\u7167\u4ED8\u304Dwalkthrough\u3092\u7BA1\u7406");
49451
50330
  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
50331
  const input = walkthroughPublishInputSchema.parse(await readStdinJson());