@spardutti/claude-skills 2.52.0 → 2.53.1
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 +3 -2
- package/hooks/diary-check.mjs +37 -0
- package/hooks/diary-lib.mjs +41 -0
- package/hooks/edit-check.sh +27 -6
- package/hooks/gate-config.sh +6 -0
- package/hooks/lint-ratchet.mjs +7 -2
- package/hooks/ship-gate-structure.sh +3 -2
- package/hooks/skill-flags-record.sh +2 -2
- package/hooks/skill-report.mjs +29 -19
- package/hooks/text-rules.mjs +1 -0
- package/lib/setup-hook.mjs +6 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ npx @spardutti/claude-skills
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```text
|
|
39
|
-
Claude Skills Installer v2.
|
|
39
|
+
Claude Skills Installer v2.53.1
|
|
40
40
|
|
|
41
41
|
── Frontend ──────────────────────────────
|
|
42
42
|
◉ react ◯ tanstack-query
|
|
@@ -139,7 +139,7 @@ Portable slash commands installed to `.claude/commands/`. Some orchestrate paral
|
|
|
139
139
|
| `/handoff` | Write a handoff doc so a fresh session picks the work up — captures what auto-compaction loses: decisions made and rejected, dead ends already ruled out, why the tree is dirty, and the one next step |
|
|
140
140
|
| `/lockdown` | Per-repo supply-chain hardening — detects the package managers, Dockerfiles, and CI in use, then guides you through and applies install-time, deploy, and pipeline hardening for npm/pnpm, pip/uv, Docker, and GitHub Actions |
|
|
141
141
|
| `/add-rule` | Turn a mistake Claude keeps making into a check on every edit — a text rule with a bad and a good example from real code when a pattern can catch it, or a lesson shown only on the files it names when it needs judgment. Old hits go into a baseline and come back as "fix it if you can" notes; nothing is added to CLAUDE.md |
|
|
142
|
-
| `/skill-report` | Which installed skills earn their place in this repo — every `/ship` skills audit is logged by a hook, and this tallies it: how often each skill applied, how often it caught a violation, and the rule it catches most. Names rules that break often enough to become a script check, and skills that never flag and may be dead weight |
|
|
142
|
+
| `/skill-report` | Which installed skills earn their place in this repo — every `/ship` skills audit is logged by a hook, and this tallies it: how often each skill applied, how often it caught a violation, and the rule it catches most. Names rules that break often enough to become a script check, and skills that never flag and may be dead weight. Also ranks the rules the edit hook blocks most, by how many branches they hit — a rule Claude keeps writing wrong first is one its skill should state earlier |
|
|
143
143
|
|
|
144
144
|
## How It Works
|
|
145
145
|
|
|
@@ -198,6 +198,7 @@ scripts/ validate-skills.mjs — checks skill length caps and reference in
|
|
|
198
198
|
version-check.sh — SessionStart nudge when a newer catalog is published
|
|
199
199
|
skill-flags-record.sh — logs each /ship skills audit and /code-review; skill-report.mjs tallies them
|
|
200
200
|
edit-check.sh — PostToolUse check on each edited file: line-limit.sh, lint-ratchet.mjs, .edit-rules.json
|
|
201
|
+
diary-check.mjs — run at the end of /ship; says when the diaries hold enough for /add-rule
|
|
201
202
|
line-limit.sh — the 200-line ratchet, shared by the edit check, the ship gate and preflight
|
|
202
203
|
gauntlet-selftest.sh — behavioural tests for the hooks (runs on pre-push)
|
|
203
204
|
gauntlet-survey.sh — what the hook would do in every repo under a directory
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { claudeDir, parseLine, readLines, verdictOf } from "./diary-lib.mjs";
|
|
5
|
+
|
|
6
|
+
const DIR = claudeDir();
|
|
7
|
+
const STAMP = resolve(DIR, "add-rule-checked");
|
|
8
|
+
const TRUNKS = new Set(["main", "master", "dev", "develop", "development"]);
|
|
9
|
+
const lines = (name) => readLines(resolve(DIR, name));
|
|
10
|
+
|
|
11
|
+
function entries() {
|
|
12
|
+
const parsed = (name) => lines(name).map(parseLine).filter(Boolean);
|
|
13
|
+
const audits = parsed("skill-flags.jsonl").filter((e) => verdictOf(e.reply ?? "")?.violations?.length > 0);
|
|
14
|
+
const all = [...parsed("review-findings.jsonl"), ...parsed("gate-findings.jsonl"), ...audits];
|
|
15
|
+
return all.filter((e) => e.branch && !TRUNKS.has(e.branch));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function check() {
|
|
19
|
+
const since = existsSync(STAMP) ? readFileSync(STAMP, "utf8").trim() : "";
|
|
20
|
+
const fresh = entries().filter((e) => (e.ts ?? "") > since);
|
|
21
|
+
const branches = new Set(fresh.map((e) => e.branch));
|
|
22
|
+
if (branches.size < 2) return;
|
|
23
|
+
console.log(`${fresh.length} diary entries from ${branches.size} branches since /add-rule last looked (${since || "never"}). Offer only repeats with an entry after that.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const mode = process.argv[2];
|
|
27
|
+
if (mode === "--mark") {
|
|
28
|
+
mkdirSync(DIR, { recursive: true });
|
|
29
|
+
writeFileSync(STAMP, `${new Date().toISOString().replace(/\.\d+Z$/, "Z")}\n`);
|
|
30
|
+
} else if (mode === "--entries") {
|
|
31
|
+
for (const e of entries()) console.log(JSON.stringify(e));
|
|
32
|
+
} else if (mode === undefined) {
|
|
33
|
+
check();
|
|
34
|
+
} else {
|
|
35
|
+
console.error("usage: diary-check.mjs [--mark | --entries]");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
// Asks gate-config.sh's diary_dir, so the hooks that write the diaries and the scripts that
|
|
6
|
+
// read them share one rule. argv[1] keeps a symlinked path the way bash sees it.
|
|
7
|
+
export function claudeDir() {
|
|
8
|
+
const here = dirname(resolve(process.argv[1]));
|
|
9
|
+
const run = spawnSync("bash", ["-c", '. "$1/gate-config.sh" && diary_dir "$1"', "_", here], { encoding: "utf8" });
|
|
10
|
+
return run.stdout?.trim() || resolve(process.env.CLAUDE_PROJECT_DIR || process.cwd(), ".claude");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readLines(path) {
|
|
14
|
+
try {
|
|
15
|
+
return readFileSync(path, "utf-8").split("\n").filter(Boolean);
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseLine(line) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(line);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function verdictOf(reply) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(reply.slice(reply.indexOf("{"), reply.lastIndexOf("}") + 1));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseVerdict(line) {
|
|
38
|
+
const entry = parseLine(line);
|
|
39
|
+
const verdict = entry && verdictOf(entry.reply ?? "");
|
|
40
|
+
return verdict ? { model: entry.model, verdict } : null;
|
|
41
|
+
}
|
package/hooks/edit-check.sh
CHANGED
|
@@ -15,21 +15,42 @@ if [ -f "$HERE/gate-config.sh" ]; then
|
|
|
15
15
|
fi
|
|
16
16
|
FAILS=""
|
|
17
17
|
NOTES=""
|
|
18
|
+
BLOCKED="" # one rule id per line
|
|
18
19
|
|
|
19
|
-
run() {
|
|
20
|
-
local out rc
|
|
20
|
+
run() { # run <rule id when it fails, or empty> <command...>
|
|
21
|
+
local id="$1" out rc
|
|
22
|
+
shift
|
|
21
23
|
out=$("$@" 2>&1); rc=$?
|
|
22
24
|
[ -z "$out" ] && return
|
|
23
|
-
if [ "$rc" = 0 ]; then NOTES="$NOTES$out"$'\n'; else FAILS="$FAILS$out"$'\n'; fi
|
|
25
|
+
if [ "$rc" = 0 ]; then NOTES="$NOTES$out"$'\n'; else FAILS="$FAILS$out"$'\n'; [ -n "$id" ] && BLOCKED="$BLOCKED$id"$'\n'; fi
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
# The gate measures length from the git root, so a monorepo subfolder shares its baseline.
|
|
27
29
|
GIT_ROOT=$(git -C "$PROJECT_DIR" rev-parse --show-toplevel 2>/dev/null || printf '%s' "$PROJECT_DIR")
|
|
28
|
-
[ -f "$HERE/line-limit.sh" ] && run env LINE_LIMIT_ROOT="$GIT_ROOT" bash "$HERE/line-limit.sh" --file "$FILE"
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
[ -f "$HERE/line-limit.sh" ] && run line-limit env LINE_LIMIT_ROOT="$GIT_ROOT" bash "$HERE/line-limit.sh" --file "$FILE"
|
|
31
|
+
if [ -f "$HERE/lint-ratchet.mjs" ] && command -v node >/dev/null; then
|
|
32
|
+
IDS=$(mktemp); trap 'rm -f "$IDS"' EXIT TERM
|
|
33
|
+
lint() { cd "$PROJECT_DIR" && LINT_RATCHET_BLOCKED="$IDS" node "$HERE/lint-ratchet.mjs" --file "$FILE"; }
|
|
34
|
+
run "" lint
|
|
35
|
+
BLOCKED="$BLOCKED$(cat "$IDS")"$'\n'
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# A rule that blocks on branch after branch is one its skill should teach up front.
|
|
39
|
+
log_block() {
|
|
40
|
+
local rules diary
|
|
41
|
+
rules=$(printf '%s' "$BLOCKED" | sort -u | while IFS= read -r id; do
|
|
42
|
+
[ -n "$id" ] && printf '"%s",' "$(printf '%s' "$id" | json_string)"
|
|
43
|
+
done)
|
|
44
|
+
[ -n "$rules" ] || return 0
|
|
45
|
+
diary=$(diary_dir "$HERE")
|
|
46
|
+
mkdir -p "$diary"
|
|
47
|
+
printf '{"ts":"%s","branch":"%s","file":"%s","rules":[%s]}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
48
|
+
"$(git -C "$(dirname "$FILE")" branch --show-current 2>/dev/null | json_string)" \
|
|
49
|
+
"$(printf '%s' "${FILE#"$PROJECT_DIR"/}" | json_string)" "${rules%,}" >> "$diary/edit-blocks.jsonl"
|
|
50
|
+
}
|
|
31
51
|
|
|
32
52
|
if [ -n "$FAILS" ]; then
|
|
53
|
+
declare -F json_string >/dev/null && declare -F diary_dir >/dev/null && log_block
|
|
33
54
|
printf '%s%s' "$FAILS" "$NOTES" >&2
|
|
34
55
|
exit 2
|
|
35
56
|
fi
|
package/hooks/gate-config.sh
CHANGED
|
@@ -26,6 +26,12 @@ is_ignored() {
|
|
|
26
26
|
return 1
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
# Every diary sits in the .claude that holds the hooks writing it, so a worktree or a
|
|
30
|
+
# subfolder session still shares one set. Outside .claude/hooks (this repo's scripts/), the project.
|
|
31
|
+
diary_dir() { # diary_dir <script dir>
|
|
32
|
+
case "$1" in */.claude/hooks) dirname "$1" ;; *) printf '%s/.claude' "${CLAUDE_PROJECT_DIR:-$PWD}" ;; esac
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
json_string() {
|
|
30
36
|
awk 'BEGIN { ORS = "" } {
|
|
31
37
|
gsub(/\033\[[0-9;?]*[ -\/]*[@-~]/, "")
|
package/hooks/lint-ratchet.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { appendFileSync, existsSync, realpathSync } from "node:fs";
|
|
4
4
|
import { relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { brokenRules, loadEditRules, projectFiles, textFindings } from "./text-rules.mjs";
|
|
@@ -42,7 +42,7 @@ function eslint(rules, target) {
|
|
|
42
42
|
const results = eslintOnce(rules, target);
|
|
43
43
|
const core = rules.filter((r) => !r.id.includes("/"));
|
|
44
44
|
if (results || core.length === rules.length) return { results, ran: rules };
|
|
45
|
-
return { results: eslintOnce(core, target), ran: core };
|
|
45
|
+
return { results: core.length ? eslintOnce(core, target) : [], ran: core };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function eslintOnce(rules, target) {
|
|
@@ -84,11 +84,15 @@ function baseline(only, newOnly) {
|
|
|
84
84
|
const lint = existsSync(ESLINT) ? LINT.filter(pick) : [];
|
|
85
85
|
const text = TEXT.filter(pick);
|
|
86
86
|
if (newOnly && was.exists && lint.length + text.length === 0) return 0;
|
|
87
|
+
if (newOnly) console.log(` Recording old code as baseline debt for: ${[...lint, ...text].map((r) => r.id).join(" ")}`);
|
|
87
88
|
const { results, ran } = lint.length ? eslint(lint, ".") : { results: [], ran: [] };
|
|
88
89
|
if (!results) {
|
|
89
90
|
console.error("lint-ratchet: eslint gave no JSON; baseline not written.");
|
|
90
91
|
return 1;
|
|
91
92
|
}
|
|
93
|
+
const cannot = lint.filter((r) => !ran.includes(r)).map((r) => r.id);
|
|
94
|
+
if (cannot.length) console.log(`lint-ratchet: ESLint could not run ${cannot.join(" ")}; its plugin is not in the ESLint config.`);
|
|
95
|
+
if (newOnly && was.exists && ran.length + text.length === 0) return 0;
|
|
92
96
|
const found = [...eslintFindings(results), ...projectFiles(ROOT).flatMap((rel) => textFindings(text, ROOT, rel))];
|
|
93
97
|
const judged = new Set([...ran, ...text].map((r) => r.id));
|
|
94
98
|
const n = writeBaseline(BASELINE, mergeBaseline(was, countBy(found), judged), new Set([...was.rules, ...judged]));
|
|
@@ -120,6 +124,7 @@ function checkFile(path) {
|
|
|
120
124
|
console.log(`NOTE — ${rule.id} has no baseline yet, so its ${hits.length} finding(s) in ${rel} do not block. Run node ${SELF} --baseline-new to record old code.`);
|
|
121
125
|
} else if (hits.length > before) {
|
|
122
126
|
failed = 1;
|
|
127
|
+
if (process.env.LINT_RATCHET_BLOCKED) appendFileSync(process.env.LINT_RATCHET_BLOCKED, `${rule.id}\n`);
|
|
123
128
|
console.log(`LINT — ${rel}: ${hits.length} × ${rule.id}, was ${before}. Fix the new one(s):`);
|
|
124
129
|
for (const h of hits) console.log(` ${rel}:${h.line} ${h.message}`);
|
|
125
130
|
} else if (hits.length > 0) {
|
|
@@ -119,8 +119,9 @@ gate_exit() {
|
|
|
119
119
|
exec 1>&3 2>&4
|
|
120
120
|
# A child left running (a dev server) can hold tee open forever; give it 2s, not a hang.
|
|
121
121
|
for _ in 1 2 3 4 5 6 7 8 9 10; do [ -n "$TEE_PID" ] && kill -0 "$TEE_PID" 2>/dev/null || break; sleep 0.2; done
|
|
122
|
-
|
|
122
|
+
local diary; diary=$(diary_dir "$HERE")
|
|
123
|
+
mkdir -p "$diary"
|
|
123
124
|
printf '{"ts":"%s","branch":"%s","status":"FAIL","report":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
124
125
|
"$(git branch --show-current 2>/dev/null | json_string)" "$(json_string < "$REPORT")" \
|
|
125
|
-
>> "$
|
|
126
|
+
>> "$diary/gate-findings.jsonl"
|
|
126
127
|
}
|
|
@@ -19,9 +19,9 @@ TRANSCRIPT=$(printf '%s' "$INPUT" | grep -o '"agent_transcript_path":"[^"]*"' |
|
|
|
19
19
|
MODEL=""
|
|
20
20
|
[ -f "$TRANSCRIPT" ] && MODEL=$(grep -o '"model":"[^"]*"' "$TRANSCRIPT" | tail -1 | sed 's/.*:"//; s/"$//')
|
|
21
21
|
|
|
22
|
-
. "$SCRIPT_DIR/gate-config.sh" 2>/dev/null ||
|
|
22
|
+
. "$SCRIPT_DIR/gate-config.sh" 2>/dev/null || exit 0
|
|
23
23
|
BRANCH=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null | json_string)
|
|
24
24
|
printf '{"ts":"%s","branch":"%s","model":"%s","reply":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$BRANCH" "$MODEL" "$REPLY" \
|
|
25
|
-
>> "$
|
|
25
|
+
>> "$(diary_dir "$SCRIPT_DIR")/$LOG"
|
|
26
26
|
|
|
27
27
|
exit 0
|
package/hooks/skill-report.mjs
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { statSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { claudeDir, parseLine, parseVerdict, readLines } from "./diary-lib.mjs";
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
function parseVerdict(line) {
|
|
9
|
-
try {
|
|
10
|
-
const { model, reply } = JSON.parse(line);
|
|
11
|
-
return { model, verdict: JSON.parse(reply.slice(reply.indexOf("{"), reply.lastIndexOf("}") + 1)) };
|
|
12
|
-
} catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
6
|
+
const given = process.argv[2] && resolve(process.argv[2]);
|
|
7
|
+
const DIR = !given ? claudeDir() : statSync(given, { throwIfNoEntry: false })?.isFile() ? dirname(given) : given;
|
|
16
8
|
|
|
17
9
|
function tally(lines) {
|
|
18
10
|
const skills = new Map();
|
|
@@ -52,10 +44,28 @@ function render({ skills, models, unreadable }) {
|
|
|
52
44
|
return out.join("\n");
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
function tallyBlocks(lines) {
|
|
48
|
+
const rules = new Map();
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const entry = parseLine(line);
|
|
51
|
+
const ids = Array.isArray(entry?.rules) ? entry.rules : (entry?.rules ?? "").split(" ").filter(Boolean);
|
|
52
|
+
for (const id of ids) {
|
|
53
|
+
const r = rules.get(id) ?? { count: 0, branches: new Set() };
|
|
54
|
+
r.count += 1;
|
|
55
|
+
if (entry.branch) r.branches.add(entry.branch);
|
|
56
|
+
rules.set(id, r);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return rules;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderBlocks(rules) {
|
|
63
|
+
if (rules.size === 0) return "";
|
|
64
|
+
const rows = [...rules].sort((a, b) => b[1].branches.size - a[1].branches.size || b[1].count - a[1].count);
|
|
65
|
+
const width = Math.max(22, ...rows.map(([id]) => id.length));
|
|
66
|
+
const line = ([id, r]) => `${id.padEnd(width)} ${String(r.count).padStart(5)} ${String(r.branches.size).padStart(8)}`;
|
|
67
|
+
return ["", `${"edit-hook blocks".padEnd(width)} count branches`, ...rows.map(line)].join("\n");
|
|
60
68
|
}
|
|
61
|
-
|
|
69
|
+
|
|
70
|
+
const audits = tally(readLines(resolve(DIR, "skill-flags.jsonl")));
|
|
71
|
+
console.log(render(audits) + renderBlocks(tallyBlocks(readLines(resolve(DIR, "edit-blocks.jsonl")))));
|
package/hooks/text-rules.mjs
CHANGED
|
@@ -39,6 +39,7 @@ function walk(root, dir) {
|
|
|
39
39
|
export function brokenRules(rules) {
|
|
40
40
|
const broken = [];
|
|
41
41
|
for (const r of rules) {
|
|
42
|
+
if (/\s/.test(r.id ?? "")) broken.push(`${r.id}: its id has whitespace, which .lint-baseline cannot store`);
|
|
42
43
|
if (matchLines(r, r.bad ?? "").length === 0) broken.push(`${r.id}: its bad example is not caught`);
|
|
43
44
|
if (matchLines(r, r.good ?? "").length > 0) broken.push(`${r.id}: its good example is flagged`);
|
|
44
45
|
}
|
package/lib/setup-hook.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import { homedir } from "node:os";
|
|
|
24
24
|
// version-check.sh SessionStart — a line when a newer catalog exists
|
|
25
25
|
// skill-flags-record.sh SubagentStop — logs each skills-audit verdict
|
|
26
26
|
// skill-report.mjs read by /skill-report — tallies that log
|
|
27
|
+
// diary-check.mjs run by /ship — says when /add-rule has repeats to look at
|
|
27
28
|
// edit-check.sh PostToolUse — line-limit.sh, lint-ratchet.mjs and
|
|
28
29
|
// text-rules.mjs on each edited file
|
|
29
30
|
|
|
@@ -45,6 +46,8 @@ const LINE_LIMIT_FILENAME = "line-limit.sh";
|
|
|
45
46
|
const LINT_RATCHET_FILENAME = "lint-ratchet.mjs";
|
|
46
47
|
const TEXT_RULES_FILENAME = "text-rules.mjs";
|
|
47
48
|
const RATCHET_BASELINE_FILENAME = "ratchet-baseline.mjs";
|
|
49
|
+
const DIARY_CHECK_FILENAME = "diary-check.mjs";
|
|
50
|
+
const DIARY_LIB_FILENAME = "diary-lib.mjs";
|
|
48
51
|
const LEGACY_EVAL_FILENAME = "skill-forced-eval-hook.sh";
|
|
49
52
|
const LEGACY_AUDIT_RUNNER_FILENAME = "skill-audit-runner.sh";
|
|
50
53
|
|
|
@@ -118,9 +121,8 @@ function writeBaselines(dir, hooksDir) {
|
|
|
118
121
|
spawnSync("bash", [join(hooksDir, LINE_LIMIT_FILENAME), "--baseline"],
|
|
119
122
|
{ cwd: root, env: { ...process.env, LINE_LIMIT_ROOT: root }, stdio: "ignore" });
|
|
120
123
|
}
|
|
121
|
-
console.log(" Recording old code as baseline debt (runs ESLint once per new rule)...");
|
|
122
124
|
spawnSync(process.execPath, [join(hooksDir, LINT_RATCHET_FILENAME), "--baseline-new"],
|
|
123
|
-
{ cwd: dir, stdio: "ignore", timeout: 600_000 });
|
|
125
|
+
{ cwd: dir, stdio: ["ignore", "inherit", "ignore"], timeout: 600_000 });
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
export async function setupHook(targetDir = process.cwd()) {
|
|
@@ -148,6 +150,8 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
148
150
|
LINT_RATCHET_FILENAME,
|
|
149
151
|
TEXT_RULES_FILENAME,
|
|
150
152
|
RATCHET_BASELINE_FILENAME,
|
|
153
|
+
DIARY_CHECK_FILENAME,
|
|
154
|
+
DIARY_LIB_FILENAME,
|
|
151
155
|
]) {
|
|
152
156
|
const dest = join(hooksDir, name);
|
|
153
157
|
await writeFile(dest, await readHookSource(name), { mode: 0o755 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spardutti/claude-skills",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.53.1",
|
|
4
4
|
"description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"claude-skills": "bin/cli.mjs"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"prepack": "cp ../README.md README.md && mkdir -p hooks && cp ../scripts/skill-gate.sh ../scripts/skill-gate-automark.sh ../scripts/skill-application-gate.sh ../scripts/gauntlet.sh ../scripts/ship-gate.sh ../scripts/gate-config.sh ../scripts/ship-gate-projects.sh ../scripts/ship-gate-structure.sh ../scripts/ship-gate-files.sh ../scripts/ship-gate-hook.sh ../scripts/version-check.sh ../scripts/skill-flags-record.sh ../scripts/skill-report.mjs ../scripts/edit-check.sh ../scripts/line-limit.sh ../scripts/lint-ratchet.mjs ../scripts/text-rules.mjs ../scripts/ratchet-baseline.mjs hooks/",
|
|
14
|
+
"prepack": "cp ../README.md README.md && mkdir -p hooks && cp ../scripts/skill-gate.sh ../scripts/skill-gate-automark.sh ../scripts/skill-application-gate.sh ../scripts/gauntlet.sh ../scripts/ship-gate.sh ../scripts/gate-config.sh ../scripts/ship-gate-projects.sh ../scripts/ship-gate-structure.sh ../scripts/ship-gate-files.sh ../scripts/ship-gate-hook.sh ../scripts/version-check.sh ../scripts/skill-flags-record.sh ../scripts/skill-report.mjs ../scripts/edit-check.sh ../scripts/line-limit.sh ../scripts/lint-ratchet.mjs ../scripts/text-rules.mjs ../scripts/ratchet-baseline.mjs ../scripts/diary-check.mjs ../scripts/diary-lib.mjs hooks/",
|
|
15
15
|
"postpack": "git checkout -- README.md && rm -rf hooks"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|