📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Source
|
||||
|
||||
- Repo: https://github.com/hyhmrright/brooks-lint
|
||||
- Ref: 0e92503911f28ff091b14c017d4345f7a2dd8817
|
||||
- Ref: 8501ba4411a9db67bcf42080b0380953b7fc90a9
|
||||
- Remove-Paths:
|
||||
- Snapshot: 2026-06-08
|
||||
- Snapshot: 2026-06-13
|
||||
- Sync-Mode: copy_skill_dirs
|
||||
- Notes: vendored into playbook branch thirdparty/skill
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/session-start\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.mjs\"",
|
||||
"async": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,62 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook for brooks-lint plugin
|
||||
# Injects lightweight awareness of brooks-lint into every Claude session.
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Auto-install short-form commands to ~/.claude/commands/
|
||||
# Plugin skills register as /brooks-lint:brooks-review etc.
|
||||
# These wrappers enable /brooks-review (no namespace prefix).
|
||||
# Versioned sentinel ensures files refresh on plugin upgrade.
|
||||
cmd_dir="$HOME/.claude/commands"
|
||||
plugin_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
version="$(sed -n 's/.*"version": "\(.*\)".*/\1/p' "$plugin_dir/package.json" | head -n 1)"
|
||||
if [ -z "$version" ]; then
|
||||
echo "Failed to read version from package.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
sentinel="$cmd_dir/.brooks-lint-v${version}"
|
||||
|
||||
if [ ! -f "$sentinel" ]; then
|
||||
mkdir -p "$cmd_dir"
|
||||
cp "$plugin_dir"/commands/brooks-*.md "$cmd_dir/"
|
||||
# Clean up old sentinel files and write current version
|
||||
rm -f "$cmd_dir"/.brooks-lint-v* "$cmd_dir"/.brooks-lint-installed
|
||||
touch "$sentinel"
|
||||
fi
|
||||
|
||||
# The context injected must be SHORT (<150 words).
|
||||
# Do NOT inject the full SKILL.md — it loads on demand via the Skill tool.
|
||||
context="You have the brooks-lint plugin installed. It provides six independent skills — load the relevant one via the Skill tool:
|
||||
brooks-lint:brooks-review → PR code review
|
||||
brooks-lint:brooks-audit → Architecture audit
|
||||
brooks-lint:brooks-debt → Tech debt assessment
|
||||
brooks-lint:brooks-test → Test quality review
|
||||
brooks-lint:brooks-health → Codebase health dashboard
|
||||
brooks-lint:brooks-sweep → Full sweep: analyse all dimensions and auto-fix findings
|
||||
|
||||
Triggers when the user asks to review code, discuss architecture, assess tech debt, or discuss test quality. Also triggers when the user mentions: Brooks's Law / Mythical Man-Month / conceptual integrity / second system effect / Hyrum's Law / deep modules / tactical programming / code smells / refactoring / clean architecture / DDD."
|
||||
|
||||
# Escape for JSON embedding
|
||||
escape_for_json() {
|
||||
local s="$1"
|
||||
s="${s//\\/\\\\}"
|
||||
s="${s//\"/\\\"}"
|
||||
s="${s//$'\n'/\\n}"
|
||||
s="${s//$'\r'/\\r}"
|
||||
s="${s//$'\t'/\\t}"
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
context_escaped=$(escape_for_json "$context")
|
||||
|
||||
# Output format differs by platform
|
||||
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
|
||||
printf '{\n "additional_context": "%s"\n}\n' "$context_escaped"
|
||||
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
||||
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$context_escaped"
|
||||
else
|
||||
printf '{\n "additional_context": "%s"\n}\n' "$context_escaped"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
exec node "$script_dir/session-start.mjs" "$@"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {
|
||||
copyFileSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const pluginDir = path.resolve(__dirname, "..");
|
||||
const homeDir = process.env.HOME || os.homedir();
|
||||
|
||||
function readVersion() {
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(path.join(pluginDir, "package.json"), "utf8"),
|
||||
);
|
||||
if (!packageJson.version) {
|
||||
throw new Error("Failed to read version from package.json");
|
||||
}
|
||||
return packageJson.version;
|
||||
}
|
||||
|
||||
function installCommands(version) {
|
||||
const commandDir = path.join(homeDir, ".claude", "commands");
|
||||
const sentinel = path.join(commandDir, `.brooks-lint-v${version}`);
|
||||
|
||||
try {
|
||||
readFileSync(sentinel, "utf8");
|
||||
return;
|
||||
} catch {
|
||||
// Sentinel does not exist yet.
|
||||
}
|
||||
|
||||
mkdirSync(commandDir, { recursive: true });
|
||||
|
||||
const commandsDir = path.join(pluginDir, "commands");
|
||||
for (const entry of readdirSync(commandsDir)) {
|
||||
if (/^brooks-.*\.md$/.test(entry)) {
|
||||
copyFileSync(path.join(commandsDir, entry), path.join(commandDir, entry));
|
||||
}
|
||||
}
|
||||
|
||||
for (const entry of readdirSync(commandDir)) {
|
||||
if (entry === ".brooks-lint-installed" || entry.startsWith(".brooks-lint-v")) {
|
||||
rmSync(path.join(commandDir, entry), { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(sentinel, "");
|
||||
}
|
||||
|
||||
function buildContext() {
|
||||
return [
|
||||
"You have the brooks-lint plugin installed. It provides six independent skills - load the relevant one via the Skill tool:",
|
||||
" brooks-lint:brooks-review -> PR code review",
|
||||
" brooks-lint:brooks-audit -> Architecture audit",
|
||||
" brooks-lint:brooks-debt -> Tech debt assessment",
|
||||
" brooks-lint:brooks-test -> Test quality review",
|
||||
" brooks-lint:brooks-health -> Codebase health dashboard",
|
||||
" brooks-lint:brooks-sweep -> Full sweep: analyse all dimensions and auto-fix findings",
|
||||
"",
|
||||
"Triggers when the user asks to review code, discuss architecture, assess tech debt, or discuss test quality. Also triggers when the user mentions: Brooks's Law / Mythical Man-Month / conceptual integrity / second system effect / Hyrum's Law / deep modules / tactical programming / code smells / refactoring / clean architecture / DDD.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildOutput(context) {
|
||||
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
||||
return {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "SessionStart",
|
||||
additionalContext: context,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
additional_context: context,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
installCommands(readVersion());
|
||||
process.stdout.write(`${JSON.stringify(buildOutput(buildContext()), null, 2)}\n`);
|
||||
} catch (error) {
|
||||
process.stderr.write(`${error.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
/** Canonical list of valid mode names — import from here to avoid drift. */
|
||||
export const VALID_MODES = ["review", "audit", "debt", "test", "health"];
|
||||
export const VALID_MODES = ["review", "audit", "debt", "test", "health", "sweep"];
|
||||
|
||||
/**
|
||||
* Assemble the system prompt for a given brooks-lint mode.
|
||||
@@ -25,7 +25,7 @@ export function assembleSystemPrompt(mode, skillsDir) {
|
||||
// Add risk definitions based on mode
|
||||
if (mode === "test") {
|
||||
sections.push(read(path.join(sharedDir, "test-decay-risks.md")));
|
||||
} else if (mode === "health") {
|
||||
} else if (mode === "health" || mode === "sweep") {
|
||||
sections.push(read(path.join(sharedDir, "decay-risks.md")));
|
||||
sections.push(read(path.join(sharedDir, "test-decay-risks.md")));
|
||||
} else {
|
||||
@@ -39,6 +39,7 @@ export function assembleSystemPrompt(mode, skillsDir) {
|
||||
debt: ["brooks-debt", "debt-guide.md"],
|
||||
test: ["brooks-test", "test-guide.md"],
|
||||
health: ["brooks-health", "health-guide.md"],
|
||||
sweep: ["brooks-sweep", "sweep-guide.md"],
|
||||
};
|
||||
|
||||
const [modeDir, guideFile] = guideMap[mode] ?? (() => { throw new Error(`Unknown mode: ${mode}`); })();
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
* validation run on import.
|
||||
*/
|
||||
|
||||
function normalizeNewlines(text) {
|
||||
return text.replace(/\r\n/g, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the `books:` list from a YAML frontmatter block at the top of a
|
||||
* markdown file. Returns an array of book title strings, or null if the
|
||||
@@ -23,7 +27,8 @@
|
||||
* other special characters — the only delimiter is the line break.
|
||||
*/
|
||||
export function parseFrontmatterBooks(text) {
|
||||
const match = text.match(/^---\n([\s\S]*?)\n---/);
|
||||
const normalized = normalizeNewlines(text);
|
||||
const match = normalized.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (!match) return null;
|
||||
const booksSection = match[1].match(/^books:\n((?:[ \t]+-[^\n]+\n?)+)/m);
|
||||
if (!booksSection) return null;
|
||||
@@ -38,7 +43,7 @@ export function parseFrontmatterBooks(text) {
|
||||
* Each book section uses the pattern: ## Author Name — *Book Title*
|
||||
*/
|
||||
export function countBookSections(text) {
|
||||
return (text.match(/^## .+ — \*/gm) ?? []).length;
|
||||
return (normalizeNewlines(text).match(/^## .+ — \*/gm) ?? []).length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +51,7 @@ export function countBookSections(text) {
|
||||
* Each risk section uses the pattern: ## Risk N: Title
|
||||
*/
|
||||
export function countProductionRisks(text) {
|
||||
return (text.match(/^## Risk \d+:/gm) ?? []).length;
|
||||
return (normalizeNewlines(text).match(/^## Risk \d+:/gm) ?? []).length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +59,7 @@ export function countProductionRisks(text) {
|
||||
* Each risk section uses the pattern: ## Risk TN: Title
|
||||
*/
|
||||
export function countTestRisks(text) {
|
||||
return (text.match(/^## Risk T\d+:/gm) ?? []).length;
|
||||
return (normalizeNewlines(text).match(/^## Risk T\d+:/gm) ?? []).length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +67,7 @@ export function countTestRisks(text) {
|
||||
* Returns null if no version header is found.
|
||||
*/
|
||||
export function extractChangelogVersion(text) {
|
||||
return text.match(/^## \[(.+?)\] - /m)?.[1] ?? null;
|
||||
return normalizeNewlines(text).match(/^## \[(.+?)\] - /m)?.[1] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +76,7 @@ export function extractChangelogVersion(text) {
|
||||
* Returns: ["1", "2a", "6b", ...] — the label portion only.
|
||||
*/
|
||||
export function extractGuideStepLabels(text) {
|
||||
return (text.match(/^### Step (\d+[a-z]?)/gm) ?? [])
|
||||
return (normalizeNewlines(text).match(/^### Step (\d+[a-z]?)/gm) ?? [])
|
||||
.map(m => m.replace(/^### Step /, ""));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(__dirname, "..");
|
||||
|
||||
function readText(relPath) {
|
||||
return readFileSync(path.join(root, relPath), "utf8");
|
||||
return readFileSync(path.join(root, relPath), "utf8").replace(/\r\n/g, "\n");
|
||||
}
|
||||
|
||||
function readJson(relPath) {
|
||||
@@ -302,7 +302,7 @@ function checkSecurity() {
|
||||
function checkHookOutput() {
|
||||
function runHook(env = {}) {
|
||||
const tempHome = mkdtempSync(path.join(os.tmpdir(), "brooks-lint-hook-home-"));
|
||||
const stdout = execFileSync("bash", ["hooks/session-start"], {
|
||||
const stdout = execFileSync(process.execPath, [path.join(root, "hooks", "session-start.mjs")], {
|
||||
cwd: root,
|
||||
env: { ...process.env, HOME: tempHome, ...env },
|
||||
encoding: "utf8",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import { assembleSystemPrompt, VALID_MODES } from "./assemble-prompt.mjs";
|
||||
import { readHistory, appendHistory, getTrend } from "./history.mjs";
|
||||
import {
|
||||
parseFrontmatterBooks,
|
||||
@@ -77,6 +78,11 @@ test("handles 4-space indentation", () => {
|
||||
assert.deepEqual(parseFrontmatterBooks(text), ["The Mythical Man-Month", "Code Complete"]);
|
||||
});
|
||||
|
||||
test("handles CRLF line endings", () => {
|
||||
const text = "---\r\nbooks:\r\n - The Mythical Man-Month\r\n - Code Complete\r\n---\r\n";
|
||||
assert.deepEqual(parseFrontmatterBooks(text), ["The Mythical Man-Month", "Code Complete"]);
|
||||
});
|
||||
|
||||
test("handles titles containing colons", () => {
|
||||
const text = "---\nbooks:\n - Domain-Driven Design: Tackling Complexity\n---\n";
|
||||
assert.deepEqual(parseFrontmatterBooks(text), ["Domain-Driven Design: Tackling Complexity"]);
|
||||
@@ -234,6 +240,21 @@ test("handles full pr-review-guide pattern", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// —— assembleSystemPrompt / VALID_MODES ————————————————————————————————
|
||||
|
||||
console.log("\nassembleSystemPrompt");
|
||||
|
||||
test("includes sweep in VALID_MODES", () => {
|
||||
assert.ok(VALID_MODES.includes("sweep"));
|
||||
});
|
||||
|
||||
test("assembles sweep prompt with both risk catalogs and sweep guide", () => {
|
||||
const prompt = assembleSystemPrompt("sweep", path.join(__dirname, "..", "skills"));
|
||||
assert.match(prompt, /## Risk 1: Cognitive Overload/);
|
||||
assert.match(prompt, /## Risk T1: Test Obscurity/);
|
||||
assert.match(prompt, /# Brooks-Lint .* Full Sweep Guide/);
|
||||
});
|
||||
|
||||
// ── readHistory ────────────────────────────────────────────────────────────
|
||||
|
||||
console.log("\nreadHistory");
|
||||
|
||||
Reference in New Issue
Block a user