feat: deliver v1.8.0 harness reliability and parity updates

This commit is contained in:
Affaan Mustafa
2026-03-04 14:48:06 -08:00
parent 32e9c293f0
commit 48b883d741
84 changed files with 2990 additions and 725 deletions

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env node
'use strict';
const { isHookEnabled } = require('../lib/hook-flags');
const [, , hookId, profilesCsv] = process.argv;
if (!hookId) {
process.stdout.write('yes');
process.exit(0);
}
process.stdout.write(isHookEnabled(hookId, { profiles: profilesCsv }) ? 'yes' : 'no');

78
scripts/hooks/cost-tracker.js Executable file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env node
/**
* Cost Tracker Hook
*
* Appends lightweight session usage metrics to ~/.claude/metrics/costs.jsonl.
*/
'use strict';
const path = require('path');
const {
ensureDir,
appendFile,
getClaudeDir,
} = require('../lib/utils');
const MAX_STDIN = 1024 * 1024;
let raw = '';
function toNumber(value) {
const n = Number(value);
return Number.isFinite(n) ? n : 0;
}
function estimateCost(model, inputTokens, outputTokens) {
// Approximate per-1M-token blended rates. Conservative defaults.
const table = {
'haiku': { in: 0.8, out: 4.0 },
'sonnet': { in: 3.0, out: 15.0 },
'opus': { in: 15.0, out: 75.0 },
};
const normalized = String(model || '').toLowerCase();
let rates = table.sonnet;
if (normalized.includes('haiku')) rates = table.haiku;
if (normalized.includes('opus')) rates = table.opus;
const cost = (inputTokens / 1_000_000) * rates.in + (outputTokens / 1_000_000) * rates.out;
return Math.round(cost * 1e6) / 1e6;
}
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = raw.trim() ? JSON.parse(raw) : {};
const usage = input.usage || input.token_usage || {};
const inputTokens = toNumber(usage.input_tokens || usage.prompt_tokens || 0);
const outputTokens = toNumber(usage.output_tokens || usage.completion_tokens || 0);
const model = String(input.model || input._cursor?.model || process.env.CLAUDE_MODEL || 'unknown');
const sessionId = String(process.env.CLAUDE_SESSION_ID || 'default');
const metricsDir = path.join(getClaudeDir(), 'metrics');
ensureDir(metricsDir);
const row = {
timestamp: new Date().toISOString(),
session_id: sessionId,
model,
input_tokens: inputTokens,
output_tokens: outputTokens,
estimated_cost_usd: estimateCost(model, inputTokens, outputTokens),
};
appendFile(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
} catch {
// Keep hook non-blocking.
}
process.stdout.write(raw);
});

View File

