@archme/cli 0.8.1 → 0.8.2

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 (2) hide show
  1. package/dist/cli.js +79 -45
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -10005,9 +10005,23 @@ async function tailLog(opts = {}) {
10005
10005
  await new Promise((resolve6, reject) => {
10006
10006
  let fileWatcher = null;
10007
10007
  let dirWatcher = null;
10008
+ let poller = null;
10009
+ let settled = false;
10008
10010
  const dir = dirname7(path7);
10009
10011
  const name = basename(path7);
10010
- const onChange = async () => {
10012
+ const stop = () => {
10013
+ settled = true;
10014
+ fileWatcher?.close();
10015
+ dirWatcher?.close();
10016
+ if (poller) clearInterval(poller);
10017
+ process.removeListener("SIGINT", handleSigint);
10018
+ };
10019
+ const fail = (err2) => {
10020
+ if (settled) return;
10021
+ stop();
10022
+ reject(err2);
10023
+ };
10024
+ const readNew = async () => {
10011
10025
  try {
10012
10026
  const st = await stat(path7);
10013
10027
  if (st.size < offset) offset = 0;
@@ -10020,61 +10034,50 @@ async function tailLog(opts = {}) {
10020
10034
  });
10021
10035
  offset = st.size;
10022
10036
  }
10037
+ if (!fileWatcher && !settled) attachFileWatcher();
10023
10038
  } catch (err2) {
10024
10039
  if (err2.code === "ENOENT") return;
10025
- reject(err2);
10040
+ fail(err2);
10026
10041
  }
10027
10042
  };
10028
- const attachFileWatcher = () => {
10029
- try {
10030
- fileWatcher = watch(path7, (event) => {
10031
- if (event === "change") void onChange();
10032
- });
10033
- } catch (err2) {
10034
- if (err2.code !== "ENOENT") {
10035
- reject(err2);
10036
- return;
10037
- }
10038
- attachDirWatcher();
10039
- }
10043
+ let chain = Promise.resolve();
10044
+ const drain = () => {
10045
+ if (settled) return;
10046
+ chain = chain.then(readNew);
10040
10047
  };
10041
- const attachDirWatcher = () => {
10048
+ const attachFileWatcher = () => {
10042
10049
  try {
10043
- dirWatcher = watch(dir, (_event, filename) => {
10044
- if (filename === name) {
10045
- void onChange().then(() => {
10046
- dirWatcher?.close();
10047
- dirWatcher = null;
10048
- attachFileWatcher();
10049
- });
10050
- }
10051
- });
10052
- } catch (err2) {
10053
- reject(err2);
10050
+ fileWatcher = watch(path7, () => drain());
10051
+ dirWatcher?.close();
10052
+ dirWatcher = null;
10053
+ } catch {
10054
+ fileWatcher = null;
10054
10055
  }
10055
10056
  };
10056
- stat(path7).then(
10057
- () => attachFileWatcher(),
10058
- (err2) => {
10059
- if (err2.code === "ENOENT") {
10060
- attachDirWatcher();
10061
- } else {
10062
- reject(err2);
10063
- }
10064
- }
10065
- );
10057
+ try {
10058
+ dirWatcher = watch(dir, (_event, filename) => {
10059
+ if (filename === name) drain();
10060
+ });
10061
+ } catch (err2) {
10062
+ fail(err2);
10063
+ return;
10064
+ }
10066
10065
  const handleSigint = () => {
10067
- fileWatcher?.close();
10068
- dirWatcher?.close();
10066
+ if (settled) return;
10067
+ stop();
10069
10068
  resolve6();
10070
10069
  };
10071
10070
  process.once("SIGINT", handleSigint);
10071
+ poller = setInterval(drain, FOLLOW_POLL_MS);
10072
+ drain();
10072
10073
  });
10073
10074
  }
10075
+ var FOLLOW_POLL_MS;
10074
10076
  var init_logs = __esm({
10075
10077
  "src/daemon/service/logs.ts"() {
10076
10078
  "use strict";
10077
10079
  init_paths();
10080
+ FOLLOW_POLL_MS = 500;
10078
10081
  }
10079
10082
  });
10080
10083
 
@@ -13294,6 +13297,13 @@ var init_sequenceChapters = __esm({
13294
13297
  }
13295
13298
  });
13296
13299
 
