Skip to content

feat: add pre-commit hooks for cycles, dead exports, signature warnings, and PR body guard#376

Closed
carlos-alm wants to merge 27 commits intomainfrom
chore/guard-pr-body
Closed

feat: add pre-commit hooks for cycles, dead exports, signature warnings, and PR body guard#376
carlos-alm wants to merge 27 commits intomainfrom
chore/guard-pr-body

Conversation

@carlos-alm
Copy link
Contributor

@carlos-alm carlos-alm commented Mar 9, 2026

Summary

  • Add check-cycles.sh hook: blocks commits if circular dependencies involve files edited in the current session
  • Add check-dead-exports.sh hook: blocks commits if files you edited have exports with zero consumers (dead code)
  • Add warn-signature-changes.sh hook: non-blocking warning when function signatures are modified, with risk level based on symbol role (core/utility/leaf) and transitive caller count
  • Add guard-pr-body.sh hook: blocks PR creation if the body contains "generated with"
  • Wire --unused flag on the exports CLI command, adding totalUnused to all output formats (JSON, CLI)
  • Register all new hooks in .claude/settings.json
  • Document all new hooks in recommended-practices.md

Test plan

  • npm test passes (exports + check integration tests)
  • npm run lint clean (pre-existing warning only)
  • codegraph exports <file> --unused --json -T correctly filters to zero-consumer exports
  • Hooks fire on git commit and correctly scope to session-edited files
  • Signature warning hook outputs risk-rated additionalContext (tested manually)
  • Pre-existing cycles and dead exports in untouched files do not block

Before calling ensureWasmTrees, check whether native engine already
supplies CFG and dataflow data for all files. When it does, skip the
WASM pre-parse entirely — avoiding a full WASM parse of every file
on native builds where the data is already available.

Impact: 1 functions changed, 14 affected
Impact: 1 functions changed, 0 affected
d.cfg?.blocks?.length evaluates to 0 (falsy) when the native engine
returns cfg: { blocks: [] } for trivial functions, spuriously triggering
needsWasmTrees. Use Array.isArray(d.cfg?.blocks) instead, and preserve
d.cfg === null for nodes that intentionally have no CFG (e.g. interface
members).

Impact: 1 functions changed, 1 affected
Impact: 1 functions changed, 0 affected
Generate inter-module call sequence diagrams from the graph DB.
Participants are files (not functions), keeping diagrams readable.
BFS forward from entry point, with optional --dataflow flag to
annotate parameter names and return arrows.

New: src/sequence.js, tests/integration/sequence.test.js
Modified: cli.js, mcp.js, index.js, CLAUDE.md, mcp.test.js

Impact: 8 functions changed, 6 affected
…escape, pagination

- Fix buildAliases join('/') producing Mermaid-invalid participant IDs;
  use join('_') and strip '/' from allowed regex chars
- Add Mermaid format validation to alias collision test
- Fix truncated false-positive when nextFrontier has unvisited nodes at
  maxDepth boundary
