Merge remote-tracking branch 'origin/main' into garrytan/gbrain-code-smell-audit

# Conflicts:
#	CHANGELOG.md
#	browse/test/dual-listener.test.ts
#	browse/test/fixtures/security-bench-haiku-responses.json
#	browse/test/sidebar-tabs.test.ts
#	browse/test/sidebar-ux.test.ts
#	browse/test/terminal-agent.test.ts
#	claude/SKILL.md.tmpl
#	scripts/gen-skill-docs.ts
#	scripts/proactive-suggestions.json
#	spec/SKILL.md
#	test/gen-skill-docs.test.ts
#	test/host-config.test.ts
This commit is contained in:
Garry Tan
2026-08-15 07:31:02 -07:00
246 changed files with 8802 additions and 1257 deletions
+16 -5
View File
@@ -317,12 +317,16 @@ export function splitCatalogDescription(description: string): CatalogParts {
const hasGstackTag = /\(gstack\)/.test(working);
if (hasGstackTag) working = working.replace(/\(gstack\)/, '').trim();
// Lead = first sentence (up to first period followed by space or end of string).
// We tolerate sentences with embedded periods (URLs, "v1.45.0.0") by requiring
// the period to be followed by whitespace OR end-of-text.
// Lead = first sentence, ending at the first `.`/`!`/`?` that is followed by
// whitespace or end-of-text. Terminator chars NOT followed by whitespace/end
// (embedded periods in "TODOS.md", URLs, "v1.45.0.0") are consumed by the
// second alternative `[.!?](?!\s|$)` and do NOT end the sentence. The two
// alternatives are disjoint character classes, so there is no ambiguity and
// no catastrophic-backtracking risk. If no terminator-followed-by-boundary
// exists at all, we fall back to a 20-word cut below.
// First normalize to single-line for sentence detection, then back out.
const collapsed = working.replace(/\s+/g, ' ').trim();
const sentenceMatch = collapsed.match(/^([^.!?]*[.!?])(?:\s|$)/);
const sentenceMatch = collapsed.match(/^((?:[^.!?]|[.!?](?!\s|$))*[.!?])(?:\s|$)/);
// sentenceLead is the FULL first sentence (no truncation). We compute routing
// from this position, then optionally truncate the displayed lead afterwards.
// Truncating first then computing routing was the v1.45.0.0 bug — when the
@@ -793,7 +797,14 @@ function processExternalHost(
}
function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: string; content: string; symlinkLoop?: boolean } {
const tmplContent = fs.readFileSync(tmplPath, 'utf-8');
// Normalize to LF at the entry point. Templates may have CRLF on disk when
// checked out on Windows with core.autocrlf=true. Downstream regexes
// (processVoiceTriggers, transformFrontmatter) hardcode \n, so without
// normalization they silently no-op on CRLF — producing different output
// than CI (Linux, LF) and breaking the Skill Docs Freshness check.
// (catalogParts left the return type with the proactive-suggestions
// retirement — merge of the two v1.64 waves.)
const tmplContent = fs.readFileSync(tmplPath, 'utf-8').replace(/\r\n/g, '\n');
const relTmplPath = path.relative(ROOT, tmplPath);
let outputPath = tmplPath.replace(/\.tmpl$/, '');