@shiyuphan/follow-me 0.4.0 → 0.5.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/src/cli.js +154 -11
- package/dist/src/comments.js +29 -11
- package/dist/src/evolve.js +6 -2
- package/dist/src/harness.js +4 -1
- package/dist/src/host.js +17 -1
- package/dist/src/install.js +1 -1
- package/dist/src/prompt.js +170 -166
- package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-byISxePP.js → abnfDiagram-WEYOIA3C-BXkLs7mV.js} +1 -1
- package/dist/ui/dist/assets/{arc-CBzsAsP2.js → arc-DNzjw7Ie.js} +1 -1
- package/dist/ui/dist/assets/{architecture-WOLXFQ4H-0OSkIwdB.js → architecture-WOLXFQ4H-D66kgJJt.js} +1 -1
- package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-ixHybNqO.js → architectureDiagram-ZHVYKT53-D8UWuhQr.js} +1 -1
- package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-1F8XL_Ou.js → blockDiagram-RJAE7QA3-BLkmr62i.js} +1 -1
- package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-D9PFBXnc.js → c4Diagram-XDPPPE54-3rZPYwOc.js} +1 -1
- package/dist/ui/dist/assets/channel-B9JVdOBZ.js +1 -0
- package/dist/ui/dist/assets/{chunk-2E7WHVMD-zZdNfMkj.js → chunk-2E7WHVMD-MMnvAgpB.js} +1 -1
- package/dist/ui/dist/assets/{chunk-4EMNKR2T-CTKoxSSj.js → chunk-4EMNKR2T-D9FGWA3l.js} +1 -1
- package/dist/ui/dist/assets/{chunk-55XCU76K-ifFnDlcS.js → chunk-55XCU76K-DuOUaV8F.js} +1 -1
- package/dist/ui/dist/assets/{chunk-7S6XSTFO-BCMVdi_1.js → chunk-7S6XSTFO-p3YWPv0m.js} +1 -1
- package/dist/ui/dist/assets/{chunk-AQDTSKZB-DC-5Whju.js → chunk-AQDTSKZB-DWfKYjwk.js} +1 -1
- package/dist/ui/dist/assets/{chunk-DSNLSHJZ-DASbe_gb.js → chunk-DSNLSHJZ-C3CIwpv0.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HAGFOTED-CArytDjj.js → chunk-HAGFOTED-Bwfl9gme.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HNE4Q36P-Dnl-Wtud.js → chunk-HNE4Q36P-0_oeU0le.js} +1 -1
- package/dist/ui/dist/assets/{chunk-NQOKRVIF-wvSLaeaK.js → chunk-NQOKRVIF-z_LPPPx5.js} +1 -1
- package/dist/ui/dist/assets/{chunk-UHW7PFBP-CckaOGiQ.js → chunk-UHW7PFBP-BH28V2yu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-VQ5W62QI-kQBMDJ7V.js → chunk-VQ5W62QI-BXDajDf7.js} +1 -1
- package/dist/ui/dist/assets/{chunk-WQQL4CDK-CMg6bbLU.js → chunk-WQQL4CDK-BphBlfKj.js} +1 -1
- package/dist/ui/dist/assets/{chunk-XXDRQBXY-CYlFr19K.js → chunk-XXDRQBXY-BiCT5NwV.js} +1 -1
- package/dist/ui/dist/assets/{chunk-YZO67HFG-CqkG1vVk.js → chunk-YZO67HFG-Bg5Pu8BX.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZAUX2LTX-BUNEdx-e.js → chunk-ZAUX2LTX-C_Tgd8tB.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZCHMQSOR-CFTbvcrm.js → chunk-ZCHMQSOR-MBRcMi8-.js} +1 -1
- package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-t2OdJ0LA.js → classDiagram-v2-4MYWR3UA-1wyeCe5y.js} +1 -1
- package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-FGm030gc.js → cose-bilkent-JH36ORCC-BtOYclF6.js} +1 -1
- package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-BtU33Lsu.js → cynefin-EF2NZ3EQ-dJmoOG8g.js} +1 -1
- package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-DUr-I7hS.js → cynefinDiagram-WF24UDRP-AloyqbEN.js} +1 -1
- package/dist/ui/dist/assets/{dagre-NK4GIZVX-BWTHwJKt.js → dagre-NK4GIZVX-NCZhdnfb.js} +1 -1
- package/dist/ui/dist/assets/{diagram-CBK3MWTC-PcHrsEBn.js → diagram-CBK3MWTC-D8w7x5Gb.js} +1 -1
- package/dist/ui/dist/assets/{diagram-I5XIIV2D-CkGtKjYj.js → diagram-I5XIIV2D-BUFnCGlp.js} +1 -1
- package/dist/ui/dist/assets/{diagram-ODIL2UCW-B_HdBB1w.js → diagram-ODIL2UCW-BULmHKaW.js} +1 -1
- package/dist/ui/dist/assets/{diagram-PFBEJUXE-DL9zayE6.js → diagram-PFBEJUXE-B5MqFo0r.js} +1 -1
- package/dist/ui/dist/assets/{diagram-RJA3I4VV-By5wl8nK.js → diagram-RJA3I4VV-BIkDLL8E.js} +1 -1
- package/dist/ui/dist/assets/{diagram-TYUNFSNR-ziWPVayL.js → diagram-TYUNFSNR-Ca4gsQV8.js} +1 -1
- package/dist/ui/dist/assets/{dist-NDVqCM7k.js → dist-G85JKY5k.js} +1 -1
- package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-DH4YGkPc.js → ebnfDiagram-V4P3TTPT-rVjcFFtb.js} +1 -1
- package/dist/ui/dist/assets/{elk-QONTBCTQ-BL2RlCEv.js → elk-QONTBCTQ-BWPdvULh.js} +1 -1
- package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-BKbTMaRp.js → erDiagram-KYQIGQAA-BPxJJkRl.js} +1 -1
- package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-Dk5RCWc-.js → eventmodeling-K75KTNOO-BSB9WYV3.js} +1 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-N-Spyk2o.js +1 -0
- package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-3vM9gxeT.js → ganttDiagram-R7GPNTJY-DKps530K.js} +1 -1
- package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-CXGFhz54.js → gitGraph-VSP46ZUC-D1d4UAFV.js} +1 -1
- package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-Cl4fwUuA.js → gitGraphDiagram-PDYL7XHY-N55yl6hc.js} +1 -1
- package/dist/ui/dist/assets/index-BKtWBS7Z.js +134 -0
- package/dist/ui/dist/assets/{info-OHQRW6UA-DfSsZ8ib.js → info-OHQRW6UA-CgxE3gf9.js} +1 -1
- package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-B_7tyrL5.js → infoDiagram-MVDRKAX5-DOnUrnMf.js} +1 -1
- package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-DUvHljpg.js → ishikawaDiagram-HPEHV2GE-BIY0fFHB.js} +1 -1
- package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-BCgqzrb8.js → journeyDiagram-BOSHW7O7-CqA2Ern0.js} +1 -1
- package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-DOzZ3NmE.js → kanban-definition-FNSA4HC5-vGrpQKIF.js} +1 -1
- package/dist/ui/dist/assets/{line-B-mPFVll.js → line-Brzv3qA6.js} +1 -1
- package/dist/ui/dist/assets/{linear-k7o0jout.js → linear-D9uIlqyh.js} +1 -1
- package/dist/ui/dist/assets/{mermaid-parser.core-CAyLSQpH.js → mermaid-parser.core-Bz9b-Thu.js} +3 -3
- package/dist/ui/dist/assets/{mermaid.core-vfVMjgx2.js → mermaid.core-BaZipu3b.js} +4 -4
- package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-B6y-B_r4.js → mindmap-definition-6OY57UG3-D6aie7uH.js} +1 -1
- package/dist/ui/dist/assets/{packet-JDAUHWVQ-C_r4ed30.js → packet-JDAUHWVQ-28r3jVFY.js} +1 -1
- package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-B8VUm3Bh.js → pegDiagram-ECIUJNBX-CbGvD4qP.js} +1 -1
- package/dist/ui/dist/assets/{pie-XZMESJXO-DcwtbH9y.js → pie-XZMESJXO-DHpcGnZd.js} +1 -1
- package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-CvUNBi2F.js → pieDiagram-GNMPY74T-DuhauwnY.js} +1 -1
- package/dist/ui/dist/assets/{prism-core-_2ju85Ya.js → prism-core-CbZMOvrb.js} +1 -1
- package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-Dg2eVS3j.js → quadrantDiagram-D57K4MC3-BfyNmVca.js} +1 -1
- package/dist/ui/dist/assets/{radar-ABXABTNO-xCNTcZE9.js → radar-ABXABTNO-Xp1TYwaj.js} +1 -1
- package/dist/ui/dist/assets/{railroad-I3PHUGI6-BI-sIL2Q.js → railroad-I3PHUGI6-c5FY4wwa.js} +1 -1
- package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-Ddvk40Wx.js → railroad-abnf-LNEFI6M7-RDyWXqcL.js} +1 -1
- package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-h35gE183.js → railroad-ebnf-DWYD2UWJ-CLD4jVou.js} +1 -1
- package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-Zim9iPdp.js → railroad-peg-JR7BVNR7-17EYTA-w.js} +1 -1
- package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-Cz6fdmOQ.js → railroadDiagram-Y72NRBL7-C4p2wtKe.js} +1 -1
- package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-DTJkCrcD.js → requirementDiagram-EQYSW5RA-DTYIFN8T.js} +1 -1
- package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-Bg1iiVla.js → sankeyDiagram-SY24JHO5-YDYhRyJU.js} +1 -1
- package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-9XfetsRA.js → sequenceDiagram-BLEHNHQU-Crvy6IlU.js} +1 -1
- package/dist/ui/dist/assets/{src-Dym5afQ5.js → src-BPod-Ype.js} +1 -1
- package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-CMmcMV8z.js → stateDiagram-v2-A6IESKWZ-BWr70YOQ.js} +1 -1
- package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-B2PcshTY.js → swimlanes-B3ENRKFQ-DvbG4I9a.js} +1 -1
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-D2wU-jW6.js +8 -0
- package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-Cr1hCNtW.js → timeline-definition-JXYKGLGB-CR950Bzb.js} +1 -1
- package/dist/ui/dist/assets/{treeView-D4JQ5SDB-jH5ksKnh.js → treeView-D4JQ5SDB-DSDDXGrk.js} +1 -1
- package/dist/ui/dist/assets/{treemap-SAJKECNS-BV90e8QL.js → treemap-SAJKECNS-Ct5w2_RR.js} +1 -1
- package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-BNwq5aJV.js → usecaseDiagram-WPOLBNNX-_eP0371y.js} +1 -1
- package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-D-lLyZCL.js → vennDiagram-Q3Y3IDCW-Cu-IA57L.js} +1 -1
- package/dist/ui/dist/assets/{wardley-7MLQ67FV-CWfHQUGt.js → wardley-7MLQ67FV-r8yd6frN.js} +1 -1
- package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-BL_qNKVd.js → wardleyDiagram-IVAIDKWA-CBUUiRQm.js} +1 -1
- package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-BHPK5Snp.js → xychartDiagram-4URNHDU4-DEVQjvgG.js} +1 -1
- package/dist/ui/dist/index.html +1 -1
- package/package.json +1 -1
- package/skills/follow-me/SKILL.md +69 -7
- package/skills/follow-me/porting.md +7 -0
- package/dist/ui/dist/assets/channel-B-fwsHf1.js +0 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-Dkjb3PCK.js +0 -1
- package/dist/ui/dist/assets/index-CBbx_pJk.js +0 -134
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DfB3XTTn.js +0 -8
package/dist/src/cli.js
CHANGED
|
@@ -5,11 +5,11 @@ import { dirname, join, relative } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { DiffParseError, formatUnitRef, isUnitId } from "./diff.js";
|
|
7
7
|
import { BranchRefusedError, branchHead, GitError, isOwnWork, signing, treeOf, writeBranch, } from "./git.js";
|
|
8
|
-
import { fetchCarried, fetchReport, findServer, followEvents, idleMs, listSessions, openBrowser, pollEvents, portFallbackMessage, postComment, postReply, probeHealth, readServerRecord, registerWith, sessionUrl, spawnDetached, startOrAttach, stateDir, stillHosted, stopServer, swapVersion, } from "./host.js";
|
|
9
|
-
import { runInstall, runUninstall, syncSkills } from "./install.js";
|
|
8
|
+
import { fetchCarried, fetchComments, fetchReport, findServer, followEvents, idleMs, listSessions, openBrowser, pollEvents, portFallbackMessage, postComment, postReply, probeHealth, readServerRecord, registerWith, sessionUrl, spawnDetached, startOrAttach, stateDir, stillHosted, stopServer, swapVersion, } from "./host.js";
|
|
9
|
+
import { findSkillDir, InstallError, runInstall, runUninstall, syncSkills, } from "./install.js";
|
|
10
10
|
import { naiveDraft } from "./naive.js";
|
|
11
|
-
import { isFields, serializePlanFile, validatePlan } from "./plan.js";
|
|
12
|
-
import { renderEvents, renderPrompt, renderReport, reportItems, } from "./prompt.js";
|
|
11
|
+
import { isCommentId, isConcernId, isFields, serializePlanFile, validatePlan, } from "./plan.js";
|
|
12
|
+
import { renderEvents, renderPrompt, renderReport, renderShownComment, renderShownConcern, renderShownUnit, reportItems, } from "./prompt.js";
|
|
13
13
|
import { RegistrationError, sessionIdOf } from "./server.js";
|
|
14
14
|
import { BranchMovedError, cleanSessions, extractSession, MissingDraftError, NotARepositoryError, openSession, SessionFileError, SessionLockedError, sinceLastPlan, } from "./session.js";
|
|
15
15
|
import { checkSyntax } from "./syntax.js";
|
|
@@ -30,6 +30,9 @@ Other commands:
|
|
|
30
30
|
Mermaid source
|
|
31
31
|
explain [--commit <n>] [--unit <id>] [--lines <a-b>] --body <text> [--diagram <file>]
|
|
32
32
|
start a thread on the page without being asked
|
|
33
|
+
show <h_…|c_…|k_…|path:line[@n|@base]> print a unit, a comment's thread, a concern, or the
|
|
34
|
+
lines around path:line in commit n, instead of
|
|
35
|
+
reading files
|
|
33
36
|
report [--json] [--markdown] print the report of the latest review; --json lists
|
|
34
37
|
the items to post to a merge request
|
|
35
38
|
|
|
@@ -82,6 +85,11 @@ Interactive loop (while the review page is open):
|
|
|
82
85
|
--lines are change lines of the unit, commits count
|
|
83
86
|
from 1; with no target it is about the whole plan.
|
|
84
87
|
watch leaves these out like your replies
|
|
88
|
+
show <h_…|c_…|k_…|path:line[@n|@base]> [--branch <name>]
|
|
89
|
+
print a unit with the commits that take it, a
|
|
90
|
+
comment with its rows and thread, a concern with its
|
|
91
|
+
lines, or the lines around path:line at the tip, in
|
|
92
|
+
commit n of the plan, or in the base
|
|
85
93
|
report [--json] [--markdown] [--branch <name>]
|
|
86
94
|
print the report of the open review, or of the last
|
|
87
95
|
one that ended. Markdown lists issues by file and
|
|
@@ -425,7 +433,7 @@ async function applyHosted(session, args, open) {
|
|
|
425
433
|
say(`follow-me review: ${sessionUrl(open.port, swapped)}`);
|
|
426
434
|
say(`the page now shows plan v${latest.version}; the comments on v${open.summary.version} are saved`);
|
|
427
435
|
}
|
|
428
|
-
next(
|
|
436
|
+
next(`run follow-me again; ${STILL_WORKING}`);
|
|
429
437
|
return 0;
|
|
430
438
|
}
|
|
431
439
|
function signMode(args) {
|
|
@@ -489,6 +497,7 @@ function promptInput(session, mode, args, changes) {
|
|
|
489
497
|
diff: session.diff,
|
|
490
498
|
draftPath: session.draftPath,
|
|
491
499
|
checkCommand: `follow-me check${branchFlag(args)}`,
|
|
500
|
+
...skillFile(),
|
|
492
501
|
...(changes === undefined ? {} : { changes }),
|
|
493
502
|
};
|
|
494
503
|
if (mode === "walk")
|
|
@@ -502,6 +511,17 @@ function promptInput(session, mode, args, changes) {
|
|
|
502
511
|
previousCommits: previous === undefined ? [] : projectedCommits(session, previous.version),
|
|
503
512
|
};
|
|
504
513
|
}
|
|
514
|
+
/** The skill this package ships, which holds the rules prompt.md points at; none if it is missing. */
|
|
515
|
+
function skillFile() {
|
|
516
|
+
try {
|
|
517
|
+
return { skillPath: join(findSkillDir(), "SKILL.md") };
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
if (error instanceof InstallError)
|
|
521
|
+
return {};
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
505
525
|
/** The commits of a frozen plan as projected now, or none when it no longer fits the branch. */
|
|
506
526
|
function projectedCommits(session, version) {
|
|
507
527
|
const plan = session.plans().find((p) => p.version === version);
|
|
@@ -917,8 +937,7 @@ function evolveHandoff(session, cwd, mode, args, moved, hosted) {
|
|
|
917
937
|
session.writeDraft(serializePlanFile(moved.draft));
|
|
918
938
|
if (draftState(session).kind === "valid")
|
|
919
939
|
return null;
|
|
920
|
-
|
|
921
|
-
session.writePrompt(renderPrompt(promptInput(session, mode, args, { since, vanished, added })));
|
|
940
|
+
session.writePrompt(renderPrompt(promptInput(session, mode, args, moved)));
|
|
922
941
|
say(`wrote ${relative(cwd, session.promptPath)}`);
|
|
923
942
|
const step = `assign the new units in ${relative(cwd, session.draftPath)}, then run follow-me again`;
|
|
924
943
|
next(hosted ? `${step}; ${STILL_WORKING}` : step);
|
|
@@ -1058,9 +1077,12 @@ async function listen(session, open, args) {
|
|
|
1058
1077
|
const news = fresh.filter(isNews);
|
|
1059
1078
|
if (news.length > 0) {
|
|
1060
1079
|
const ctx = { commits: projectedCommits(session, version), units: session.diff.units };
|
|
1061
|
-
process.stdout.write(renderEvents(news, ctx));
|
|
1062
1080
|
const ended = news.some((e) => e.type === "ended");
|
|
1063
|
-
|
|
1081
|
+
const done = ended || news.some((e) => e.type === "submitted");
|
|
1082
|
+
// The report that follows quotes every comment, so the digest only says what else happened.
|
|
1083
|
+
const shown = done ? news.filter((e) => !isCommentEvent(e)) : news;
|
|
1084
|
+
process.stdout.write(renderEvents(shown, ctx));
|
|
1085
|
+
if (done) {
|
|
1064
1086
|
say();
|
|
1065
1087
|
if (ended) {
|
|
1066
1088
|
const stored = reviewsOf(session).find((r) => r.version === version);
|
|
@@ -1259,6 +1281,9 @@ async function feedback(cwd, args) {
|
|
|
1259
1281
|
console.error("follow-me: feedback is now report; printing the same");
|
|
1260
1282
|
return report(cwd, args, args.flags.has("walk"));
|
|
1261
1283
|
}
|
|
1284
|
+
function isCommentEvent(event) {
|
|
1285
|
+
return event.type === "comment" || event.type === "comment-updated";
|
|
1286
|
+
}
|
|
1262
1287
|
/** Events the reviewer caused; the agent's own replies, explanations and version swaps are not news. */
|
|
1263
1288
|
function isNews(event) {
|
|
1264
1289
|
if (event.type === "version")
|
|
@@ -1357,7 +1382,7 @@ async function reply(cwd, args) {
|
|
|
1357
1382
|
...(diagram === undefined ? {} : { diagram }),
|
|
1358
1383
|
});
|
|
1359
1384
|
say(`replied ${id} to ${commentId}`);
|
|
1360
|
-
next(
|
|
1385
|
+
next(`run follow-me again; ${STILL_WORKING}`);
|
|
1361
1386
|
return 0;
|
|
1362
1387
|
}
|
|
1363
1388
|
/** The Mermaid source in --diagram's file, or undefined without the flag. */
|
|
@@ -1474,7 +1499,123 @@ async function explain(cwd, args) {
|
|
|
1474
1499
|
...(diagram === undefined ? {} : { diagram }),
|
|
1475
1500
|
});
|
|
1476
1501
|
say(`explained ${id}`);
|
|
1477
|
-
next(
|
|
1502
|
+
next(`run follow-me again; ${STILL_WORKING}`);
|
|
1503
|
+
return 0;
|
|
1504
|
+
}
|
|
1505
|
+
const SHOW_USAGE = "usage: follow-me show <h_…|c_…|k_…|path:line[@<commit>|@base]>";
|
|
1506
|
+
/** Every comment the session has, with the plan version it was made on: live ones first. */
|
|
1507
|
+
async function knownComments(session) {
|
|
1508
|
+
const open = await openOnServer(session, null);
|
|
1509
|
+
const live = open === null ? [] : await fetchComments(open.port, open.summary);
|
|
1510
|
+
const saved = [
|
|
1511
|
+
session.priorReview("split", null),
|
|
1512
|
+
session.priorReview("walk", null),
|
|
1513
|
+
...reviewsOf(session).map((r) => r.review),
|
|
1514
|
+
].flatMap((r) => (r === null ? [] : [{ version: r.planVersion, comments: r.comments }]));
|
|
1515
|
+
return [...live, ...saved];
|
|
1516
|
+
}
|
|
1517
|
+
const SHOW_MARGIN = 3;
|
|
1518
|
+
/**
|
|
1519
|
+
* The lines around path:line as commit `at` of the latest plan writes the file (1-based), the
|
|
1520
|
+
* base, or the tip when `at` is undefined. Read from the plan, so it works before any apply.
|
|
1521
|
+
*/
|
|
1522
|
+
function showLines(session, raw) {
|
|
1523
|
+
const m = /^(.+):(\d+)(?:@(\d+|base))?$/.exec(raw);
|
|
1524
|
+
const [, typed, lineRaw, at] = m ?? [];
|
|
1525
|
+
if (typed === undefined || lineRaw === undefined)
|
|
1526
|
+
throw new UsageError(SHOW_USAGE);
|
|
1527
|
+
const path = Buffer.from(typed, "utf8").toString("latin1");
|
|
1528
|
+
const file = [...session.diff.textFiles.values()].find((f) => f.path === path);
|
|
1529
|
+
if (file === undefined) {
|
|
1530
|
+
return `${typed} is not a text file this branch changes; it reads the same in every commit, so read it from the working tree\n`;
|
|
1531
|
+
}
|
|
1532
|
+
let text;
|
|
1533
|
+
let label;
|
|
1534
|
+
if (at === undefined) {
|
|
1535
|
+
text = file.tip?.bytes ?? null;
|
|
1536
|
+
label = "the tip";
|
|
1537
|
+
}
|
|
1538
|
+
else if (at === "base") {
|
|
1539
|
+
text = file.base === null ? null : file.base.lines.join("");
|
|
1540
|
+
label = "the base";
|
|
1541
|
+
}
|
|
1542
|
+
else {
|
|
1543
|
+
const latest = session.plans().at(-1);
|
|
1544
|
+
const commits = latest === undefined ? [] : projectedCommits(session, latest.version);
|
|
1545
|
+
const n = Number(at);
|
|
1546
|
+
if (n < 1 || n > commits.length) {
|
|
1547
|
+
throw new UsageError(`@${at} is not one of the ${commits.length} commits of the plan`);
|
|
1548
|
+
}
|
|
1549
|
+
text = file.base === null ? null : file.base.lines.join("");
|
|
1550
|
+
for (const commit of commits.slice(0, n)) {
|
|
1551
|
+
for (const change of commit.changes) {
|
|
1552
|
+
if (change.path !== path)
|
|
1553
|
+
continue;
|
|
1554
|
+
text = change.op === "write" ? change.bytes : null;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
label = `commit ${n}`;
|
|
1558
|
+
}
|
|
1559
|
+
if (text === null)
|
|
1560
|
+
return `${typed} does not exist in ${label}\n`;
|
|
1561
|
+
const lines = Buffer.from(text, "latin1").toString("utf8").split("\n");
|
|
1562
|
+
if (lines.at(-1) === "")
|
|
1563
|
+
lines.pop();
|
|
1564
|
+
const line = Number(lineRaw);
|
|
1565
|
+
if (line < 1 || line > lines.length) {
|
|
1566
|
+
return `${typed} has ${plural(lines.length, "line")} in ${label}\n`;
|
|
1567
|
+
}
|
|
1568
|
+
const from = Math.max(1, line - SHOW_MARGIN);
|
|
1569
|
+
const to = Math.min(lines.length, line + SHOW_MARGIN);
|
|
1570
|
+
const width = String(to).length;
|
|
1571
|
+
const rows = lines.slice(from - 1, to).map((l, i) => {
|
|
1572
|
+
const n = from + i;
|
|
1573
|
+
return `${n === line ? ">" : " "} ${String(n).padStart(width)} ${l}`;
|
|
1574
|
+
});
|
|
1575
|
+
return `${typed}:${line} in ${label}\n${rows.join("\n")}\n`;
|
|
1576
|
+
}
|
|
1577
|
+
/** Expands a ref the agent was given to the lines and thread it names, so no file read is needed. */
|
|
1578
|
+
async function showRef(cwd, args) {
|
|
1579
|
+
const [ref, extra] = args.positionals;
|
|
1580
|
+
if (ref === undefined || extra !== undefined)
|
|
1581
|
+
throw new UsageError(SHOW_USAGE);
|
|
1582
|
+
const session = await openSession(cwd, sessionOpts(args));
|
|
1583
|
+
const { units } = session.diff;
|
|
1584
|
+
const latest = session.plans().at(-1);
|
|
1585
|
+
if (isUnitId(ref)) {
|
|
1586
|
+
const unit = units.get(ref);
|
|
1587
|
+
if (unit === undefined) {
|
|
1588
|
+
throw new UsageError(`${ref} is not a unit of the branch now; its hunk changed`);
|
|
1589
|
+
}
|
|
1590
|
+
const commits = latest === undefined ? [] : projectedCommits(session, latest.version);
|
|
1591
|
+
process.stdout.write(renderShownUnit(unit, commits));
|
|
1592
|
+
return 0;
|
|
1593
|
+
}
|
|
1594
|
+
if (/^c_[a-z2-7]{6}$/.test(ref) && isCommentId(ref)) {
|
|
1595
|
+
for (const { version, comments } of await knownComments(session)) {
|
|
1596
|
+
const comment = comments.find((c) => c.id === ref);
|
|
1597
|
+
if (comment === undefined)
|
|
1598
|
+
continue;
|
|
1599
|
+
const ctx = { commits: projectedCommits(session, version), units };
|
|
1600
|
+
process.stdout.write(renderShownComment(comment, ctx, version));
|
|
1601
|
+
return 0;
|
|
1602
|
+
}
|
|
1603
|
+
throw new UsageError(`no comment ${ref} in this session`);
|
|
1604
|
+
}
|
|
1605
|
+
if (isConcernId(ref)) {
|
|
1606
|
+
for (const plan of session.plans().toReversed()) {
|
|
1607
|
+
const commits = projectedCommits(session, plan.version);
|
|
1608
|
+
for (const commit of commits) {
|
|
1609
|
+
const concern = commit.concerns?.find((k) => k.id === ref);
|
|
1610
|
+
if (concern === undefined)
|
|
1611
|
+
continue;
|
|
1612
|
+
process.stdout.write(renderShownConcern(concern, commit.index, { commits, units }));
|
|
1613
|
+
return 0;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
throw new UsageError(`no concern ${ref} in a plan that fits the branch`);
|
|
1617
|
+
}
|
|
1618
|
+
process.stdout.write(showLines(session, ref));
|
|
1478
1619
|
return 0;
|
|
1479
1620
|
}
|
|
1480
1621
|
function describeLock(lock) {
|
|
@@ -1711,6 +1852,8 @@ export async function main(argv, cwd) {
|
|
|
1711
1852
|
const spec = { values: ["commit", "unit", "lines", "body", "diagram", "branch"] };
|
|
1712
1853
|
return await explain(cwd, parseArgs(rest, spec));
|
|
1713
1854
|
}
|
|
1855
|
+
case "show":
|
|
1856
|
+
return await showRef(cwd, parseArgs(rest, { values: ["branch"] }));
|
|
1714
1857
|
case "report":
|
|
1715
1858
|
return await report(cwd, parseArgs(rest, { values: ["branch"], flags: ["json", "markdown"] }));
|
|
1716
1859
|
case "clean": {
|
package/dist/src/comments.js
CHANGED
|
@@ -187,7 +187,10 @@ export function parseNewDecision(concern, v) {
|
|
|
187
187
|
return "the decision is not an object";
|
|
188
188
|
return withDecision(concern, { ...v, decidedAt: undefined });
|
|
189
189
|
}
|
|
190
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Absent reads as none. The first item that does not parse names its index and reason. Items
|
|
192
|
+
* are objects so a string result can only mean a failure; string values go through parseIds.
|
|
193
|
+
*/
|
|
191
194
|
function parseList(raw, parse, what) {
|
|
192
195
|
if (raw === undefined)
|
|
193
196
|
return [];
|
|
@@ -202,8 +205,26 @@ function parseList(raw, parse, what) {
|
|
|
202
205
|
}
|
|
203
206
|
return out;
|
|
204
207
|
}
|
|
205
|
-
|
|
206
|
-
|
|
208
|
+
/** Absent reads as none. The first item that is not an id of the kind names its index. */
|
|
209
|
+
function parseIds(raw, is, what, kind) {
|
|
210
|
+
if (raw === undefined)
|
|
211
|
+
return [];
|
|
212
|
+
if (!isArray(raw))
|
|
213
|
+
return `${what} is not an array`;
|
|
214
|
+
const out = [];
|
|
215
|
+
for (const [i, item] of raw.entries()) {
|
|
216
|
+
if (!is(item))
|
|
217
|
+
return `${what}[${i}]: not a ${kind} id`;
|
|
218
|
+
out.push(item);
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
/** Keeps only the items that are ids of the kind. */
|
|
223
|
+
function lenientIds(raw, is) {
|
|
224
|
+
return isArray(raw) ? raw.filter(is) : [];
|
|
225
|
+
}
|
|
226
|
+
function isCommentIdValue(item) {
|
|
227
|
+
return typeof item === "string" && isCommentId(item);
|
|
207
228
|
}
|
|
208
229
|
/** Absent reads as nothing excluded. */
|
|
209
230
|
export function parseExcluded(v) {
|
|
@@ -211,17 +232,14 @@ export function parseExcluded(v) {
|
|
|
211
232
|
return { comments: [], concerns: [] };
|
|
212
233
|
if (!isFields(v))
|
|
213
234
|
return "excluded is not an object";
|
|
214
|
-
const comments =
|
|
235
|
+
const comments = parseIds(v.comments, isCommentIdValue, "excluded.comments", "comment");
|
|
215
236
|
if (typeof comments === "string")
|
|
216
237
|
return comments;
|
|
217
|
-
const concerns =
|
|
238
|
+
const concerns = parseIds(v.concerns, isConcernId, "excluded.concerns", "concern");
|
|
218
239
|
return typeof concerns === "string" ? concerns : { comments, concerns };
|
|
219
240
|
}
|
|
220
241
|
function asCommentId(item) {
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
function parseCommentId(item) {
|
|
224
|
-
return asCommentId(item) ?? "not a comment id";
|
|
242
|
+
return isCommentIdValue(item) ? item : null;
|
|
225
243
|
}
|
|
226
244
|
function parseEarlierReplies(v) {
|
|
227
245
|
if (!isFields(v))
|
|
@@ -286,7 +304,7 @@ export function parseReview(v) {
|
|
|
286
304
|
const concerns = parseList(v.concerns, parseDecision, "concerns");
|
|
287
305
|
if (typeof concerns === "string")
|
|
288
306
|
return concerns;
|
|
289
|
-
const resolved =
|
|
307
|
+
const resolved = parseIds(v.resolved, isCommentIdValue, "resolved", "comment");
|
|
290
308
|
if (typeof resolved === "string")
|
|
291
309
|
return resolved;
|
|
292
310
|
const excluded = parseExcluded(v.excluded);
|
|
@@ -329,7 +347,7 @@ export function parseDraft(v) {
|
|
|
329
347
|
return {
|
|
330
348
|
comments: lenient(v.comments, parseComment),
|
|
331
349
|
concerns: lenient(v.concerns, parseDecision),
|
|
332
|
-
resolved:
|
|
350
|
+
resolved: lenientIds(v.resolved, isCommentIdValue),
|
|
333
351
|
excluded: typeof excluded === "string" ? { comments: [], concerns: [] } : excluded,
|
|
334
352
|
earlierReplies: lenient(v.earlierReplies, parseEarlierReplies),
|
|
335
353
|
};
|
package/dist/src/evolve.js
CHANGED
|
@@ -90,9 +90,13 @@ function carry(comment, oldUnits, newUnits) {
|
|
|
90
90
|
const nowhere = { comment, unit: null, offsets: null, newLine: null };
|
|
91
91
|
if (anchor.kind !== "hunk")
|
|
92
92
|
return { ...nowhere, changed: false, path: comment.context.path };
|
|
93
|
+
// A comment on a whole hunk has no line; inventing one moves its card off the hunk's end.
|
|
94
|
+
const whole = anchor.changes === null && anchor.offsets === undefined;
|
|
93
95
|
const kept = newUnits.get(anchor.unit);
|
|
94
96
|
if (kept !== undefined) {
|
|
95
|
-
const newLine = kept.kind === "hunk"
|
|
97
|
+
const newLine = kept.kind === "hunk" && !whole
|
|
98
|
+
? (tipLines(kept, newUnits)[firstRow(comment, kept)] ?? null)
|
|
99
|
+
: null;
|
|
96
100
|
return {
|
|
97
101
|
comment,
|
|
98
102
|
unit: kept.id,
|
|
@@ -118,6 +122,6 @@ function carry(comment, oldUnits, newUnits) {
|
|
|
118
122
|
return {
|
|
119
123
|
...moved,
|
|
120
124
|
unit: nearest.id,
|
|
121
|
-
newLine: landing(nearest, newUnits, oldLine, old.body[row]?.text),
|
|
125
|
+
newLine: whole ? null : landing(nearest, newUnits, oldLine, old.body[row]?.text),
|
|
122
126
|
};
|
|
123
127
|
}
|
package/dist/src/harness.js
CHANGED
|
@@ -68,7 +68,10 @@ export const HARNESSES = [
|
|
|
68
68
|
export function findHarness(id) {
|
|
69
69
|
return HARNESSES.find((h) => h.id === id);
|
|
70
70
|
}
|
|
71
|
+
/** The files of the skill folder, SKILL.md first. */
|
|
72
|
+
export const SKILL_FILES = ["SKILL.md", "porting.md"];
|
|
71
73
|
export function plannedInstall(harness, scope, home, repoRoot) {
|
|
72
74
|
const root = scope === "user" ? join(home, harness.userDir) : join(repoRoot, harness.projectDir);
|
|
73
|
-
|
|
75
|
+
// porting.md is read only when the user asks to post a review, so the skill stays short.
|
|
76
|
+
return SKILL_FILES.map((source) => ({ path: join(root, SKILL_NAME, source), source }));
|
|
74
77
|
}
|
package/dist/src/host.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { closeSync, linkSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { parseCarried, parseEvent, parseEventBatch } from "./comments.js";
|
|
6
|
+
import { parseCarried, parseComment, parseEvent, parseEventBatch, } from "./comments.js";
|
|
7
7
|
import { carryComments } from "./evolve.js";
|
|
8
8
|
import { isPlanVersion, serializePlanFile, validatePlan } from "./plan.js";
|
|
9
9
|
import { HEALTH_APP, isSessionId, loadDraftComments, PortInUseError, RegistrationError, startServer, } from "./server.js";
|
|
@@ -295,6 +295,22 @@ export async function fetchCarried(port, summary) {
|
|
|
295
295
|
return [carried];
|
|
296
296
|
});
|
|
297
297
|
}
|
|
298
|
+
/** The comments of the hosted review, and of the version before a swap, with their versions. */
|
|
299
|
+
export async function fetchComments(port, summary) {
|
|
300
|
+
const state = await call(port, "GET", `${summary.url}api/state`);
|
|
301
|
+
const parse = (raw) => (Array.isArray(raw) ? raw : []).flatMap((item) => {
|
|
302
|
+
const comment = parseComment(item);
|
|
303
|
+
return typeof comment === "string" ? [] : [comment];
|
|
304
|
+
});
|
|
305
|
+
if (!isFields(state))
|
|
306
|
+
throw new RegistrationError("the server sent no state");
|
|
307
|
+
const previous = state["previous"];
|
|
308
|
+
const out = [{ version: summary.version, comments: parse(state["comments"]) }];
|
|
309
|
+
if (isFields(previous) && isPlanVersion(previous["version"])) {
|
|
310
|
+
out.push({ version: previous["version"], comments: parse(previous["comments"]) });
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
298
314
|
/** One long poll of the session's event log: what happened after `since`, or nothing at timeout. */
|
|
299
315
|
export async function pollEvents(port, summary, since, timeoutMs) {
|
|
300
316
|
const path = `${summary.url}api/events?since=${since}&timeout=${timeoutMs}`;
|
package/dist/src/install.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, rmdirSync, rmSync, writeFileSync }
|
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { findHarness, HARNESSES, plannedInstall, SKILL_NAME, } from "./harness.js";
|
|
5
|
-
class InstallError extends Error {
|
|
5
|
+
export class InstallError extends Error {
|
|
6
6
|
name = "InstallError";
|
|
7
7
|
}
|
|
8
8
|
/**
|