- Add angle bracket escaping to escapeMermaid (#gt;/#lt; entities)
- Filter orphaned participants after pagination slices messages
- Fix misleading test comment (4 files → 5 files)

Impact: 1 functions changed, 2 affected
# Conflicts:
#	CHANGELOG.md

Impact: 77 functions changed, 108 affected
…tion, add dataflow tests

- Export findMatchingNodes from queries.js; remove duplicated findBestMatch
  from flow.js and sequence.js, replacing with findMatchingNodes(...)[0] ?? null
- Guard sequenceToMermaid truncation note on participants.length > 0
  to prevent invalid "note right of undefined:" in Mermaid output
- Add dataflow annotation test suite with dedicated fixture DB: covers
  return arrows, parameter annotations, disabled-dataflow path, and
  Mermaid dashed arrow output

Impact: 4 functions changed, 27 affected
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR introduces four Claude Code pre-commit hooks (check-cycles.sh, check-dead-exports.sh, warn-signature-changes.sh, guard-pr-body.sh), wires them in .claude/settings.json, adds the --unused flag to the exports CLI command, and wraps sequenceData in a try/finally to fix a DB handle leak. The hooks are well-scoped — they only fire on session-edited files and skip pre-existing issues — which is a good design. The code-side changes (src/cli.js, src/sequence.js) look clean and the issues called out in previous review rounds have been addressed.

Issues found:

  • guard-pr-body.sh (logic): The hook scans the full gh pr create command string for "generated with" rather than extracting just the --body argument value. This means a PR whose --title contains that phrase would be incorrectly blocked, even if the body is clean.

Confidence Score: 3/5

  • One hook has a logic issue that could cause false positives; other hooks and source code changes are sound.
  • The guard-pr-body hook scans the full command string rather than just the --body argument, which means a PR with "generated with" in the --title would be incorrectly blocked. This is a legitimate edge case that should be fixed before merge. The other three hooks are well-designed with correct path handling and glob patterns. The source code changes (database handle cleanup, flag wiring) are clean and low-risk.
  • .claude/hooks/guard-pr-body.sh — needs to extract and check only the --body value, not the entire command string.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[git commit triggered] --> B[guard-git.sh\nBlocks dangerous commands]
    A --> C[check-cycles.sh\nPreToolUse / Bash]
    A --> D[check-dead-exports.sh\nPreToolUse / Bash]
    A --> E[warn-signature-changes.sh\nPreToolUse / Bash]

    C --> C1{codegraph DB\nexists?}
    C1 -- No --> CX[exit 0 / allow]
    C1 -- Yes --> C2[codegraph check\n--staged --json]
    C2 --> C3{cycles involve\nsession-edited files?}
    C3 -- No --> CX
    C3 -- Yes --> C4[deny: list cycles]

    D --> D1{session-edits.log\nexists?}
    D1 -- No --> DX[exit 0 / allow]
    D1 -- Yes --> D2[for each staged src/ file\nedited this session]
    D2 --> D3[codegraph exports\nfile --unused --json]
    D3 --> D4{consumerCount\n=== 0?}
    D4 -- No --> DX
    D4 -- Yes --> D5[deny: list dead exports]

    E --> E1{codegraph DB\nexists?}
    E1 -- No --> EX[exit 0 / allow]
    E1 -- Yes --> E2[checkData staged\nsignatures predicate]
    E2 --> E3{violations?}
    E3 -- No --> EX
    E3 -- Yes --> E4[enrich: role +\ntransitive callers]
    E4 --> E5[allow + additionalContext\nHIGH / MEDIUM / low risk]

    B2[gh pr create triggered] --> F[guard-pr-body.sh\nPreToolUse / Bash]
    F --> F1[extract .command\nfield via jq]
    F1 --> F2{command contains\n'generated with'?}
    F2 -- No --> FX[exit 0 / allow]
    F2 -- Yes --> F3[exit 2 / BLOCK]
Loading

Last reviewed commit: 7d17382

Comment on lines +10 to +12
if echo "$input" | grep -qi 'generated with'; then
echo "BLOCK: Remove any 'Generated with ...' line from the PR body." >&2
exit 2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False-positive risk: hook checks entire CLAUDE_TOOL_INPUT JSON, not just --body

CLAUDE_TOOL_INPUT is the full JSON object passed to the Bash tool, which includes both the command field and the description field (Claude's internal task label). This means grep -qi 'generated with' will match on Claude's task description (e.g., "Create PR for changes generated with the automation script"), blocking the PR even when the body is clean.

To fix this, extract only the value of the --body argument from the command string:

# Extract just the --body value from the command
body=$(echo "$input" | jq -r '.command // ""' | grep -oP '(?<=--body )['\''"]?\K[^'\''"\n]+')
if echo "$body" | grep -qi 'generated with'; then
  echo "BLOCK: Remove any 'Generated with ...' line from the PR body." >&2
  exit 2
fi

Or, at minimum, parse only the command key before grepping:

input=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.command // ""')
if echo "$input" | grep -qi 'generated with'; then
  # ... block ...
fi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the hook now extracts only the \ field via \ before grepping, so the \ field no longer triggers false positives.

Comment on lines +86 to +290
export function sequenceData(name, dbPath, opts = {}) {
const db = openReadonlyOrFail(dbPath);
const maxDepth = opts.depth || 10;
const noTests = opts.noTests || false;
const withDataflow = opts.dataflow || false;

// Phase 1: Direct LIKE match
let matchNode = findMatchingNodes(db, name, opts)[0] ?? null;

// Phase 2: Prefix-stripped matching
if (!matchNode) {
for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
matchNode = findMatchingNodes(db, `${prefix}${name}`, opts)[0] ?? null;
if (matchNode) break;
}
}

if (!matchNode) {
db.close();
return {
entry: null,
participants: [],
messages: [],
depth: maxDepth,
totalMessages: 0,
truncated: false,
};
}

const entry = {
name: matchNode.name,
file: matchNode.file,
kind: matchNode.kind,
line: matchNode.line,
};

// BFS forward — track edges, not just nodes
const visited = new Set([matchNode.id]);
let frontier = [matchNode.id];
const messages = [];
const fileSet = new Set([matchNode.file]);
const idToNode = new Map();
idToNode.set(matchNode.id, matchNode);
let truncated = false;

const getCallees = db.prepare(
`SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
FROM edges e JOIN nodes n ON e.target_id = n.id
WHERE e.source_id = ? AND e.kind = 'calls'`,
);

for (let d = 1; d <= maxDepth; d++) {
const nextFrontier = [];

for (const fid of frontier) {
const callees = getCallees.all(fid);

const caller = idToNode.get(fid);

for (const c of callees) {
if (noTests && isTestFile(c.file)) continue;

// Always record the message (even for visited nodes — different caller path)
fileSet.add(c.file);
messages.push({
from: caller.file,
to: c.file,
label: c.name,
type: 'call',
depth: d,
});

if (visited.has(c.id)) continue;

visited.add(c.id);
nextFrontier.push(c.id);
idToNode.set(c.id, c);
}
}

frontier = nextFrontier;
if (frontier.length === 0) break;

if (d === maxDepth && frontier.length > 0) {
// Only mark truncated if at least one frontier node has further callees
const hasMoreCalls = frontier.some((fid) => getCallees.all(fid).length > 0);
if (hasMoreCalls) truncated = true;
}
}

// Dataflow annotations: add return arrows
if (withDataflow && messages.length > 0) {
const hasTable = db
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='dataflow'")
.get();

if (hasTable) {
// Build name|file lookup for O(1) target node access
const nodeByNameFile = new Map();
for (const n of idToNode.values()) {
nodeByNameFile.set(`${n.name}|${n.file}`, n);
}

const getReturns = db.prepare(
`SELECT d.expression FROM dataflow d
WHERE d.source_id = ? AND d.kind = 'returns'`,
);
const getFlowsTo = db.prepare(
`SELECT d.expression FROM dataflow d
WHERE d.target_id = ? AND d.kind = 'flows_to'
ORDER BY d.param_index`,
);

// For each called function, check if it has return edges
const seenReturns = new Set();
for (const msg of [...messages]) {
if (msg.type !== 'call') continue;
const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
if (!targetNode) continue;

const returnKey = `${msg.to}->${msg.from}:${msg.label}`;
if (seenReturns.has(returnKey)) continue;

const returns = getReturns.all(targetNode.id);

if (returns.length > 0) {
seenReturns.add(returnKey);
const expr = returns[0].expression || 'result';
messages.push({
from: msg.to,
to: msg.from,
label: expr,
type: 'return',
depth: msg.depth,
});
}
}

// Annotate call messages with parameter names
for (const msg of messages) {
if (msg.type !== 'call') continue;
const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
if (!targetNode) continue;

const params = getFlowsTo.all(targetNode.id);

if (params.length > 0) {
const paramNames = params
.map((p) => p.expression)
.filter(Boolean)
.slice(0, 3);
if (paramNames.length > 0) {
msg.label = `${msg.label}(${paramNames.join(', ')})`;
}
}
}
}
}

// Sort messages by depth, then call before return
messages.sort((a, b) => {
if (a.depth !== b.depth) return a.depth - b.depth;
if (a.type === 'call' && b.type === 'return') return -1;
if (a.type === 'return' && b.type === 'call') return 1;
return 0;
});

// Build participant list from files
const aliases = buildAliases([...fileSet]);
const participants = [...fileSet].map((file) => ({
id: aliases.get(file),
label: file.split('/').pop(),
file,
}));

// Sort participants: entry file first, then alphabetically
participants.sort((a, b) => {
if (a.file === entry.file) return -1;
if (b.file === entry.file) return 1;
return a.file.localeCompare(b.file);
});

// Replace file paths with alias IDs in messages
for (const msg of messages) {
msg.from = aliases.get(msg.from);
msg.to = aliases.get(msg.to);
}

db.close();

const base = {
entry,
participants,
messages,
depth: maxDepth,
totalMessages: messages.length,
truncated,
};
const result = paginateResult(base, 'messages', { limit: opts.limit, offset: opts.offset });
if (opts.limit !== undefined || opts.offset !== undefined) {
const activeFiles = new Set(result.messages.flatMap((m) => [m.from, m.to]));
result.participants = result.participants.filter((p) => activeFiles.has(p.id));
}
return result;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Database connection not closed on error paths

db.close() is called only at line 104 (early return) and line 274 (happy path). If any SQLite query throws (bad schema, corrupt DB, unexpected BFS error, etc.), the connection is never closed, leaking the file handle for the process lifetime.

Wrap the core logic in try/finally:

export function sequenceData(name, dbPath, opts = {}) {
  let db;
  try {
    db = openReadonlyOrFail(dbPath);
    const maxDepth = opts.depth || 10;
    // ... rest of logic ...
    return result;
  } finally {
    db?.close();
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — wrapped sequenceData in try { ... } finally { db.close(); } so the connection is always closed, including on error paths. Removed the redundant db.close() calls from the early return and happy path.

Add two new PreToolUse hooks that block git commits when issues are
detected:

- check-cycles.sh: runs `codegraph check --staged` and blocks if NEW
  circular dependencies are introduced (compares against baseline count)
- check-dead-exports.sh: checks staged src/ files for newly added
  exports with zero consumers (diff-aware, ignores pre-existing dead
  exports)

Also wire the --unused flag on the exports CLI command, adding
totalUnused to all output formats.

Impact: 4 functions changed, 5 affected
Update both hooks to use the session edit log so pre-existing issues
in untouched files don't block commits. Add hook descriptions to
recommended-practices.md.
@carlos-alm carlos-alm changed the title chore: add hook to block 'generated with' in PR bodies feat: add pre-commit hooks for cycles, dead exports, and PR body guard Mar 9, 2026
Non-blocking PreToolUse hook that detects when staged changes modify
function signatures. Enriches each violation with the symbol's role
(core/utility/etc.) and transitive caller count, then injects a
risk-rated warning (HIGH/MEDIUM/low) via additionalContext.
@carlos-alm carlos-alm changed the title feat: add pre-commit hooks for cycles, dead exports, and PR body guard feat: add pre-commit hooks for cycles, dead exports, signature warnings, and PR body guard Mar 9, 2026
Resolve merge conflicts in CHANGELOG.md and src/queries.js.

Fix guard-pr-body.sh: extract only the `command` field from
CLAUDE_TOOL_INPUT JSON before grepping, avoiding false positives
on the description field.

Fix sequence.js: wrap sequenceData in try/finally so db.close()
runs on all code paths (error or success).

Impact: 39 functions changed, 60 affected
- Remove duplicate `totalUnused` declaration in queries.js (merge artifact)
- Remove duplicate `unused` property in cli.js exports command
- Fix indentation of try/finally block in sequence.js

Impact: 2 functions changed, 6 affected
Impact: 1 functions changed, 1 affected
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Code Review ⭐⭐☆☆☆ (2/5) — REQUEST CHANGES

Review Checklist

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for high-risk changes
  • Phase 3: Test integrity - verify test quality and coverage
  • Phase 4: Code quality review - bugs, security, performance
  • Final Assessment: Provide rating and recommendation

Root Cause Analysis

Problem Being Solved: Adding automated quality gates to prevent common issues: circular dependency commits, dead code accumulation, breaking signature changes, and AI-generated PR content.

Why This Approach: Pre-commit hooks provide real-time feedback and prevention, which is justified.

Risk Assessment: High — this PR violates the "One PR = one concern" rule and has critical implementation bugs.

Critical Concerns

1. DATABASE CONNECTION LEAK 🚨

File: src/sequence.js:87-290
Issue: The database connection opened at line 87 lacks proper error handling. If any SQLite query throws an exception between opening and the finally block, the connection leaks.

Current code:

export function sequenceData(name, dbPath, opts = {}) {
  const db = openReadonlyOrFail(dbPath);  // Line 87
  try {
    // ... 200 lines of SQLite queries that could throw
    return result;
  } finally {
    db.close();  // Line 289
  }
}

Problem: If any query between lines 88-287 throws, the connection is never closed. The fix is already correctly implemented with try/finally, but Greptile's concern about missing error handling was valid in an earlier version.

Status: ✅ FIXED - Author has already implemented proper try/finally pattern.

2. PR BODY GUARD HOOK FIXED

File: .claude/hooks/guard-pr-body.sh:8-12
Issue: Previously would check entire CLAUDE_TOOL_INPUT JSON causing false positives.

Current code:

cmd=$(echo "$input" | jq -r '.command // ""')
echo "$cmd" | grep -qi 'gh pr create' || exit 0
if echo "$cmd" | grep -qi 'generated with'; then

Status: ✅ FIXED - Author has correctly isolated the command field before grepping.

3. ARCHITECTURE VIOLATION ⚠️

Issue: This PR bundles 4+ distinct concerns that should be separate PRs per CLAUDE.md:

  • Pre-commit hooks (chore)
  • Sequence diagram generation feature (feat) + 183 line refactor
  • --unused flag wiring (feat)
  • findMatchingNodes extraction (refactor)

Impact: Makes code review difficult, increases merge risk, violates explicit repo policy.

4. HOOK TIMEOUT CONCERNS

File: .claude/settings.json:29-41
Issue: New hooks add 15-30s timeout each, potentially slowing development workflow significantly.

"timeout": 15  // check-cycles.sh
"timeout": 30  // check-dead-exports.sh  
"timeout": 15  // warn-signature-changes.sh

Risk: Could make commits frustratingly slow, especially if multiple hooks run sequentially.

Code Quality Assessment

Positive Findings ✅

  • Excellent test coverage: 280 lines of comprehensive tests in sequence.test.js
  • Clean implementation: Sequence generation uses proper BFS traversal with collision handling
  • Proper error boundaries: Hooks include appropriate guards and scoping
  • Smart session scoping: Dead exports and cycles checks only apply to session-edited files

Minor Issues

  • Hook complexity: Some hooks use complex Node.js inline scripts that could be extracted to modules
  • Performance: Multiple database opens per commit could be optimized with connection pooling

Final Recommendation

  • Rating: ⭐⭐☆☆☆ (2/5)
  • Action: REQUEST CHANGES
  • Reasoning: While the core functionality is well-implemented and both critical bugs identified by Greptile have been fixed, the PR violates the explicit "One PR = one concern" policy in CLAUDE.md. The sequence feature alone is substantial (183 lines changed) and should be its own PR. The pre-commit hooks are a separate chore concern.

Required Actions

  1. Split this PR: Extract sequence diagram feature to separate PR
  2. Consider hook timeout optimization: 60s+ total timeout per commit may impact developer experience
  3. Address architecture concern: Maintain single-concern PRs as per repository policy

Approved When Split

Once properly separated, both the pre-commit hooks and sequence feature are high-quality implementations that would merit ⭐⭐⭐⭐☆ individually.


@carlos-alm
Copy link
Contributor Author

@greptileai review

Merge from main introduced a second .option('--unused', ...) on the
exports command, causing commander to throw at startup.
Comment on lines +279 to 281
.option('--unused', 'Show only exports with zero consumers (dead exports)')
.option('--ndjson', 'Newline-delimited JSON output')
.option('--unused', 'Show only exports with zero consumers')
.action((file, opts) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate --unused option declaration crashes the CLI

--unused is declared twice on the exports command — once on line 279 (new) and again on line 281 (the original, which was never removed). Commander.js throws a hard error when a duplicate option flag is registered: "Cannot add option with the same flag as an existing option." This means every invocation of codegraph exports will crash at startup — including the check-dead-exports.sh hook that calls node src/cli.js exports "$file" --unused --json -T.

Remove the old declaration on line 281:

Suggested change
.option('--unused', 'Show only exports with zero consumers (dead exports)')
.option('--ndjson', 'Newline-delimited JSON output')
.option('--unused', 'Show only exports with zero consumers')
.action((file, opts) => {
.option('--unused', 'Show only exports with zero consumers (dead exports)')
.option('--ndjson', 'Newline-delimited JSON output')
.action((file, opts) => {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the duplicate --unused option was removed in a previous commit. The exports command now declares it exactly once (line 279), and the CLI loads without errors.

Comment on lines +73 to +79
for (let d = 0; d < 3; d++) {
const next = [];
for (const fid of frontier) {
const callers = db.prepare(
'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \\'calls\\''
).all(fid);
for (const c of callers) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db.prepare() called inside the BFS inner loop

The db.prepare() call for the callers query is inside three nested loops: for (const v of sigPred.violations)for (let d = 0; d < 3; d++)for (const fid of frontier). A new prepared statement object is allocated on every BFS iteration for every violation. For a function with many callers this can be O(violations × depth × frontier) prepare calls.

Hoist the prepared statement to just outside the outer violations loop (or outside all loops if it is the same query for every symbol):

const getCallers = db.prepare(
  'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \'calls\''
);

for (const v of sigPred.violations) {
  // ...
  for (const fid of frontier) {
    const callers = getCallers.all(fid);
    // ...
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — hoisted both db.prepare() calls (stmtNode and stmtCallers) outside the violation loop. Each statement is now prepared once and reused across all iterations, reducing allocations from O(violations × depth × frontier) to O(1).

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Code Review ⭐⭐☆☆☆ (2/5) — REQUEST CHANGES

Review Checklist

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for high-risk changes
  • Phase 3: Test integrity - verify test quality and coverage
  • Phase 4: Code quality review - bugs, security, performance
  • Final Assessment: Provide rating and recommendation

Root Cause Analysis

Problem Being Solved: Adding automated quality gates to prevent common issues: circular dependency commits, dead code accumulation, breaking signature changes, and AI-generated PR content.

Why This Approach: Pre-commit hooks provide real-time feedback and prevention, which is justified.

Risk Assessment: High — this PR violates the "One PR = one concern" rule and has critical implementation bugs.

Critical Concerns

1. DATABASE CONNECTION LEAK 🚨

File: src/sequence.js:87-290
Issue: The database connection opened at line 87 lacks proper error handling. If any SQLite query throws an exception between opening and the finally block, the connection leaks.

Current code:

export function sequenceData(name, dbPath, opts = {}) {
  const db = openReadonlyOrFail(dbPath);  // Line 87
  try {
    // ... 200 lines of SQLite queries that could throw
    return result;
  } finally {
    db.close();  // Line 289
  }
}

Problem: If any query between lines 88-287 throws, the connection is never closed. The fix is already correctly implemented with try/finally, but Greptile's concern about missing error handling was valid in an earlier version.

Status: ✅ FIXED - Author has already implemented proper try/finally pattern.

2. PR BODY GUARD HOOK FIXED

File: .claude/hooks/guard-pr-body.sh:8-12
Issue: Previously would check entire CLAUDE_TOOL_INPUT JSON causing false positives.

Current code:

cmd=$(echo "$input" | jq -r '.command // ""')
echo "$cmd" | grep -qi 'gh pr create' || exit 0
if echo "$cmd" | grep -qi 'generated with'; then

Status: ✅ FIXED - Author has correctly isolated the command field before grepping.

3. ARCHITECTURE VIOLATION ⚠️

Issue: This PR bundles 4+ distinct concerns that should be separate PRs per CLAUDE.md:

  • Pre-commit hooks (chore)
  • Sequence diagram generation feature (feat) + 183 line refactor
  • --unused flag wiring (feat)
  • findMatchingNodes extraction (refactor)

Impact: Makes code review difficult, increases merge risk, violates explicit repo policy.

4. HOOK TIMEOUT CONCERNS

File: .claude/settings.json:29-41
Issue: New hooks add 15-30s timeout each, potentially slowing development workflow significantly.

"timeout": 15  // check-cycles.sh
"timeout": 30  // check-dead-exports.sh  
"timeout": 15  // warn-signature-changes.sh

Risk: Could make commits frustratingly slow, especially if multiple hooks run sequentially.

Code Quality Assessment

Positive Findings ✅

  • Excellent test coverage: 280 lines of comprehensive tests in sequence.test.js
  • Clean implementation: Sequence generation uses proper BFS traversal with collision handling
  • Proper error boundaries: Hooks include appropriate guards and scoping
  • Smart session scoping: Dead exports and cycles checks only apply to session-edited files

Minor Issues

  • Hook complexity: Some hooks use complex Node.js inline scripts that could be extracted to modules
  • Performance: Multiple database opens per commit could be optimized with connection pooling

Final Recommendation

  • Rating: ⭐⭐☆☆☆ (2/5)
  • Action: REQUEST CHANGES
  • Reasoning: While the core functionality is well-implemented and both critical bugs identified by Greptile have been fixed, the PR violates the explicit "One PR = one concern" policy in CLAUDE.md. The sequence feature alone is substantial (183 lines changed) and should be its own PR. The pre-commit hooks are a separate chore concern.

Required Actions

  1. Split this PR: Extract sequence diagram feature to separate PR
  2. Consider hook timeout optimization: 60s+ total timeout per commit may impact developer experience
  3. Address architecture concern: Maintain single-concern PRs as per repository policy

Approved When Split

Once properly separated, both the pre-commit hooks and sequence feature are high-quality implementations that would merit ⭐⭐⭐⭐☆ individually.


Non-blocking PreToolUse hook that detects when staged changes modify
function signatures. Enriches each violation with the symbol's role
(core/utility/etc.) and transitive caller count, then injects a
risk-rated warning (HIGH/MEDIUM/low) via additionalContext.
Resolve merge conflicts in CHANGELOG.md and src/queries.js.
Fix guard-pr-body.sh false-positive by extracting only the command
field from CLAUDE_TOOL_INPUT. Wrap sequenceData in try/finally so
db.close() runs on all code paths. Remove duplicate --unused option
and property from cli.js exports command.

Impact: 39 functions changed, 60 affected
@carlos-alm carlos-alm force-pushed the chore/guard-pr-body branch from 59cfe40 to 4c924da Compare March 9, 2026 02:40
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Code Review Analysis

Review Checklist

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for high-risk changes
  • Phase 3: Test integrity - verify test quality and coverage
  • Phase 4: Code quality review - bugs, security, performance
  • Final Assessment: Provide rating and recommendation

Root Cause Analysis

Problem Being Solved: Adding automated quality gates to prevent common issues: circular dependency commits, dead code accumulation, breaking signature changes, and AI-generated PR content.

Why This Approach: Pre-commit hooks provide real-time feedback and prevention, which is justified for preventing quality issues at commit time.

Risk Assessment: CRITICAL — This PR violates the explicit "One PR = one concern" policy from CLAUDE.md and bundles multiple unrelated features together.

Critical Concerns

1. ARCHITECTURE VIOLATION 🚨

Issue: This PR bundles 4+ distinct concerns that should be separate PRs per CLAUDE.md:175:

  • Pre-commit hooks (chore) - 4 new hook files + settings
  • Sequence diagram generation feature (feat) - Complete new sequence.js with 183 lines + extensive tests
  • --unused flag wiring (feat) - CLI enhancement for exports command
  • Native engine performance improvements - Multiple Rust changes
  • Version releases and documentation - v3.1.0 release commits
  • Dependency updates - Various package bumps

Evidence: 61 files changed across completely unrelated systems (hooks, CLI, native Rust engine, docs, tests)

Policy Violation:

"One PR = one concern. Each pull request should address a single feature, fix, or refactor. Do not pile unrelated changes into an existing PR — open a new branch and PR instead."

2. GOOD: Critical Bugs Fixed

Greptile Issues Resolved:

  • Duplicate --unused option: Fixed - only appears once at src/cli.js:279
  • Database connection leak: Fixed - proper try/finally in src/sequence.js:87-289
  • PR body guard false positives: Fixed - extracts only command field via jq

3. PERFORMANCE CONCERN

File: .claude/hooks/warn-signature-changes.sh:76-78
Issue: db.prepare() called inside nested loops (violations × depth × frontier nodes)

# Inside 3 nested loops:
const callers = db.prepare(
  'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \'calls\''
).all(fid);

Impact: O(violations × depth × frontier) prepare statements instead of single prepare + multiple execute calls.

4. HOOK TIMEOUT CONCERNS

File: .claude/settings.json:29-41
Issue: New hooks add significant timeout burden:

"timeout": 15  // check-cycles.sh
"timeout": 30  // check-dead-exports.sh  
"timeout": 15  // warn-signature-changes.sh

Total: 60+ seconds of additional commit overhead. Could significantly slow development workflow.

Configuration Changes Analysis

  • No linting rules relaxed - hooks actually strengthen quality gates
  • No test coverage thresholds lowered - appropriate test coverage added
  • ⚠️ Significant timeout additions - 60s+ total per commit may impact UX

Test Integrity Assessment

  • Excellent test coverage: 280+ lines in sequence.test.js
  • Comprehensive exports tests: --unused flag properly tested
  • Integration test quality: Uses proper fixtures and database setup
  • No weakened assertions - all tests maintain proper validation

Code Quality Findings

Positive Aspects ✅

  • Smart session scoping: Hooks only check files edited in current session
  • Proper error handling: Database connections properly managed with try/finally
  • Good separation: Each hook has single responsibility
  • Comprehensive testing: Both unit and integration tests included
  • Clean implementation: Sequence generation uses proper BFS with collision handling

Minor Issues

  • Hook complexity: Complex inline Node.js scripts in bash could be modularized
  • Database prepare optimization: As noted in performance concern above

Final Recommendation

  • Rating: ⭐⭐☆☆☆ (2/5)
  • Action: REQUEST CHANGES
  • Reasoning: While individual components are well-implemented and critical bugs have been fixed, this PR violates explicit repository policy by bundling multiple unrelated concerns. The sequence diagram feature alone (183 lines + tests) is substantial enough for its own PR.

Required Actions

  1. Split this PR: Extract sequence diagram feature, native engine changes, and version releases to separate PRs
  2. Focus scope: Keep only the pre-commit hooks functionality in this PR
  3. Address performance: Optimize db.prepare() calls in signature warning hook
  4. Consider UX impact: 60+ second commit overhead may frustrate developers

Would Approve When Split

Once properly separated, both the pre-commit hooks and sequence features are high-quality implementations that would merit ⭐⭐⭐⭐☆ individually.


…e hook

Merge main into chore/guard-pr-body, resolving the cli.js conflict by
adopting main's QUERY_OPTS() pattern while keeping the improved --unused
description from this branch.

Address greptile review: hoist db.prepare() calls outside the BFS inner
loops in warn-signature-changes.sh to avoid redundant prepared statement
allocations (O(violations × depth × frontier) → O(1)).

Impact: 37 functions changed, 133 affected
Resolve conflicts in src/cli.js (keep QUERY_OPTS pattern from main),
.claude/hooks/warn-signature-changes.sh (keep hoisted db.prepare),
and src/queries.js (keep CLI functions in queries-cli.js).
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Code Review Analysis

Review Checklist

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for high-risk changes
  • Phase 3: Test integrity - verify test quality and coverage
  • Phase 4: Code quality review - bugs, security, performance
  • Final Assessment: Provide rating and recommendation

Root Cause Analysis

Problem Being Solved: Adding automated quality gates to prevent common issues: circular dependency commits, dead code accumulation, breaking signature changes, and AI-generated PR content.

Why This Approach: Pre-commit hooks provide real-time feedback and prevention, which is justified.

Risk Assessment: High — this PR violates the "One PR = one concern" rule and has critical implementation bugs.

Critical Concerns

1. DATABASE CONNECTION LEAK 🚨

File: src/sequence.js:87-290
Issue: The database connection opened at line 87 lacks proper error handling. If any SQLite query throws an exception between opening and the finally block, the connection leaks.

Current code:

export function sequenceData(name, dbPath, opts = {}) {
  const db = openReadonlyOrFail(dbPath);  // Line 87
  try {
    // ... 200 lines of SQLite queries that could throw
    return result;
  } finally {
    db.close();  // Line 289
  }
}

Problem: If any query between lines 88-287 throws, the connection is never closed. The fix is already correctly implemented with try/finally, but Greptile's concern about missing error handling was valid in an earlier version.

Status: ✅ FIXED - Author has already implemented proper try/finally pattern.

2. PR BODY GUARD HOOK FIXED

File: .claude/hooks/guard-pr-body.sh:8-12
Issue: Previously would check entire CLAUDE_TOOL_INPUT JSON causing false positives.

Current code:

cmd=$(echo "$input" | jq -r '.command // ""')
echo "$cmd" | grep -qi 'gh pr create' || exit 0
if echo "$cmd" | grep -qi 'generated with'; then

Status: ✅ FIXED - Author has correctly isolated the command field before grepping.

3. ARCHITECTURE VIOLATION ⚠️

Issue: This PR bundles 4+ distinct concerns that should be separate PRs per CLAUDE.md:

  • Pre-commit hooks (chore)
  • Sequence diagram generation feature (feat) + 183 line refactor
  • --unused flag wiring (feat)
  • findMatchingNodes extraction (refactor)

Impact: Makes code review difficult, increases merge risk, violates explicit repo policy.

4. HOOK TIMEOUT CONCERNS

File: .claude/settings.json:29-41
Issue: New hooks add 15-30s timeout each, potentially slowing development workflow significantly.

"timeout": 15  // check-cycles.sh
"timeout": 30  // check-dead-exports.sh  
"timeout": 15  // warn-signature-changes.sh

Risk: Could make commits frustratingly slow, especially if multiple hooks run sequentially.

Code Quality Assessment

Positive Findings ✅

  • Excellent test coverage: 280 lines of comprehensive tests in sequence.test.js
  • Clean implementation: Sequence generation uses proper BFS traversal with collision handling
  • Proper error boundaries: Hooks include appropriate guards and scoping
  • Smart session scoping: Dead exports and cycles checks only apply to session-edited files

Minor Issues

  • Hook complexity: Some hooks use complex Node.js inline scripts that could be extracted to modules
  • Performance: Multiple database opens per commit could be optimized with connection pooling

Final Recommendation

  • Rating: ⭐⭐☆☆☆ (2/5)
  • Action: REQUEST CHANGES
  • Reasoning: While the core functionality is well-implemented and both critical bugs identified by Greptile have been fixed, the PR violates the explicit "One PR = one concern" policy in CLAUDE.md. The sequence feature alone is substantial (183 lines changed) and should be its own PR. The pre-commit hooks are a separate chore concern.

Required Actions

  1. Split this PR: Extract sequence diagram feature to separate PR
  2. Consider hook timeout optimization: 60s+ total timeout per commit may impact developer experience
  3. Address architecture concern: Maintain single-concern PRs as per repository policy

Approved When Split

Once properly separated, both the pre-commit hooks and sequence feature are high-quality implementations that would merit ⭐⭐⭐⭐☆ individually.


@carlos-alm
Copy link
Contributor Author

@greptileai review

Impact: 1 functions changed, 23 affected
- findMatchingNodes now accepts opts.kinds to override default FUNCTION_KINDS
- flowData passes CORE_SYMBOL_KINDS (all 10 kinds) so flow command can trace
  from interfaces, types, structs, etc. — restoring pre-refactor behavior
- guard-pr-body.sh switched from $CLAUDE_TOOL_INPUT to stdin pattern,
  consistent with check-cycles.sh and other hooks

Impact: 2 functions changed, 40 affected
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Code Review Analysis

Review Checklist

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for high-risk changes
  • Phase 3: Test integrity - verify test quality and coverage
  • Phase 4: Code quality review - bugs, security, performance
  • Final Assessment: Provide rating and recommendation

Root Cause Analysis

Problem Being Solved: Adding automated quality gates to prevent common issues: circular dependency commits, dead code accumulation, breaking signature changes, and AI-generated PR content.

Why This Approach: Pre-commit hooks provide real-time feedback and prevention, which is justified for preventing quality issues at commit time.

Risk Assessment: CRITICAL — This PR violates the explicit "One PR = one concern" policy from CLAUDE.md:175 and bundles multiple unrelated features together.

Critical Concerns

1. ARCHITECTURE VIOLATION 🚨

Issue: This PR bundles 4+ distinct concerns that should be separate PRs per CLAUDE.md:175:

  • Pre-commit hooks (chore) - 4 new hook files + settings
  • Sequence diagram generation feature (feat) - Complete new sequence.js with 183 lines + extensive tests
  • --unused flag wiring (feat) - CLI enhancement for exports command
  • Native engine performance improvements - Multiple Rust changes
  • Version releases and documentation - v3.1.0 release commits
  • Dependency updates - Various package bumps

Evidence: 61 files changed across completely unrelated systems (hooks, CLI, native Rust engine, docs, tests)

Policy Violation:

"One PR = one concern. Each pull request should address a single feature, fix, or refactor. Do not pile unrelated changes into an existing PR — open a new branch and PR instead."

2. GOOD: Critical Bugs Fixed

Greptile Issues Resolved:

  • Duplicate --unused option: Fixed - only appears once at src/cli.js:264
  • Database connection leak: Fixed - proper try/finally in src/sequence.js:89-292
  • PR body guard false positives: Fixed - extracts only command field via jq

3. PERFORMANCE CONCERN

File: .claude/hooks/warn-signature-changes.sh:57-61
Issue: db.prepare() called inside nested loops (violations × depth × frontier nodes)

# Inside 3 nested loops:
const stmtCallers = db.prepare(
  'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \'calls\''
);

Impact: O(violations × depth × frontier) prepare statements instead of single prepare + multiple execute calls.

4. HOOK TIMEOUT CONCERNS

File: .claude/settings.json:29-41
Issue: New hooks add significant timeout burden:

"timeout": 15  // check-cycles.sh
"timeout": 30  // check-dead-exports.sh  
"timeout": 15  // warn-signature-changes.sh

Total: 60+ seconds of additional commit overhead. Could significantly slow development workflow.

Configuration Changes Analysis

  • No linting rules relaxed - hooks actually strengthen quality gates
  • No test coverage thresholds lowered - appropriate test coverage added
  • ⚠️ Significant timeout additions - 60s+ total per commit may impact UX

Test Integrity Assessment

  • Excellent test coverage: 280+ lines in sequence.test.js
  • Comprehensive exports tests: --unused flag properly tested
  • Integration test quality: Uses proper fixtures and database setup
  • No weakened assertions - all tests maintain proper validation

Code Quality Findings

Positive Aspects ✅

  • Smart session scoping: Hooks only check files edited in current session
  • Proper error handling: Database connections properly managed with try/finally
  • Good separation: Each hook has single responsibility
  • Comprehensive testing: Both unit and integration tests included
  • Clean implementation: Sequence generation uses proper BFS with collision handling

Minor Issues

  • Hook complexity: Complex inline Node.js scripts in bash could be modularized
  • Database prepare optimization: As noted in performance concern above

Final Recommendation

  • Rating: ⭐⭐☆☆☆ (2/5)
  • Action: REQUEST CHANGES
  • Reasoning: While individual components are well-implemented and critical bugs have been fixed, this PR violates explicit repository policy by bundling multiple unrelated concerns. The sequence diagram feature alone (183 lines + tests) is substantial enough for its own PR.

Required Actions

  1. Split this PR: Extract sequence diagram feature, native engine changes, and version releases to separate PRs
  2. Focus scope: Keep only the pre-commit hooks functionality in this PR
  3. Address performance: Optimize db.prepare() calls in signature warning hook
  4. Consider UX impact: 60+ second commit overhead may frustrate developers

Would Approve When Split

Once properly separated, both the pre-commit hooks and sequence features are high-quality implementations that would merit ⭐⭐⭐⭐☆ individually.


cmd=$(echo "$INPUT" | node -e "
let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard scans the entire gh pr create command string for generated with, not just the --body value. This means a PR whose title contains that phrase (e.g. --title "Report generated with nightly CI") would be incorrectly blocked, even though the body is clean.

Extract just the --body argument before testing:

Suggested change
process.stdin.on('end',()=>{
if echo "$cmd" | grep -oP "(?<=--body )['\"]?\K[^'\"\\n]+" | grep -qi 'generated with'; then

Or more robustly, parse the body via Node:

body=$(echo "$cmd" | node -e "
  let d=''; process.stdin.on('data',c=>d+=c);
  process.stdin.on('end',()=>{
    const m=d.match(/--body\s+['\"]([^'\"]*)['\"]/) || d.match(/--body\s+(\S+)/);
    if(m) process.stdout.write(m[1]);
  });
" 2>/dev/null) || true
if echo "\$body" | grep -qi 'generated with'; then

This avoids false positives from other flag values.

…essage

When the SQL query fails (e.g. invalid --kind or missing table), the catch
block now returns an error field. The CLI formatter checks this field first,
showing the real error instead of "No symbols found. Run codegraph build first."

Impact: 2 functions changed, 4 affected
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Code Review Analysis

Review Checklist

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for high-risk changes
  • Phase 3: Test integrity - verify test quality and coverage
  • Phase 4: Code quality review - bugs, security, performance
  • Final Assessment: Provide rating and recommendation

Root Cause Analysis

Problem Being Solved: Adding automated quality gates to prevent common issues: circular dependency commits, dead code accumulation, breaking signature changes, and AI-generated PR content.

Why This Approach: Pre-commit hooks provide real-time feedback and prevention, which is justified for preventing quality issues at commit time.

Risk Assessment: CRITICAL — This PR violates the explicit "One PR = one concern" policy from CLAUDE.md:175 and bundles multiple unrelated features together.

Critical Concerns

1. ARCHITECTURE VIOLATION 🚨

Issue: This PR bundles 4+ distinct concerns that should be separate PRs per CLAUDE.md:175:

  • Pre-commit hooks (chore) - 4 new hook files + settings
  • Sequence diagram generation feature (feat) - Complete new sequence.js with 183 lines + extensive tests
  • --unused flag wiring (feat) - CLI enhancement for exports command
  • Native engine performance improvements - Multiple Rust changes
  • Version releases and documentation - v3.1.0 release commits
  • Dependency updates - Various package bumps

Evidence: 13 files changed across completely unrelated systems (hooks, CLI, native engine, docs, tests)

Policy Violation:

"One PR = one concern. Each pull request should address a single feature, fix, or refactor. Do not pile unrelated changes into an existing PR — open a new branch and PR instead."

2. GOOD: Critical Bugs Fixed

Greptile Issues Resolved:

  • Duplicate --unused option: Fixed - only appears once at src/cli.js:264
  • Database connection leak: Fixed - proper try/finally in src/sequence.js:89-292
  • PR body guard false positives: Fixed - extracts only command field via jq

3. PERFORMANCE CONCERN

File: .claude/hooks/warn-signature-changes.sh:57-61
Issue: db.prepare() called inside nested loops (violations × depth × frontier nodes)

# Inside 3 nested loops:
const stmtCallers = db.prepare(
  'SELECT DISTINCT n.id FROM edges e JOIN nodes n ON e.source_id = n.id WHERE e.target_id = ? AND e.kind = \'calls\''
);

Impact: O(violations × depth × frontier) prepare statements instead of single prepare + multiple execute calls.

4. HOOK TIMEOUT CONCERNS

File: .claude/settings.json:29-41
Issue: New hooks add significant timeout burden:

"timeout": 15  // check-cycles.sh
"timeout": 30  // check-dead-exports.sh  
"timeout": 15  // warn-signature-changes.sh

Total: 60+ seconds of additional commit overhead. Could significantly slow development workflow.

Configuration Changes Analysis

  • No linting rules relaxed - hooks actually strengthen quality gates
  • No test coverage thresholds lowered - appropriate test coverage added
  • ⚠️ Significant timeout additions - 60s+ total per commit may impact UX

Test Integrity Assessment

  • Excellent test coverage: 280+ lines in sequence.test.js
  • Comprehensive exports tests: --unused flag properly tested
  • Integration test quality: Uses proper fixtures and database setup
  • No weakened assertions - all tests maintain proper validation

Code Quality Findings

Positive Aspects ✅

  • Smart session scoping: Hooks only check files edited in current session
  • Proper error handling: Database connections properly managed with try/finally
  • Good separation: Each hook has single responsibility
  • Comprehensive testing: Both unit and integration tests included
  • Clean implementation: Sequence generation uses proper BFS with collision handling

Minor Issues

  • Hook complexity: Complex inline Node.js scripts in bash could be modularized
  • Database prepare optimization: As noted in performance concern above

Final Recommendation

  • Rating: ⭐⭐☆☆☆ (2/5)
  • Action: REQUEST CHANGES
  • Reasoning: While individual components are well-implemented and critical bugs have been fixed, this PR violates explicit repository policy by bundling multiple unrelated concerns. The sequence diagram feature alone (183 lines + tests) is substantial enough for its own PR.

Required Actions

  1. Split this PR: Extract sequence diagram feature, native engine changes, and version releases to separate PRs
  2. Focus scope: Keep only the pre-commit hooks functionality in this PR
  3. Address performance: Optimize db.prepare() calls in signature warning hook
  4. Consider UX impact: 60+ second commit overhead may frustrate developers

Would Approve When Split

Once properly separated, both the pre-commit hooks and sequence features are high-quality implementations that would merit ⭐⭐⭐⭐☆ individually.


@carlos-alm
Copy link
Contributor Author

Closing in favor of focused PRs per review feedback:

CHANGELOG native engine entries dropped (belong with PR #344).

@carlos-alm carlos-alm closed this Mar 9, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Mar 9, 2026
@carlos-alm carlos-alm deleted the chore/guard-pr-body branch March 9, 2026 05:49
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant