fix(v0.18.2.fork.1): allow underscore in slug-prefix rules
Phase 6 deploy uncovered: chezmoi-managed prefixes like `dot_claude/` are legitimate slugs (chezmoi convention maps `~/.claude/` → `dot_claude/` in source tree). The original validator rejected underscores, which blocked the Phase 4 source taxonomy mid-way: $ gbrain sources update claude-config --slug-prefix 'dot_claude/,claude' Invalid slug-prefix rule "dot_claude/". Must be lowercase a-z, 0-9, '-', '/', optionally ending in '*'. Reject: underscores, ... Underscore is now first-class. Updated regex + comment + test (flipped the "reject underscore" case to "accept underscore" with chezmoi example). Discovered during Phase 6 deploy: blocked at E3 step 4 of 5 (`gbrain sources update claude-config --slug-prefix 'dot_claude/,claude'`). First 3 commands had succeeded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,17 +47,23 @@ function validateSourceId(id: string): void {
|
|||||||
// - Single-level glob: 'wedding-planning/*' (cosmetic; same as literal)
|
// - Single-level glob: 'wedding-planning/*' (cosmetic; same as literal)
|
||||||
//
|
//
|
||||||
// Rules:
|
// Rules:
|
||||||
// - Allowed chars: lowercase a-z, 0-9, '-' (hyphen), '/' (slash)
|
// - Allowed chars: lowercase a-z, 0-9, '-' (hyphen), '_' (underscore), '/' (slash)
|
||||||
// - Optional single trailing '*' (no other position)
|
// - Optional single trailing '*' (no other position)
|
||||||
// - Length 1..64 (excluding the trailing '*')
|
// - Length 1..64 (excluding the trailing '*')
|
||||||
// - Reject underscores ('_'), uppercase, whitespace, mid-string '*',
|
// - Reject uppercase, whitespace, mid-string '*', multi-level globs
|
||||||
// multi-level globs ('**'), any other punctuation
|
// ('**'), any other punctuation
|
||||||
//
|
//
|
||||||
// Why fail-fast at write time: a typo'd rule (`memory_dashboard/`) writes
|
// Why fail-fast at write time: a typo'd rule writes successfully into
|
||||||
// successfully into config_jsonb but never matches anything at routing
|
// config_jsonb but never matches anything at routing time — the put_page
|
||||||
// time — the put_page silently falls to brain-default. Catching at
|
// silently falls to brain-default. Catching at CLI-write moment surfaces
|
||||||
// CLI-write moment surfaces "this is a typo" before bad data lands.
|
// the typo before bad data lands.
|
||||||
const SLUG_PREFIX_RULE_RE = /^[a-z0-9](?:[a-z0-9-/]{0,62}[a-z0-9-/])?\*?$/;
|
//
|
||||||
|
// Underscore note (v0.18.2.fork.1 Phase 6 fix): chezmoi-managed prefixes
|
||||||
|
// like `dot_claude/` are legitimate slugs — chezmoi conventionally maps
|
||||||
|
// `~/.claude/` → `dot_claude/` in source. Earlier draft of this validator
|
||||||
|
// rejected underscores, which broke Phase 4 source taxonomy. Underscore
|
||||||
|
// is now first-class.
|
||||||
|
const SLUG_PREFIX_RULE_RE = /^[a-z0-9_](?:[a-z0-9_\-/]{0,62}[a-z0-9_\-/])?\*?$/;
|
||||||
function validateSlugPrefix(rule: string): void {
|
function validateSlugPrefix(rule: string): void {
|
||||||
if (!rule || rule.length === 0) {
|
if (!rule || rule.length === 0) {
|
||||||
throw new Error('Empty slug-prefix rule. Each --slug-prefix entry must be a non-empty string.');
|
throw new Error('Empty slug-prefix rule. Each --slug-prefix entry must be a non-empty string.');
|
||||||
|
|||||||
@@ -106,8 +106,10 @@ describe('Prefix grammar validator (Issue #6 — fail-fast at write time)', () =
|
|||||||
if (hint) expect(msg).toContain(hint);
|
if (hint) expect(msg).toContain(hint);
|
||||||
};
|
};
|
||||||
|
|
||||||
test('reject underscore', async () => {
|
test('accept underscore (chezmoi-style prefixes like dot_claude/)', async () => {
|
||||||
await expectReject('memory_dashboard/', 'Invalid slug-prefix');
|
await runSources(engine, ['add', 'test-accept-under', '--slug-prefix', 'dot_claude/,foo_bar/']);
|
||||||
|
const cfg = await readConfig('test-accept-under');
|
||||||
|
expect(cfg.slug_prefix_rules).toEqual(['dot_claude/', 'foo_bar/']);
|
||||||
});
|
});
|
||||||
test('reject uppercase', async () => {
|
test('reject uppercase', async () => {
|
||||||
await expectReject('MemoryDashboard/', 'Invalid slug-prefix');
|
await expectReject('MemoryDashboard/', 'Invalid slug-prefix');
|
||||||
|
|||||||
Reference in New Issue
Block a user