13300
+ // ../shared/architectureDiff.ts
13301
+ var init_architectureDiff = __esm({
13302
+ "../shared/architectureDiff.ts"() {
13303
+ init_storyChanges();
13304
+ }
13305
+ });
13306
+
13297
13307
  // ../shared/walkthrough.ts
13298
13308
  var MAX_WALKTHROUGH_STEPS, MAX_STEP_TITLE_CHARS;
13299
13309
  var init_walkthrough = __esm({
@@ -13301,6 +13311,7 @@ var init_walkthrough = __esm({
13301
13311
  init_storyChanges();
13302
13312
  init_sequenceChapters();
13303
13313
  init_validationCodes();
13314
+ init_architectureDiff();
13304
13315
  MAX_WALKTHROUGH_STEPS = 20;
13305
13316
  MAX_STEP_TITLE_CHARS = 60;
13306
13317
  }
@@ -13317,13 +13328,15 @@ var init_walkthroughGuidance = __esm({
13317
13328
  - **Steps are in reading order, not build order.** This is not an implementation plan and not a restatement of your sequencing section \u2014 start where a reviewer should start, and put each step where it makes the next one make sense.
13318
13329
  - **Group by idea.** One step is one coherent change, usually 3\u20138 elements. A one-element step is fine and often valuable: an independent fix that would otherwise be lost among the rest. Aim for a handful of steps; the cap is ${MAX_WALKTHROUGH_STEPS}.
13319
13330
  - **Titles are terse** \u2014 a handle, not a summary. Under ${MAX_STEP_TITLE_CHARS} characters. \`Markers move per-repo\`, not \`This step explains how the source marker moves onto a per-repo table\`.
13320
- - **Description is optional and usually absent.** Never restate the components, stories or connections the step already references \u2014 the reader is looking at them. Write one only for something the diagrams cannot show: a constraint, a consequence, a trade-off that is not visible in the graph.
13321
- - **Write it for someone meeting this change for the first time.** They know the system; they have NOT read your proposal body and were not in the design discussion. Say plainly what the step does and why it is there. Two sentences is plenty.
13331
+ - **Every step gets a short description** \u2014 the narration a reader follows while the diagrams move beside it, one step at a time. Two or three plain sentences: what this part of the change is, why it exists, and how it fits the rest, at a high level. Example: \`This is the daemon picking up GitHub's answer after it posts a comment, review or approval on your behalf, and recording whether it worked.\`
13332
+ - Not prescriptive: describe the change, do not tell the reviewer what to check or what to think of it.
13333
+ - Not dense: no condensed paragraph of identifiers, flags and edge cases crammed into one sentence. Never restate the components, stories or connections the step already references \u2014 the reader is looking at them.
13334
+ - **Write it for someone meeting this change for the first time.** They know the system; they have NOT read your proposal body and were not in the design discussion. Say plainly what the step does and why it is there.
13322
13335
  - Don't argue with objections the reader hasn't raised \u2014 no \`not just a nicety\`, no \`as discussed\`, no \`this is why it is its own step\`. They don't know how the walkthrough was assembled and haven't disagreed with you. A trade-off worth stating is information (\`X was considered; it breaks Y\`), not a rebuttal.
13323
13336
  - Don't use the change's OWN new vocabulary undefined. If the step introduces a term, say what it means in the same breath or use plain words.
13324
13337
  - Bad: \`A correctness item, not a phase-3 nicety: anchors are repo-relative paths, so two repos with the same conventional layout collide silently and seed stories from the wrong one.\`
13325
13338
  - Good: \`With two repos in one project a path like src/index.ts exists in both, so looking up which component owns a changed file can return the wrong repo's. Each repo now records the container its code lives under, and lookups search only that subtree.\`
13326
- - **Cover every story the change touches.** A changed story no step references is a change the reader will step straight past.
13339
+ - **Cover every change** \u2014 every story, component and connection the change touches \u2014 or \`set_walkthrough\` rejects the walkthrough and lists what is missing. Guided mode walks the steps and nothing after them, so anything no step reaches is a change the reviewer never sees. A connection is covered when a step names it, names both of its components, or names a story page that draws it as an arrow. Name a connection inside a component as \`<component path>::<id>\` (\`daemon::session_auth->key_account\`); a root connection by its plain id.
13327
13340
  - **Skip it for a small change.** A handful of elements needs no walkthrough; omit it rather than writing a step per element.
13328
13341
  - **Prefer CHAPTERS to narrow a diagram.** If a story's sequence carries \`%% chapter: <id> | <Label>\` markers, name the ones your step covers as \`chapters: [{ story, chapter }]\`, using the **id**, never the label \u2014 ids are stable across a rename, labels are not. A chapter is a page break the story's author already wrote, so it is sharper than naming components, where one hub the step mentions decides the whole view.`;
13329
13342
  }
@@ -16129,7 +16142,9 @@ function observeGithubPull(p) {
16129
16142
  createdAt: p.created_at ?? null,
16130
16143
  updatedAt: p.updated_at ?? null,
16131
16144
  mergedAt: p.merged_at ?? null,
16132
- closedAt: p.closed_at ?? null
16145
+ closedAt: p.closed_at ?? null,
16146
+ // Before the merge it is GitHub's throwaway test merge.
16147
+ mergeCommitSha: p.merged_at ? p.merge_commit_sha ?? null : null
16133
16148
  };
16134
16149
  }
16135
16150
  function metaKey(o2) {
@@ -16164,6 +16179,7 @@ function toItem(o2, action, sent) {
16164
16179
  created_at: o2.createdAt,
16165
16180
  merged_at: o2.mergedAt,
16166
16181
  closed_at: o2.closedAt,
16182
+ merge_commit_sha: o2.mergeCommitSha,
16167
16183
  body: o2.body,
16168
16184
  ...action === "synchronize" && sent.seq !== void 0 ? { seq: sent.seq } : {}
16169
16185
  };
@@ -16573,6 +16589,22 @@ var init_defaultBranch = __esm({
16573
16589
  }
16574
16590
  });
16575
16591
 
16592
+ // ../shared/prRefs.ts
16593
+ function pushCommitSubject(message) {
16594
+ if (message == null) return null;
16595
+ const [subject = "", ...body] = message.split("\n");
16596
+ const pr = MERGE_PR_SUBJECT.exec(subject.trim());
16597
+ const title = body.find((line) => line.trim())?.trim();
16598
+ return pr && title && !TRAILER_LINE.test(title) ? `${title} (#${pr[1]})` : subject;
16599
+ }
16600
+ var MERGE_PR_SUBJECT, TRAILER_LINE;
16601
+ var init_prRefs = __esm({
16602
+ "../shared/prRefs.ts"() {
16603
+ MERGE_PR_SUBJECT = /^Merge pull request #(\d+) from \S+$/;
16604
+ TRAILER_LINE = /^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+:\s/;
16605
+ }
16606
+ });
16607
+
16576
16608
  // src/daemon/localGit.ts
16577
16609
  var localGit_exports = {};
16578
16610
  __export(localGit_exports, {
@@ -16641,13 +16673,14 @@ async function firstParentRange(dir, marker, tip) {
16641
16673
  async function readCommitMeta(dir, sha) {
16642
16674
  const { exitCode, stdout } = await git(
16643
16675
  dir,
16644
- ["show", "-s", "--format=%s%n%an%n%cI", sha],
16676
+ // %b last: it spans lines, and `pushCommitSubject` reads a merge commit's title from it.
16677
+ ["show", "-s", "--format=%an%n%cI%n%s%n%b", sha],
16645
16678
  { allowFail: true }
16646
16679
  );
16647
16680
  if (exitCode !== 0) return { message: "", authorName: null, committedAt: null };
16648
- const [message = "", authorName = "", committedAt = ""] = stdout.split("\n");
16681
+ const [authorName = "", committedAt = "", ...message] = stdout.split("\n");
16649
16682
  return {
16650
- message: message.trim(),
16683
+ message: (pushCommitSubject(message.join("\n")) ?? "").trim(),
16651
16684
  authorName: authorName.trim() || null,
16652
16685
  committedAt: committedAt.trim() || null
16653
16686
  };
@@ -16676,6 +16709,7 @@ var init_localGit = __esm({
16676
16709
  init_execa();
16677
16710
  init_preflight();
16678
16711
  init_defaultBranch();
16712
+ init_prRefs();
16679
16713
  GIT_TIMEOUT_MS = 10 * 60 * 1e3;
16680
16714
  }
16681
16715
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Map your codebase into a living architecture diagram: run `archme init` in a repo to scan it, `archme daemon start` to keep it current on every commit.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",