@shiyuphan/follow-me 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +44 -55
  2. package/dist/src/cli.js +566 -188
  3. package/dist/src/comments.js +66 -5
  4. package/dist/src/git.js +12 -0
  5. package/dist/src/host.js +19 -8
  6. package/dist/src/install.js +21 -0
  7. package/dist/src/plan.js +1 -1
  8. package/dist/src/prompt.js +343 -39
  9. package/dist/src/server.js +176 -67
  10. package/dist/src/session.js +13 -11
  11. package/dist/ui/dist/assets/abnfDiagram-WEYOIA3C-DcmzRkVZ.js +1 -0
  12. package/dist/ui/dist/assets/arc-T5QB0T1C.js +1 -0
  13. package/dist/ui/dist/assets/architecture-WOLXFQ4H-DUIkQTeE.js +1 -0
  14. package/dist/ui/dist/assets/architectureDiagram-ZHVYKT53-BUYi4-bA.js +36 -0
  15. package/dist/ui/dist/assets/array-BifhSqXX.js +1 -0
  16. package/dist/ui/dist/assets/blockDiagram-RJAE7QA3-dyJxtT9e.js +140 -0
  17. package/dist/ui/dist/assets/c4Diagram-XDPPPE54-CrAgiaZR.js +38 -0
  18. package/dist/ui/dist/assets/channel-wW7ebm0i.js +1 -0
  19. package/dist/ui/dist/assets/chunk-2E7WHVMD-C6fAf4Vj.js +88 -0
  20. package/dist/ui/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  21. package/dist/ui/dist/assets/chunk-4EMNKR2T-BLZjwZ4Q.js +1 -0
  22. package/dist/ui/dist/assets/chunk-55XCU76K-DGLK2Q-L.js +1 -0
  23. package/dist/ui/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  24. package/dist/ui/dist/assets/chunk-7S6XSTFO-BablszTX.js +2 -0
  25. package/dist/ui/dist/assets/chunk-AQDTSKZB-Cuk3B_vb.js +72 -0
  26. package/dist/ui/dist/assets/chunk-DSNLSHJZ-DiC78RqE.js +127 -0
  27. package/dist/ui/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  28. package/dist/ui/dist/assets/chunk-HAGFOTED-X_RbvixX.js +62 -0
  29. package/dist/ui/dist/assets/chunk-HNE4Q36P-C14_x5ck.js +1 -0
  30. package/dist/ui/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  31. package/dist/ui/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  32. package/dist/ui/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  33. package/dist/ui/dist/assets/chunk-NQOKRVIF-Dxd0dtYR.js +213 -0
  34. package/dist/ui/dist/assets/chunk-UHW7PFBP-BIxePNUS.js +1 -0
  35. package/dist/ui/dist/assets/chunk-VQ5W62QI-CKSKLbj4.js +1 -0
  36. package/dist/ui/dist/assets/chunk-WQQL4CDK-CWNmZGlR.js +10 -0
  37. package/dist/ui/dist/assets/chunk-XXDRQBXY-D-I5QHgG.js +1 -0
  38. package/dist/ui/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  39. package/dist/ui/dist/assets/chunk-YZO67HFG-DFP68CFd.js +1 -0
  40. package/dist/ui/dist/assets/chunk-ZAUX2LTX-DY90d4QG.js +1 -0
  41. package/dist/ui/dist/assets/chunk-ZCHMQSOR-Dlpr-5UC.js +2 -0
  42. package/dist/ui/dist/assets/classDiagram-v2-4MYWR3UA-CH9uhoLJ.js +217 -0
  43. package/dist/ui/dist/assets/cose-bilkent-JH36ORCC-DtYHrp8D.js +1 -0
  44. package/dist/ui/dist/assets/cynefin-EF2NZ3EQ-BtslbtrY.js +1 -0
  45. package/dist/ui/dist/assets/cynefinDiagram-WF24UDRP-DUU1Jcc7.js +62 -0
  46. package/dist/ui/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  47. package/dist/ui/dist/assets/dagre-NK4GIZVX-CDlP-dRW.js +4 -0
  48. package/dist/ui/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  49. package/dist/ui/dist/assets/diagram-CBK3MWTC-DiV2fn-r.js +24 -0
  50. package/dist/ui/dist/assets/diagram-I5XIIV2D-Bg5RehGi.js +24 -0
  51. package/dist/ui/dist/assets/diagram-ODIL2UCW-CFmazz3f.js +30 -0
  52. package/dist/ui/dist/assets/diagram-PFBEJUXE-uw3ZBqDp.js +3 -0
  53. package/dist/ui/dist/assets/diagram-RJA3I4VV-uEOYYRYa.js +41 -0
  54. package/dist/ui/dist/assets/diagram-TYUNFSNR-BN58LF_3.js +200 -0
  55. package/dist/ui/dist/assets/dist-tWS_lXDS.js +1 -0
  56. package/dist/ui/dist/assets/ebnfDiagram-V4P3TTPT-B68Tz-w8.js +1 -0
  57. package/dist/ui/dist/assets/elk-QONTBCTQ-DZZohNea.js +27 -0
  58. package/dist/ui/dist/assets/erDiagram-KYQIGQAA-BH_m74pv.js +99 -0
  59. package/dist/ui/dist/assets/eventmodeling-K75KTNOO-CTp5FHos.js +1 -0
  60. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-CsCvexry.js +1 -0
  61. package/dist/ui/dist/assets/ganttDiagram-R7GPNTJY-BDau7_0f.js +292 -0
  62. package/dist/ui/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  63. package/dist/ui/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  64. package/dist/ui/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  65. package/dist/ui/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  66. package/dist/ui/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  67. package/dist/ui/dist/assets/gitGraph-VSP46ZUC-Bv84IoCm.js +1 -0
  68. package/dist/ui/dist/assets/gitGraphDiagram-PDYL7XHY-DzXcMlm8.js +106 -0
  69. package/dist/ui/dist/assets/graphlib-DS17s2tU.js +1 -0
  70. package/dist/ui/dist/assets/index-BTJoqVVN.js +133 -0
  71. package/dist/ui/dist/assets/index-D60U3smc.css +1 -0
  72. package/dist/ui/dist/assets/info-OHQRW6UA-222J0O_j.js +1 -0
  73. package/dist/ui/dist/assets/infoDiagram-MVDRKAX5-DE2sa3Vz.js +2 -0
  74. package/dist/ui/dist/assets/init-C-OQMol4.js +1 -0
  75. package/dist/ui/dist/assets/ishikawaDiagram-HPEHV2GE-BJDk0Zcj.js +70 -0
  76. package/dist/ui/dist/assets/journeyDiagram-BOSHW7O7-JSUXX5Hb.js +139 -0
  77. package/dist/ui/dist/assets/kanban-definition-FNSA4HC5-Bg79b49M.js +89 -0
  78. package/dist/ui/dist/assets/katex-B_JcIwqj.js +257 -0
  79. package/dist/ui/dist/assets/line-Bk5qQSIa.js +1 -0
  80. package/dist/ui/dist/assets/linear-PFnVMru5.js +1 -0
  81. package/dist/ui/dist/assets/mermaid-parser.core-Dcxlzc-S.js +7 -0
  82. package/dist/ui/dist/assets/mermaid.core-TeOHQJ00.js +44 -0
  83. package/dist/ui/dist/assets/mindmap-definition-6OY57UG3-IaNk8NnW.js +98 -0
  84. package/dist/ui/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  85. package/dist/ui/dist/assets/packet-JDAUHWVQ-DMff5cVK.js +1 -0
  86. package/dist/ui/dist/assets/path-fybaL0A-.js +1 -0
  87. package/dist/ui/dist/assets/pegDiagram-ECIUJNBX-66Xu4YRc.js +1 -0
  88. package/dist/ui/dist/assets/pie-XZMESJXO-GEtzLCIM.js +1 -0
  89. package/dist/ui/dist/assets/pieDiagram-GNMPY74T-7jMBi-kU.js +39 -0
  90. package/dist/ui/dist/assets/prism-bash-D6zCJ74D.js +1 -0
  91. package/dist/ui/dist/assets/prism-c-04YixN25.js +1 -0
  92. package/dist/ui/dist/assets/prism-clike-DapgWyxx.js +1 -0
  93. package/dist/ui/dist/assets/prism-core-FDHlWQ43.js +1 -0
  94. package/dist/ui/dist/assets/prism-cpp-C-lJbC-6.js +1 -0
  95. package/dist/ui/dist/assets/prism-css-CPgOzxXp.js +1 -0
  96. package/dist/ui/dist/assets/prism-go-C_8qAH5n.js +1 -0
  97. package/dist/ui/dist/assets/prism-java-6wtusvIl.js +1 -0
  98. package/dist/ui/dist/assets/prism-javascript-BnO-swvr.js +1 -0
  99. package/dist/ui/dist/assets/prism-json-Dp_-W-Hv.js +1 -0
  100. package/dist/ui/dist/assets/prism-jsx-D-D0NWtC.js +1 -0
  101. package/dist/ui/dist/assets/prism-kotlin-BUlJgC-o.js +1 -0
  102. package/dist/ui/dist/assets/prism-markdown-Bc9-36OC.js +1 -0
  103. package/dist/ui/dist/assets/prism-markup-Cp04rIa1.js +1 -0
  104. package/dist/ui/dist/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
  105. package/dist/ui/dist/assets/prism-php-Co4fM40K.js +1 -0
  106. package/dist/ui/dist/assets/prism-python-CfiSVtM6.js +1 -0
  107. package/dist/ui/dist/assets/prism-ruby-DFCASkuD.js +1 -0
  108. package/dist/ui/dist/assets/prism-rust-Bu9N8X3N.js +1 -0
  109. package/dist/ui/dist/assets/prism-sql-CYhVDMEE.js +1 -0
  110. package/dist/ui/dist/assets/prism-swift-B0kWc-ne.js +1 -0
  111. package/dist/ui/dist/assets/prism-toml-DKb54GAT.js +1 -0
  112. package/dist/ui/dist/assets/prism-tsx-p6J2Kc_4.js +1 -0
  113. package/dist/ui/dist/assets/prism-typescript-BMqONzxf.js +1 -0
  114. package/dist/ui/dist/assets/prism-yaml-C0JHu8gr.js +1 -0
  115. package/dist/ui/dist/assets/quadrantDiagram-D57K4MC3-8hIWayC2.js +7 -0
  116. package/dist/ui/dist/assets/radar-ABXABTNO-BTdwUOOF.js +1 -0
  117. package/dist/ui/dist/assets/railroad-I3PHUGI6-DcMbDqBL.js +1 -0
  118. package/dist/ui/dist/assets/railroad-abnf-LNEFI6M7-gTzUTWeI.js +1 -0
  119. package/dist/ui/dist/assets/railroad-ebnf-DWYD2UWJ-CNtdUPtk.js +1 -0
  120. package/dist/ui/dist/assets/railroad-peg-JR7BVNR7-DOiExAtE.js +1 -0
  121. package/dist/ui/dist/assets/railroadDiagram-Y72NRBL7-2kE_ygvM.js +1 -0
  122. package/dist/ui/dist/assets/requirementDiagram-EQYSW5RA-CoIAHRq2.js +84 -0
  123. package/dist/ui/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  124. package/dist/ui/dist/assets/sankeyDiagram-SY24JHO5-7p8D3wim.js +40 -0
  125. package/dist/ui/dist/assets/sequenceDiagram-BLEHNHQU-C6QFnMvn.js +169 -0
  126. package/dist/ui/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  127. package/dist/ui/dist/assets/src-FgijoBVs.js +1 -0
  128. package/dist/ui/dist/assets/stateDiagram-v2-A6IESKWZ-n9eAb_bT.js +291 -0
  129. package/dist/ui/dist/assets/swimlanes-B3ENRKFQ-U18mtUzX.js +1 -0
  130. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DX07QVy9.js +8 -0
  131. package/dist/ui/dist/assets/timeline-definition-JXYKGLGB-CD2Z8QGP.js +120 -0
  132. package/dist/ui/dist/assets/treeView-D4JQ5SDB-sqUjH5gB.js +1 -0
  133. package/dist/ui/dist/assets/treemap-SAJKECNS-DvZ38FP7.js +1 -0
  134. package/dist/ui/dist/assets/usecaseDiagram-WPOLBNNX-CqOGidaZ.js +328 -0
  135. package/dist/ui/dist/assets/vennDiagram-Q3Y3IDCW-D1SXSBQ5.js +34 -0
  136. package/dist/ui/dist/assets/wardley-7MLQ67FV-Bpwio-Dx.js +1 -0
  137. package/dist/ui/dist/assets/wardleyDiagram-IVAIDKWA-JSUXHMnk.js +78 -0
  138. package/dist/ui/dist/assets/xychartDiagram-4URNHDU4-B4IFqQTN.js +7 -0
  139. package/dist/ui/dist/index.html +15 -0
  140. package/dist/ui/dist/style.css +121 -0
  141. package/package.json +31 -10
  142. package/skills/follow-me/SKILL.md +15 -89
  143. package/dist/src/anthropic.js +0 -133
  144. package/dist/ui/app.js +0 -2201
  145. package/dist/ui/concerns.js +0 -165
  146. package/dist/ui/highlight.js +0 -221
  147. package/dist/ui/index.html +0 -94
  148. package/dist/ui/live.js +0 -184
  149. package/dist/ui/playback.js +0 -579
  150. package/dist/ui/questions.js +0 -189
  151. package/dist/ui/style.css +0 -1331
  152. package/dist/ui/summary.js +0 -45
