@spardutti/claude-skills 2.62.0 → 2.63.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/hooks/registry-lib.mjs +9 -0
- package/hooks/registry.mjs +1 -10
- package/hooks/ship-log-parse.mjs +15 -0
- package/hooks/ship-log.mjs +18 -18
- package/package.json +1 -1
package/hooks/registry-lib.mjs
CHANGED
|
@@ -62,6 +62,15 @@ export function symbolsOf(extendBy = "") {
|
|
|
62
62
|
return [...new Set([...ticked, ...shaped])];
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
export function planMarks(planPath) {
|
|
66
|
+
const text = existsSync(planPath) ? readFileSync(planPath, "utf8") : null;
|
|
67
|
+
if (text === null) return null;
|
|
68
|
+
const section = text.split(/^## /m).find((s) => /^Registry\s*$/m.test(s.split("\n")[0])) ?? "";
|
|
69
|
+
const marks = new Map();
|
|
70
|
+
for (const m of section.matchAll(/^[-*]\s*`?([\w.-]+)`?\s*:\s*(reuse|skip)\b/gim)) marks.set(m[1], m[2].toLowerCase());
|
|
71
|
+
return marks;
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
export const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
66
75
|
|
|
67
76
|
export function triggerHits(entry, text) {
|
package/hooks/registry.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import {
|
|
5
|
-
REGISTRY_SUFFIX, addedText, escape, insideLocation, loadRegistry, projectFiles, projectRoot, symbolsOf, triggerHits,
|
|
5
|
+
REGISTRY_SUFFIX, addedText, escape, insideLocation, loadRegistry, planMarks, projectFiles, projectRoot, symbolsOf, triggerHits,
|
|
6
6
|
} from "./registry-lib.mjs";
|
|
7
7
|
import { loadEditRules } from "./text-rules.mjs";
|
|
8
8
|
|
|
@@ -26,15 +26,6 @@ function list() {
|
|
|
26
26
|
stale();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function planMarks(planPath) {
|
|
30
|
-
const text = existsSync(planPath) ? readFileSync(planPath, "utf8") : null;
|
|
31
|
-
if (text === null) return null;
|
|
32
|
-
const section = text.split(/^## /m).find((s) => /^Registry\s*$/m.test(s.split("\n")[0])) ?? "";
|
|
33
|
-
const marks = new Map();
|
|
34
|
-
for (const m of section.matchAll(/^[-*]\s*`?([\w.-]+)`?\s*:\s*(reuse|skip)\b/gim)) marks.set(m[1], m[2].toLowerCase());
|
|
35
|
-
return marks;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
29
|
function checkPlan(planPath, stubs) {
|
|
39
30
|
if (!entries.length) return console.log("registry ok — no entries");
|
|
40
31
|
const marks = planMarks(planPath);
|
package/hooks/ship-log-parse.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parseLine, readLines } from "./diary-lib.mjs";
|
|
5
|
+
import { planMarks } from "./registry-lib.mjs";
|
|
3
6
|
|
|
4
7
|
const PROJECT = /^ {2}(\S.*?) (stryker|mutmut|config)(?: \((\d+)s\))? — (.*)$/;
|
|
5
8
|
const SURVIVOR = /^ {6}\[(Survived|NoCoverage)\] (\S+)\s+(\S+)$/;
|
|
@@ -100,3 +103,15 @@ export function prSize(base, testRe, sourceExt) {
|
|
|
100
103
|
}
|
|
101
104
|
return size;
|
|
102
105
|
}
|
|
106
|
+
|
|
107
|
+
export function planRegistry(root) {
|
|
108
|
+
const marks = planMarks(join(root, "PLAN.md"));
|
|
109
|
+
return marks && Object.fromEntries(marks);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Worktrees share one diary, so only this branch's blocks during the ship count.
|
|
113
|
+
export function ruleBlocks(diary, branch, since, until) {
|
|
114
|
+
return readLines(join(diary, "edit-blocks.jsonl")).map(parseLine)
|
|
115
|
+
.filter((r) => r?.branch === branch && r.ts >= since && r.ts <= until)
|
|
116
|
+
.map(({ ts, file, rules }) => ({ ts, file, rules: [].concat(rules) }));
|
|
117
|
+
}
|
package/hooks/ship-log.mjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { parseLine, readLines } from "./diary-lib.mjs";
|
|
6
|
+
import { git, parseReport, planRegistry, prSize, ruleBlocks } from "./ship-log-parse.mjs";
|
|
6
7
|
import { printReport, tally } from "./ship-log-report.mjs";
|
|
7
8
|
|
|
8
9
|
const HERE = dirname(resolve(process.argv[1]));
|
|
@@ -58,13 +59,14 @@ function settleSurvivors(record) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function finalize(
|
|
62
|
+
function finalize(c, path, record, tag, status, reason, at) {
|
|
62
63
|
settleSurvivors(record);
|
|
63
64
|
Object.assign(record, { status, reason: reason ?? null, finished: at ?? now() });
|
|
65
|
+
record.rules = ruleBlocks(c.diary, record.branch, record.started, record.finished);
|
|
64
66
|
record.summary = summarize(record);
|
|
65
67
|
const stem = `${record.app}_${record.finished.slice(0, 10)}_${tag}`;
|
|
66
|
-
let out = join(dir, `${stem}.json`);
|
|
67
|
-
for (let i = 2; existsSync(out); i++) out = join(dir, `${stem}-${i}.json`);
|
|
68
|
+
let out = join(c.dir, `${stem}.json`);
|
|
69
|
+
for (let i = 2; existsSync(out); i++) out = join(c.dir, `${stem}-${i}.json`);
|
|
68
70
|
save(path, record);
|
|
69
71
|
renameSync(path, out);
|
|
70
72
|
console.log(`ship-log: saved ${out}`);
|
|
@@ -78,8 +80,8 @@ function prOf(branch) {
|
|
|
78
80
|
// Agents skip the `pr` call, so a log whose branch has a PR by now is closed at that PR.
|
|
79
81
|
// A PR older than the log was only updated by it, so that log ends at its own last event.
|
|
80
82
|
// A long-lived branch like dev keeps its merged PRs, so an old one that is not open is not this log's.
|
|
81
|
-
function closeOpened(
|
|
82
|
-
const open = join(dir, "open");
|
|
83
|
+
function closeOpened(c, app) {
|
|
84
|
+
const open = join(c.dir, "open");
|
|
83
85
|
for (const f of existsSync(open) ? readdirSync(open).filter((n) => n.startsWith(`${app}__`)) : []) {
|
|
84
86
|
const path = join(open, f), record = JSON.parse(readFileSync(path, "utf8"));
|
|
85
87
|
const pr = prOf(record.branch), empty = !record.gate.length && !record.findings.length;
|
|
@@ -88,16 +90,16 @@ function closeOpened(dir, app) {
|
|
|
88
90
|
if (empty) continue;
|
|
89
91
|
const opened = pr && pr.createdAt >= record.started;
|
|
90
92
|
if (!pr || (!opened && pr.state !== "OPEN")) continue;
|
|
91
|
-
finalize(
|
|
93
|
+
finalize(c, path, Object.assign(record, { pr: pr.number }), `pr${pr.number}`,
|
|
92
94
|
opened ? "pr-opened" : "pr-updated", null, opened ? pr.createdAt : lastEvent(record));
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
// A ship that never reached pr or end was abandoned; keep it, but never fold it into the next one.
|
|
97
|
-
function restart(
|
|
98
|
-
closeOpened(
|
|
99
|
-
if (existsSync(path)) finalize(
|
|
100
|
-
save(path, load(path, id));
|
|
99
|
+
function restart(c, path, record, id) {
|
|
100
|
+
closeOpened(c, id.app);
|
|
101
|
+
if (existsSync(path)) finalize(c, path, record, id.branch.replace(/[^\w.-]+/g, "-"), "abandoned");
|
|
102
|
+
save(path, { ...load(path, id), registry: planRegistry(c.root) });
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
function addGateRun(record, [report, exit, seconds, base]) {
|
|
@@ -129,9 +131,7 @@ function addStep(record, [name, edge]) {
|
|
|
129
131
|
|
|
130
132
|
// Worktrees share one diary, so only this branch's latest review since the ship started counts.
|
|
131
133
|
function lastReview(diary, branch, since) {
|
|
132
|
-
const
|
|
133
|
-
const rows = existsSync(file) ? readFileSync(file, "utf8").split("\n").filter(Boolean) : [];
|
|
134
|
-
const last = rows.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
134
|
+
const last = readLines(join(diary, "review-findings.jsonl")).map(parseLine)
|
|
135
135
|
.filter((r) => r?.branch === branch && r.ts >= since && typeof r.reply === "string").at(-1);
|
|
136
136
|
const json = last?.reply.match(/```json\s*([\s\S]*?)```/)?.[1];
|
|
137
137
|
try {
|
|
@@ -161,11 +161,11 @@ function run(mode, args) {
|
|
|
161
161
|
if (!c.on) return 0;
|
|
162
162
|
const id = identity(c.root), path = openPath(c.dir, id), record = load(path, id);
|
|
163
163
|
if (mode === "start" && RELEASE.test(id.branch)) {
|
|
164
|
-
closeOpened(c
|
|
164
|
+
closeOpened(c, id.app);
|
|
165
165
|
if (existsSync(path)) rmSync(path);
|
|
166
166
|
return 0;
|
|
167
167
|
}
|
|
168
|
-
if (mode === "start") return restart(c
|
|
168
|
+
if (mode === "start") return restart(c, path, record, id), 0;
|
|
169
169
|
if (mode === "ready") {
|
|
170
170
|
const todo = existsSync(path) ? missing(record, c) : [];
|
|
171
171
|
if (todo.length) console.log(todo.map((t) => ` - ${t}`).join("\n"));
|
|
@@ -178,9 +178,9 @@ function run(mode, args) {
|
|
|
178
178
|
else if (mode === "step") addStep(record, args);
|
|
179
179
|
else if (mode === "pr" && /^\d+$/.test(args[0] ?? "")) {
|
|
180
180
|
record.pr = Number(args[0]);
|
|
181
|
-
return finalize(c
|
|
181
|
+
return finalize(c, path, record, `pr${args[0]}`, "pr-opened"), 0;
|
|
182
182
|
}
|
|
183
|
-
else if (mode === "end") return finalize(c
|
|
183
|
+
else if (mode === "end") return finalize(c, path, record, id.branch.replace(/[^\w.-]+/g, "-"), "stopped", args.join(" ") || null), 0;
|
|
184
184
|
else throw new Error("usage: ship-log.mjs on | start | ready | gate <report> <exit> <seconds> <base> | step <name> start|end | finding '<json>' | pr <number> | end [reason] | report [dir]");
|
|
185
185
|
save(path, record);
|
|
186
186
|
return 0;
|