homeostat 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -6,7 +6,7 @@ Homeostat turns review feedback that led to a code change into short, scored rul
6
6
 
7
7
  ```sh
8
8
  # In a repo for your team's knowledge base:
9
- npx --yes homeostat@0.1.3 init
9
+ npx --yes homeostat@0.1.5 init
10
10
 
11
11
  # In Claude Code:
12
12
  claude plugin marketplace add ysugarmen/homeostat
package/dist/src/daily.js CHANGED
@@ -67,6 +67,14 @@ async function daily(opts, io) {
67
67
  settled++;
68
68
  }
69
69
  }
70
+ // Lessons still waiting in an open review PR: a new lesson that repeats one waits with it.
71
+ git(root, "fetch", "--quiet", "origin", "+refs/heads/homeostat/review-*:refs/remotes/origin/homeostat/review-*");
72
+ const inReview = [...decisions.values()].flatMap((d) => d.decided.flatMap((r) => {
73
+ if (r.decision !== "review" || !r.pr || !open.has(r.pr))
74
+ return [];
75
+ const entry = parseEntry(showFile(root, remote(r.pr), r.path) ?? "").entry;
76
+ return entry ? [entry] : [];
77
+ }));
70
78
  const lessons = [];
71
79
  const decidedHashes = (m) => new Set(decisions.get(m)?.decided.map((r) => r.hash));
72
80
  const heartbeats = {};
@@ -152,7 +160,12 @@ async function daily(opts, io) {
152
160
  const mine = lessons.find((l) => l !== doc && l.kind === "doc" && l.path === doc.path && l.member === doc.member);
153
161
  if (!mine)
154
162
  continue;
155
- const merged = mergeText(onBase(doc.path) ?? "", mine.text, doc.text);
163
+ // Each side stamps the document's `updated:` line with its own day; take the later one, or
164
+ // that single line would make every pair of edits from different days conflict.
165
+ const day = (t) => /^updated: (.*)$/m.exec(t)?.[1] ?? "";
166
+ const latest = [day(mine.text), day(doc.text)].sort().at(-1) ?? "";
167
+ const stamp = (t) => (latest ? t.replace(/^updated: .*$/m, `updated: ${latest}`) : t);
168
+ const merged = mergeText(onBase(doc.path) ?? "", stamp(mine.text), stamp(doc.text));
156
169
  lessons.splice(lessons.indexOf(doc), 1);
157
170
  if (merged === undefined) {
158
171
  mine.reason = `document edits go to the owning team; a section patch from review (${doc.url ?? "a review thread"}) touched the same lines and was left out`;
@@ -177,6 +190,13 @@ async function daily(opts, io) {
177
190
  }
178
191
  for (const l of lessons)
179
192
  decide(l, files, config, inForce, groups.get(l)?.length ?? 1);
193
+ for (const { l, entry } of fresh) {
194
+ const twin = inReview.find((r) => r.frontmatter.id === entry.frontmatter.id || sameLesson(r, entry));
195
+ if (!twin || l.decision === "declined")
196
+ continue;
197
+ l.decision = "review";
198
+ l.reason = `the same lesson as ${twin.frontmatter.id}, which is waiting in review: decide them together`;
199
+ }
180
200
  for (const [l, group] of groups) {
181
201
  if (group.length < 2)
182
202
  continue;
@@ -258,8 +278,17 @@ async function daily(opts, io) {
258
278
  if (pr.pr) {
259
279
  row.reviewPr = pr.pr.url;
260
280
  const logins = reviewerLogins(config, review, member);
261
- if (logins.length)
262
- await provider.requestReviewers?.(kbRepo, pr.pr.id, logins).catch(() => undefined);
281
+ if (logins.length && provider.requestReviewers) {
282
+ try {
283
+ await provider.requestReviewers(kbRepo, pr.pr.id, logins);
284
+ io.stdout(` asked ${logins.map((l) => `@${l}`).join(", ")} to review ${pr.pr.url}\n`);
285
+ }
286
+ catch (e) {
287
+ // Usually: they are not collaborators on the KB repo. The PR still waits in the queue.
288
+ row.reviewError = `could not request ${logins.map((l) => `@${l}`).join(", ")}: ${e.message}`;
289
+ io.stdout(` ${row.reviewError}; reviewers must be collaborators on ${sources.kbRepo}\n`);
290
+ }
291
+ }
263
292
  }
264
293
  for (const l of review)
265
294
  Object.assign(l, { pr: pr.branch });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homeostat",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Turn your team's code-review lessons into rules every coding agent follows: Claude Code, MCP, Cursor, Copilot, AGENTS.md.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -39,9 +39,9 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "commander": "^15.0.0",
42
- "@homeostat/core": "0.1.3",
43
- "@homeostat/mcp": "0.1.3",
44
- "@homeostat/dashboard": "0.1.3"
42
+ "@homeostat/mcp": "0.1.5",
43
+ "@homeostat/dashboard": "0.1.5",
44
+ "@homeostat/core": "0.1.5"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public",