@@ -14,8 +14,9 @@ function isIndex(v) {
14
14
  return typeof v === "number" && Number.isInteger(v) && v >= 0;
15
15
  }
16
16
  function parseAnchor(v) {
17
- if (!isFields(v))
17
+ if (!isFields(v)) {
18
18
  return "anchor is not an object";
19
+ }
19
20
  if (v.kind === "plan")
20
21
  return { kind: "plan" };
21
22
  if (!isIndex(v.commit))
@@ -27,12 +28,25 @@ function parseAnchor(v) {
27
28
  return "anchor.kind is unknown";
28
29
  if (typeof v.unit !== "string" || !isUnitId(v.unit))
29
30
  return "anchor.unit is not a unit id";
30
- if (v.changes === null)
31
+ if (v.changes === null) {
32
+ if (v.offsets !== undefined)
33
+ return "anchor.offsets needs anchor.changes to be a list";
31
34
  return { kind: "hunk", unit: v.unit, commit, changes: null };
35
+ }
32
36
  if (!isArray(v.changes) || !v.changes.every(isIndex)) {
33
37
  return "anchor.changes is not null or a list of change indices";
34
38
  }
35
- return { kind: "hunk", unit: v.unit, commit, changes: v.changes.filter(isIndex) };
39
+ const changes = v.changes.filter(isIndex);
40
+ if (v.offsets === undefined)
41
+ return { kind: "hunk", unit: v.unit, commit, changes };
42
+ if (!isArray(v.offsets) || v.offsets.length === 0 || !v.offsets.every(isIndex)) {
43
+ return "anchor.offsets is not a non-empty list of row indices";
44
+ }
45
+ const offsets = v.offsets.filter(isIndex);
46
+ if (!offsets.every((o, i) => i === 0 || o > (offsets[i - 1] ?? 0))) {
47
+ return "anchor.offsets is not strictly ascending";
48
+ }
49
+ return { kind: "hunk", unit: v.unit, commit, changes, offsets };
36
50
  }
37
51
  /** A stored reply. Replies a client posts go through parseNewReply, which mints id and time. */
38
52
  export function parseReply(v) {
@@ -101,6 +115,17 @@ export function parseComment(v) {
101
115
  }
102
116
  if (v.kind !== undefined && v.kind !== "question")
103
117
  return 'kind is not "question"';
118
+ if (v.author !== undefined && v.author !== "agent")
119
+ return 'author is not "agent"';
120
+ if (v.diagram !== undefined) {
121
+ if (typeof v.diagram !== "string")
122
+ return "diagram is not a string";
123
+ if (v.author !== "agent")
124
+ return "diagram is only for an agent's comment";
125
+ const problem = diagramProblem(v.diagram);
126
+ if (problem !== null)
127
+ return `diagram: ${problem}`;
128
+ }
104
129
  const follows = v.follows;
105
130
  if (follows !== undefined && !(typeof follows === "string" && isCommentId(follows))) {
106
131
  return "follows is not a comment id";
@@ -108,6 +133,11 @@ export function parseComment(v) {
108
133
  const severity = v.severity;
109
134
  if (severity !== undefined && !isSeverity(severity))
110
135
  return "severity is unknown";
136
+ if (v.pending !== undefined && v.pending !== true)
137
+ return "pending is not true";
138
+ if (v.pending === true && (v.kind === "question" || v.author === "agent")) {
139
+ return "only a reviewer's flag can be pending";
140
+ }
111
141
  const replies = parseList(v.replies, parseReply, "replies");
112
142
  if (typeof replies === "string")
113
143
  return replies;
@@ -124,7 +154,10 @@ export function parseComment(v) {
124
154
  ...(v.kind === "question" ? { kind: "question" } : {}),
125
155
  ...(follows === undefined ? {} : { follows }),
126
156
  ...(severity === undefined ? {} : { severity }),
157
+ ...(v.pending === true ? { pending: true } : {}),
127
158
  ...(replies.length === 0 ? {} : { replies }),
159
+ ...(v.author === "agent" ? { author: "agent" } : {}),
160
+ ...(v.diagram === undefined ? {} : { diagram: v.diagram }),
128
161
  };
129
162
  }
130
163
  function withDecision(concern, v) {
@@ -135,11 +168,15 @@ function withDecision(concern, v) {
135
168
  return "severity is unknown";
136
169
  if (v.note !== undefined && typeof v.note !== "string")
137
170
  return "note is not a string";
171
+ if (v.decidedAt !== undefined && typeof v.decidedAt !== "string") {
172
+ return "decidedAt is not a string";
173
+ }
138
174
  return {
139
175
  concern,
140
176
  decision: v.decision,
141
177
  ...(v.severity === undefined ? {} : { severity: v.severity }),
142
178
  ...(v.note === undefined || v.note.trim() === "" ? {} : { note: v.note }),
179
+ ...(v.decidedAt === undefined ? {} : { decidedAt: v.decidedAt }),
143
180
  };
144
181
  }
145
182
  /** A stored decision. */
@@ -150,11 +187,11 @@ export function parseDecision(v) {
150
187
  return "concern is not a concern id";
151
188
  return withDecision(v.concern, v);
152
189
  }
153
- /** The body of POST .../concerns/<id>; the id comes from the URL. */
190
+ /** The body of POST .../concerns/<id>; the id comes from the URL and the time is the server's. */
154
191
  export function parseNewDecision(concern, v) {
155
192
  if (!isFields(v))
156
193
  return "the decision is not an object";
157
- return withDecision(concern, v);
194
+ return withDecision(concern, { ...v, decidedAt: undefined });
158
195
  }
159
196
  /** Absent reads as none. The first item that does not parse names its index and reason. */
160
197
  function parseList(raw, parse, what) {
@@ -171,6 +208,21 @@ function parseList(raw, parse, what) {
171
208
  }
172
209
  return out;
173
210
  }
211
+ function parseConcernId(item) {
212
+ return isConcernId(item) ? item : "not a concern id";
213
+ }
214
+ /** Absent reads as nothing excluded. */
215
+ export function parseExcluded(v) {
216
+ if (v === undefined)
217
+ return { comments: [], concerns: [] };
218
+ if (!isFields(v))
219
+ return "excluded is not an object";
220
+ const comments = parseList(v.comments, parseCommentId, "excluded.comments");
221
+ if (typeof comments === "string")
222
+ return comments;
223
+ const concerns = parseList(v.concerns, parseConcernId, "excluded.concerns");
224
+ return typeof concerns === "string" ? concerns : { comments, concerns };
225
+ }
174
226
  function asCommentId(item) {
175
227
  return typeof item === "string" && isCommentId(item) ? item : null;
176
228
  }
@@ -203,6 +255,10 @@ export function parseReview(v) {
203
255
  const resolved = parseList(v.resolved, parseCommentId, "resolved");
204
256
  if (typeof resolved === "string")
205
257
  return resolved;
258
+ const excluded = parseExcluded(v.excluded);
259
+ if (typeof excluded === "string")
260
+ return excluded;
261
+ const anyExcluded = excluded.comments.length + excluded.concerns.length > 0;
206
262
  return {
207
263
  schema: v.schema,
208
264
  mode,
@@ -212,6 +268,7 @@ export function parseReview(v) {
212
268
  comments,
213
269
  ...(concerns.length === 0 ? {} : { concerns }),
214
270
  ...(resolved.length === 0 ? {} : { resolved }),
271
+ ...(anyExcluded ? { excluded } : {}),
215
272
  };
216
273
  }
217
274
  function lenient(items, parse) {
@@ -230,10 +287,12 @@ export function parseDraft(v) {
230
287
  if (!isFields(v) || !isArray(v.comments)) {
231
288
  return "not a review draft";
232
289
  }
290
+ const excluded = parseExcluded(v.excluded);
233
291
  return {
234
292
  comments: lenient(v.comments, parseComment),
235
293
  concerns: lenient(v.concerns, parseDecision),
236
294
  resolved: lenient(v.resolved, parseCommentId),
295
+ excluded: typeof excluded === "string" ? { comments: [], concerns: [] } : excluded,
237
296
  };
238
297
  }
239
298
  export function parseEvent(v) {
@@ -280,6 +339,8 @@ export function parseEvent(v) {
280
339
  return "malformed version event";
281
340
  return { seq, type: v.type, version: v.version, hash: v.hash };
282
341
  }
342
+ case "submitted":
343
+ return { seq, type: v.type };
283
344
  case "ended":
284
345
  return typeof v.review === "string"
285
346
  ? { seq, type: v.type, review: v.review }
package/dist/src/git.js CHANGED
@@ -134,6 +134,18 @@ export async function loadDiff(repo, baseRef, tipRef) {
134
134
  const blobs = await catBlobs(repo, neededBlobs(rawZ, patchText));
135
135
  return parseDiff({ base, tip, baseTree, tipTree, rawZ, patch: patchText, blobs });
136
136
  }
137
+ /**
138
+ * Whether the configured user wrote every commit of base..tip, which is how follow-me tells its
139
+ * author's own branch from one it is asked to review. False when no user.email is set.
140
+ */
141
+ export async function isOwnWork(repo, base, tip) {
142
+ const me = await gitText(repo, ["config", "--default=", "user.email"]);
143
+ if (me === "")
144
+ return false;
145
+ const authors = await gitText(repo, ["log", "--format=%ae", `${base}..${tip}`]);
146
+ const emails = authors.split("\n").filter((line) => line !== "");
147
+ return emails.length > 0 && emails.every((email) => email === me);
148
+ }
137
149
  export async function signing(repo, mode) {
138
150
  const configured = await gitText(repo, [
139
151
  "config",
package/dist/src/host.js CHANGED
@@ -125,7 +125,7 @@ async function loadHosted(registration, held) {
125
125
  if (!verdict.ok) {
126
126
  throw new RegistrationError(`plan v${version} no longer fits ${branch}; check it again`);
127
127
  }
128
- const reviewsDir = session.reviewsDir(mode);
128
+ const reviewsDir = session.reviewsDir();
129
129
  const comments = await loadDraftComments(reviewsDir, version);
130
130
  const prior = session.priorReview(mode, version);
131
131
  const previous = prior === null || prior.comments.length === 0
@@ -135,7 +135,7 @@ async function loadHosted(registration, held) {
135
135
  comments: prior.comments,
136
136
  responses: stored.file.responses,
137
137
  };
138
- const command = mode === "walk" ? "walk" : "review";
138
+ const command = "review";
139
139
  // A version swap runs inside the server that already owns this session's lock.
140
140
  const release = held ??
141
141
  (lockToken === null ? session.lock(command) : session.adoptLock(lockToken, command)).release;
@@ -250,14 +250,16 @@ async function call(port, method, path, body) {
250
250
  function parseSummary(raw) {
251
251
  if (!isFields(raw))
252
252
  return null;
253
- const { id, url, repo, branch, mode, version, comments } = raw;
253
+ const { id, url, repo, branch, mode, version, comments, openedAt } = raw;
254
254
  if (typeof url !== "string" || typeof repo !== "string")
255
255
  return null;
256
256
  if (typeof branch !== "string" || (mode !== "split" && mode !== "walk"))
257
257
  return null;
258
258
  if (!isSessionId(id) || !isPlanVersion(version) || typeof comments !== "number")
259
259
  return null;
260
- return { id, url, repo, branch, mode, version, comments };
260
+ if (typeof openedAt !== "string")
261
+ return null;
262
+ return { id, url, repo, branch, mode, version, comments, openedAt };
261
263
  }
262
264
  /** Registers a session with the server on this port; the same session yields the same URL. */
263
265
  export async function registerWith(port, registration) {
@@ -275,7 +277,7 @@ export async function swapVersion(port, summary, version, hash) {
275
277
  }
276
278
  /** One long poll of the session's event log: what happened after `since`, or nothing at timeout. */
277
279
  export async function pollEvents(port, summary, since, timeoutMs) {
278
- const path = `${summary.url}api/events?since=${since}&timeout=${timeoutMs}`;
280
+ const path = `${summary.url}api/events?since=${since}&timeout=${timeoutMs}&audience=agent`;
279
281
  const batch = parseEventBatch(await call(port, "GET", path));
280
282
  if (typeof batch === "string")
281
283
  throw new RegistrationError(`the server sent bad events: ${batch}`);
@@ -286,7 +288,7 @@ export async function pollEvents(port, summary, since, timeoutMs) {
286
288
  * the server closes the stream, which it does after the `ended` event.
287
289
  */
288
290
  export async function followEvents(port, summary, since, onEvent) {
289
- const res = await fetch(`${sessionUrl(port, summary)}api/events/stream?since=${since}`);
291
+ const res = await fetch(`${sessionUrl(port, summary)}api/events/stream?since=${since}&audience=agent`);
290
292
  if (!res.ok || res.body === null) {
291
293
  throw new RegistrationError(`GET api/events/stream failed with ${res.status}`);
292
294
  }
@@ -317,9 +319,18 @@ export async function postReply(port, summary, commentId, reply) {
317
319
  throw new RegistrationError("the server answered without a reply id");
318
320
  return id;
319
321
  }
322
+ /** Posts a comment the agent starts and returns its id; the server mints id and time. */
323
+ export async function postComment(port, summary, comment) {
324
+ const body = await call(port, "POST", `${summary.url}api/comments`, comment);
325
+ const id = isFields(body) ? body["id"] : undefined;
326
+ if (typeof id !== "string") {
327
+ throw new RegistrationError("the server answered without a comment id");
328
+ }
329
+ return id;
330
+ }
320
331
  /** The consolidated report of the hosted session as it stands. */
321
- export async function fetchReport(port, summary) {
322
- const res = await fetch(`${sessionUrl(port, summary)}api/report`);
332
+ export async function fetchReport(port, summary, format = "markdown") {
333
+ const res = await fetch(`${sessionUrl(port, summary)}api/report?format=${format}`);
323
334
  if (!res.ok)
324
335
  throw new RegistrationError(`GET api/report failed with ${res.status}`);
325
336
  return res.text();
@@ -122,6 +122,27 @@ export function runInstall(options, home, repoRoot, say, skillDir = findSkillDir
122
122
  }
123
123
  });
124
124
  }
125
+ /**
126
+ * Keeps the skill current in every harness found at user scope: writes what is missing or differs
127
+ * from the shipped copy and says so, one line per file; says nothing when all is current. A host
128
+ * with no harness, or a package without its skill folder, is not an error here.
129
+ */
130
+ export function syncSkills(home, say, skillDir) {
131
+ try {
132
+ const dir = skillDir ?? findSkillDir();
133
+ const targets = { harness: undefined, all: false, project: false, dryRun: false };
134
+ for (const file of planSkillFiles(targets, home, home)) {
135
+ const line = writeSkill(dir, file, false);
136
+ if (line.startsWith("wrote "))
137
+ say(line);
138
+ }
139
+ }
140
+ catch (error) {
141
+ if (error instanceof InstallError)
142
+ return;
143
+ process.stderr.write(`follow-me: could not update the skill: ${error instanceof Error ? error.message : error}\n`);
144
+ }
145
+ }
125
146
  export function runUninstall(options, home, repoRoot, say) {
126
147
  return reportingErrors(() => {
127
148
  for (const file of planSkillFiles(options, home, repoRoot)) {
package/dist/src/plan.js CHANGED
@@ -645,7 +645,7 @@ export function validatePlan(diff, draft, opts = {}) {
645
645
  const warnings = review === null
646
646
  ? []
647
647
  : review.comments
648
- .filter((c) => !answered.has(c.id))
648
+ .filter((c) => c.author !== "agent" && !answered.has(c.id))
649
649
  .map((c) => ({ kind: "unanswered", comment: c.id, review: review.planVersion }));
650
650
  file.commits.forEach((c, commit) => {
651
651
  const length = (c.message.split("\n")[0] ?? "").length;