@@ -1,28 +1,63 @@
#!/usr/bin/env node
/**
* Doc file warning hook (PreToolUse - Write)
* Warns about non-standard documentation files.
* Exit code 0 always (warns only, never blocks).
*/
'use strict';
const path = require('path');
const MAX_STDIN = 1024 * 1024;
let data = '';
process.stdin.on('data', c => (data += c));
function isAllowedDocPath(filePath) {
const normalized = filePath.replace(/\\/g, '/');
const basename = path.basename(filePath);
if (!/\.(md|txt)$/i.test(filePath)) return true;
if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL|MEMORY|WORKLOG)\.md$/i.test(basename)) {
return true;
}
if (/\.claude\/(commands|plans|projects)\//.test(normalized)) {
return true;
}
if (/(^|\/)(docs|skills|\.history|memory)\//.test(normalized)) {
return true;
}
if (/\.plan\.md$/i.test(basename)) {
return true;
}
return false;
}
process.stdin.setEncoding('utf8');
process.stdin.on('data', c => {
if (data.length < MAX_STDIN) {
const remaining = MAX_STDIN - data.length;
data += c.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(data);
const filePath = input.tool_input?.file_path || '';
const filePath = String(input.tool_input?.file_path || '');
if (
/\.(md|txt)$/.test(filePath) &&
!/(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL)\.md$/i.test(filePath) &&
!/\.claude[/\\]plans[/\\]/.test(filePath) &&
!/(^|[/\\])(docs|skills|\.history)[/\\]/.test(filePath)
) {
if (filePath && !isAllowedDocPath(filePath)) {
console.error('[Hook] WARNING: Non-standard documentation file detected');
console.error('[Hook] File: ' + filePath);
console.error(`[Hook] File: ${filePath}`);
console.error('[Hook] Consider consolidating into README.md or docs/ directory');
}
} catch {
/* ignore parse errors */
// ignore parse errors
}
console.log(data);
process.stdout.write(data);
});

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env node
'use strict';
const MAX_STDIN = 1024 * 1024;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(raw);
const cmd = String(input.tool_input?.command || '');
if (/(npm run build|pnpm build|yarn build)/.test(cmd)) {
console.error('[Hook] Build completed - async analysis running in background');
}
} catch {
// ignore parse errors and pass through
}
process.stdout.write(raw);
});

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env node
'use strict';
const MAX_STDIN = 1024 * 1024;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(raw);
const cmd = String(input.tool_input?.command || '');
if (/\bgh\s+pr\s+create\b/.test(cmd)) {
const out = String(input.tool_output?.output || '');
const match = out.match(/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/);
if (match) {
const prUrl = match[0];
const repo = prUrl.replace(/https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+/, '$1');
const prNum = prUrl.replace(/.+\/pull\/(\d+)/, '$1');
console.error(`[Hook] PR created: ${prUrl}`);
console.error(`[Hook] To review: gh pr review ${prNum} --repo ${repo}`);
}
}
} catch {
// ignore parse errors and pass through
}
process.stdout.write(raw);
});

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
'use strict';
const MAX_STDIN = 1024 * 1024;
function splitShellSegments(command) {
const segments = [];
let current = '';
let quote = null;
for (let i = 0; i < command.length; i++) {
const ch = command[i];
if (quote) {
if (ch === quote) quote = null;
current += ch;
continue;
}
if (ch === '"' || ch === "'") {
quote = ch;
current += ch;
continue;
}
const next = command[i + 1] || '';
if (ch === ';' || (ch === '&' && next === '&') || (ch === '|' && next === '|') || (ch === '&' && next !== '&')) {
if (current.trim()) segments.push(current.trim());
current = '';
if ((ch === '&' && next === '&') || (ch === '|' && next === '|')) i++;
continue;
}
current += ch;
}
if (current.trim()) segments.push(current.trim());
return segments;
}
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(raw);
const cmd = String(input.tool_input?.command || '');
if (process.platform !== 'win32') {
const segments = splitShellSegments(cmd);
const tmuxLauncher = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/;
const devPattern = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn\s+dev|bun\s+run\s+dev)\b/;
const hasBlockedDev = segments.some(segment => devPattern.test(segment) && !tmuxLauncher.test(segment));
if (hasBlockedDev) {
console.error('[Hook] BLOCKED: Dev server must run in tmux for log access');
console.error('[Hook] Use: tmux new-session -d -s dev "npm run dev"');
console.error('[Hook] Then: tmux attach -t dev');
process.exit(2);
}
}
} catch {
// ignore parse errors and pass through
}
process.stdout.write(raw);
});

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env node
'use strict';
const MAX_STDIN = 1024 * 1024;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(raw);
const cmd = String(input.tool_input?.command || '');
if (/\bgit\s+push\b/.test(cmd)) {
console.error('[Hook] Review changes before push...');
console.error('[Hook] Continuing with push (remove this hook to add interactive review)');
}
} catch {
// ignore parse errors and pass through
}
process.stdout.write(raw);
});

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env node
'use strict';
const MAX_STDIN = 1024 * 1024;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(raw);
const cmd = String(input.tool_input?.command || '');
if (
process.platform !== 'win32' &&
!process.env.TMUX &&
/(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make\b|docker\b|pytest|vitest|playwright)/.test(cmd)
) {
console.error('[Hook] Consider running in tmux for session persistence');
console.error('[Hook] tmux new -s dev | tmux attach -t dev');
}
} catch {
// ignore parse errors and pass through
}
process.stdout.write(raw);
});

View File

@@ -1,61 +1,9 @@
#!/usr/bin/env node
/**
* PreToolUse Hook: Warn about non-standard documentation files
*
* Cross-platform (Windows, macOS, Linux)
*
* Runs before Write tool use. If the file is a .md or .txt file that isn't
* a standard documentation file (README, CLAUDE, AGENTS, etc.) or in an
* expected directory (docs/, skills/, .claude/plans/), warns the user.
*
* Exit code 0 — warn only, does not block.
* Backward-compatible doc warning hook entrypoint.
* Kept for consumers that still reference pre-write-doc-warn.js directly.
*/
const path = require('path');
'use strict';
const MAX_STDIN = 1024 * 1024; // 1MB limit
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (data.length < MAX_STDIN) {
const remaining = MAX_STDIN - data.length;
data += chunk.length > remaining ? chunk.slice(0, remaining) : chunk;
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(data);
const filePath = input.tool_input?.file_path || '';
// Only check .md and .txt files
if (!/\.(md|txt)$/.test(filePath)) {
process.stdout.write(data);
return;
}
// Allow standard documentation files
const basename = path.basename(filePath);
if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL)\.md$/i.test(basename)) {
process.stdout.write(data);
return;
}
// Allow files in .claude/plans/, docs/, and skills/ directories
const normalized = filePath.replace(/\\/g, '/');
if (/\.claude\/plans\//.test(normalized) || /(^|\/)(docs|skills)\//.test(normalized)) {
process.stdout.write(data);
return;
}
// Warn about non-standard documentation files
console.error('[Hook] WARNING: Non-standard documentation file detected');
console.error('[Hook] File: ' + filePath);
console.error('[Hook] Consider consolidating into README.md or docs/ directory');
} catch {
// Parse error — pass through
}
process.stdout.write(data);
});
require('./doc-file-warning.js');

