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 +1 -1
- package/dist/src/daily.js +32 -3
- package/package.json +4 -4
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
"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/
|
|
43
|
-
"@homeostat/
|
|
44
|
-
"@homeostat/
|
|
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",
|