mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 09:55:29 +02:00
fix: pre-landing review fixes — review-army findings hardened
Specialist review (testing, maintainability, security, performance,
data-migration) findings, each verified against code before fixing:
- legacy remove: preserve malformed/foreign entries (hooks absent, non-array,
or pre-existing empty) — only entries THIS pass emptied are dropped
- add-event: never tag a mixed entry (old gstack versions in sibling
worktrees treat tags as entry-level ownership and would destroy the user's
co-located items); tag only single-item entries; prune-stale drops tags
from mixed entries for the same reason
- prune-stale: within-entry twin collapse (two dead copies of one hook
re-pointed to the same canonical command no longer double-fire); command
quoting hardened via gsQuoteCmd (escapes \\ " $ backtick; gsStripWrap
unescapes so identity round-trips); NUL bytes in the dedupe key replaced
with a JSON.stringify key (bash silently dropped the NULs, degrading the
separator; the file also read as binary to tooling)
- gsIsAlive: only provable absence (ENOENT/ENOTDIR) counts as dead —
EACCES/EIO/unmounted volumes no longer prune (one-way-ratchet guard)
- gsWriteIfChanged: preserves the live settings.json mode across rewrites
(a user-tightened 0600 carrying API keys was silently broadened to 0644);
fresh files start 0600; backups rotate (keep 10)
- remove-source: command-less items default to foreign (gstack only writes
type:command items); single-item stray claim requires a command
- rollback: pointer target must be a sibling settings.json.bak.* file
- uninstall + setup --no-team + SessionStart registration: stderr stays
attached — a lock give-up or fail-closed parse during TEARDOWN must be
visible ("the next setup retries" does not apply after uninstall)
- setup: team-mode banner no longer claims an auto-update hook when
registration was skipped; heal log documents the rollback-pointer caveat;
SESSION_UPDATE_CMD quoting mirrors gsQuoteCmd; lock constants named
- list-sources: corrupt settings.json reports to stderr instead of silently
printing nothing (setup guards must not misread corrupt as no-hooks)
- tests: 10 new pins (malformed-entry preservation, mixed no-tag, twin
collapse, 0600 mode, metachar escaping round-trip, backup rotation,
rollback pointer refusal, held-lock uninstall warning, matcher-drift
tripwire, ownership negatives)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d5626653ac
commit
19eed1b392
@@ -186,3 +186,31 @@ describe('the defect-class warning is written down where the next author will se
|
||||
expect(setupSrc).toMatch(/NEVER register .*SOURCE_GSTACK_DIR.*hook paths/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matcher-literal drift tripwire (review-army)', () => {
|
||||
test("every --matcher literal in setup equals its KNOWN_HOOKS row's matcher", () => {
|
||||
// gsOwnedRow requires an EXACT matcher match — if setup's registration
|
||||
// matcher drifts from the table row, identity re-pointing/pruning silently
|
||||
// stops recognizing the hook and the phantom-duplicate class returns.
|
||||
const rowMatcher = (name: string) => {
|
||||
const rowStart = hookBinSrc.indexOf(`"${name}":`);
|
||||
expect(rowStart).toBeGreaterThan(-1);
|
||||
const row = hookBinSrc.slice(rowStart, hookBinSrc.indexOf('}', rowStart));
|
||||
return row.match(/matcher: "([^"]*)"/)![1];
|
||||
};
|
||||
const pairs: Array<[string, string]> = [
|
||||
['question-log-hook', '(AskUserQuestion|mcp__.*__AskUserQuestion)'],
|
||||
['question-preference-hook', '(AskUserQuestion|mcp__.*__AskUserQuestion)'],
|
||||
['auq-error-fallback-hook', '(AskUserQuestion|mcp__.*__AskUserQuestion)'],
|
||||
];
|
||||
for (const [name, expected] of pairs) {
|
||||
expect(rowMatcher(name)).toBe(expected);
|
||||
}
|
||||
// And setup registers those hooks with exactly that matcher literal.
|
||||
const matcherLiterals = [...setupSrc.matchAll(/--matcher '([^']+)'/g)].map((m) => m[1]);
|
||||
expect(matcherLiterals.length).toBeGreaterThanOrEqual(3);
|
||||
for (const lit of matcherLiterals) {
|
||||
expect(lit).toBe('(AskUserQuestion|mcp__.*__AskUserQuestion)');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user