98
scripts/hooks/quality-gate.js Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env node
/**
* Quality Gate Hook
*
* Runs lightweight quality checks after file edits.
* - Targets one file when file_path is provided
* - Falls back to no-op when language/tooling is unavailable
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const MAX_STDIN = 1024 * 1024;
let raw = '';
function run(command, args, cwd = process.cwd()) {
return spawnSync(command, args, {
cwd,
encoding: 'utf8',
env: process.env,
});
}
function log(msg) {
process.stderr.write(`${msg}\n`);
}
function maybeRunQualityGate(filePath) {
if (!filePath || !fs.existsSync(filePath)) {
return;
}
const ext = path.extname(filePath).toLowerCase();
const fix = String(process.env.ECC_QUALITY_GATE_FIX || '').toLowerCase() === 'true';
const strict = String(process.env.ECC_QUALITY_GATE_STRICT || '').toLowerCase() === 'true';
if (['.ts', '.tsx', '.js', '.jsx', '.json', '.md'].includes(ext)) {
// Prefer biome if present
if (fs.existsSync(path.join(process.cwd(), 'biome.json')) || fs.existsSync(path.join(process.cwd(), 'biome.jsonc'))) {
const args = ['biome', 'check', filePath];
if (fix) args.push('--write');
const result = run('npx', args);
if (result.status !== 0 && strict) {
log(`[QualityGate] Biome check failed for ${filePath}`);
}
return;
}
// Fallback to prettier when installed
const prettierArgs = ['prettier', '--check', filePath];
if (fix) {
prettierArgs[1] = '--write';
}
const prettier = run('npx', prettierArgs);
if (prettier.status !== 0 && strict) {
log(`[QualityGate] Prettier check failed for ${filePath}`);
}
return;
}
if (ext === '.go' && fix) {
run('gofmt', ['-w', filePath]);
return;
}
if (ext === '.py') {
const args = ['format'];
if (!fix) args.push('--check');
args.push(filePath);
const r = run('ruff', args);
if (r.status !== 0 && strict) {
log(`[QualityGate] Ruff check failed for ${filePath}`);
}
}
}
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
try {
const input = JSON.parse(raw);
const filePath = String(input.tool_input?.file_path || '');
maybeRunQualityGate(filePath);
} catch {
// Ignore parse errors.
}
process.stdout.write(raw);
});

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
HOOK_ID="${1:-}"
REL_SCRIPT_PATH="${2:-}"
PROFILES_CSV="${3:-standard,strict}"
# Preserve stdin for passthrough or script execution
INPUT="$(cat)"
if [[ -z "$HOOK_ID" || -z "$REL_SCRIPT_PATH" ]]; then
printf '%s' "$INPUT"
exit 0
fi
# Ask Node helper if this hook is enabled
ENABLED="$(node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/check-hook-enabled.js" "$HOOK_ID" "$PROFILES_CSV" 2>/dev/null || echo yes)"
if [[ "$ENABLED" != "yes" ]]; then
printf '%s' "$INPUT"
exit 0
fi
SCRIPT_PATH="${CLAUDE_PLUGIN_ROOT}/${REL_SCRIPT_PATH}"
if [[ ! -f "$SCRIPT_PATH" ]]; then
echo "[Hook] Script not found for ${HOOK_ID}: ${SCRIPT_PATH}" >&2
printf '%s' "$INPUT"
exit 0
fi
printf '%s' "$INPUT" | "$SCRIPT_PATH"

80
scripts/hooks/run-with-flags.js Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env node
/**
* Executes a hook script only when enabled by ECC hook profile flags.
*
* Usage:
* node run-with-flags.js <hookId> <scriptRelativePath> [profilesCsv]
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { isHookEnabled } = require('../lib/hook-flags');
const MAX_STDIN = 1024 * 1024;
function readStdinRaw() {
return new Promise(resolve => {
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => resolve(raw));
process.stdin.on('error', () => resolve(raw));
});
}
function getPluginRoot() {
if (process.env.CLAUDE_PLUGIN_ROOT && process.env.CLAUDE_PLUGIN_ROOT.trim()) {
return process.env.CLAUDE_PLUGIN_ROOT;
}
return path.resolve(__dirname, '..', '..');
}
async function main() {
const [, , hookId, relScriptPath, profilesCsv] = process.argv;
const raw = await readStdinRaw();
if (!hookId || !relScriptPath) {
process.stdout.write(raw);
process.exit(0);
}
if (!isHookEnabled(hookId, { profiles: profilesCsv })) {
process.stdout.write(raw);
process.exit(0);
}
const pluginRoot = getPluginRoot();
const scriptPath = path.join(pluginRoot, relScriptPath);
if (!fs.existsSync(scriptPath)) {
process.stderr.write(`[Hook] Script not found for ${hookId}: ${scriptPath}\n`);
process.stdout.write(raw);
process.exit(0);
}
const result = spawnSync('node', [scriptPath], {
input: raw,
encoding: 'utf8',
env: process.env,
cwd: process.cwd(),
});
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
const code = Number.isInteger(result.status) ? result.status : 0;
process.exit(code);
}
main().catch(err => {
process.stderr.write(`[Hook] run-with-flags error: ${err.message}\n`);
process.exit(0);
});

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env node
'use strict';
const MAX_STDIN = 1024 * 1024;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
process.stdout.write(raw);
});

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env node
/**
* Stop Hook (Session End) - Persist learnings when session ends
* Stop Hook (Session End) - Persist learnings during active sessions
*
* Cross-platform (Windows, macOS, Linux)
*
* Runs when Claude session ends. Extracts a meaningful summary from
* the session transcript (via stdin JSON transcript_path) and saves it
* to a session file for cross-session continuity.
* Runs on Stop events (after each response). Extracts a meaningful summary
* from the session transcript (via stdin JSON transcript_path) and updates a
* session file for cross-session continuity.
*/
const path = require('path');
@@ -23,6 +23,9 @@ const {
log
} = require('../lib/utils');
const SUMMARY_START_MARKER = '<!-- ECC:SUMMARY:START -->';
const SUMMARY_END_MARKER = '<!-- ECC:SUMMARY:END -->';
/**
* Extract a meaningful summary from the session transcript.
* Reads the JSONL transcript and pulls out key information:
@@ -167,16 +170,28 @@ async function main() {
log(`[SessionEnd] Failed to update timestamp in ${sessionFile}`);
}
// If we have a new summary, update the session file content
// If we have a new summary, update only the generated summary block.
// This keeps repeated Stop invocations idempotent and preserves
// user-authored sections in the same session file.
if (summary) {
const existing = readFile(sessionFile);
if (existing) {
// Use a flexible regex that matches both "## Session Summary" and "## Current State"
// Match to end-of-string to avoid duplicate ### Stats sections
const updatedContent = existing.replace(
/## (?:Session Summary|Current State)[\s\S]*?$/ ,
buildSummarySection(summary).trim() + '\n'
);
const summaryBlock = buildSummaryBlock(summary);
let updatedContent = existing;
if (existing.includes(SUMMARY_START_MARKER) && existing.includes(SUMMARY_END_MARKER)) {
updatedContent = existing.replace(
new RegExp(`${escapeRegExp(SUMMARY_START_MARKER)}[\\s\\S]*?${escapeRegExp(SUMMARY_END_MARKER)}`),
summaryBlock
);
} else {
// Migration path for files created before summary markers existed.
updatedContent = existing.replace(
/## (?:Session Summary|Current State)[\s\S]*?$/,
`${summaryBlock}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\`\n`
);
}
writeFile(sessionFile, updatedContent);
}
}
@@ -185,7 +200,7 @@ async function main() {
} else {
// Create new session file
const summarySection = summary
? buildSummarySection(summary)
? `${buildSummaryBlock(summary)}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``
: `## Current State\n\n[Session context goes here]\n\n### Completed\n- [ ]\n\n### In Progress\n- [ ]\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``;
const template = `# Session: ${today}
@@ -234,3 +249,10 @@ function buildSummarySection(summary) {
return section;
}
function buildSummaryBlock(summary) {
return `${SUMMARY_START_MARKER}\n${buildSummarySection(summary).trim()}\n${SUMMARY_END_MARKER}`;
}
function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}