mirror of https://github.com/garrytan/gstack.git
fix(harness): anchor extractPlanFilePath path captures on /Users|~|/home|/var|/tmp
Adversarial-tightened gate sweep surfaced a real bug in the path
extraction: stripAnsi collapses whitespace via cursor-positioning escape
removal, so "yet at /Users/..." in the visible buffer becomes
"yetat/Users/..." with no space between. The previous fallback pattern
`(~?\/?\S*\.claude\/plans\/[\w-]+\.md)` greedily matched non-whitespace
characters BEFORE the path, producing `yetat/Users/garrytan/.claude/...`
which then fails fs.readFileSync.
Fix: every regex now requires the path to START at a known path-anchor:
`~/`, `/Users/`, `/home/`, `/var/`, `/tmp/`, or `./`. Earlier
non-whitespace runs can't be glommed in.
Verified against the failing fixture (`yetat/Users/...`) plus the four
canonical render forms ("Plan saved to:", "Plan file:", `·`-decorated
ctrl-g hint, and the bare fallback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ef34603df
commit
bdc4818bf5
|
|
@ -183,21 +183,24 @@ export function isAutoDecidedVisible(visible: string): boolean {
|
||||||
*/
|
*/
|
||||||
export function extractPlanFilePath(visible: string): string | null {
|
export function extractPlanFilePath(visible: string): string | null {
|
||||||
// Patterns checked in order of specificity. Each captures the .md path.
|
// Patterns checked in order of specificity. Each captures the .md path.
|
||||||
// The visible buffer may have stripAnsi-collapsed whitespace, so we
|
// The visible buffer may have stripAnsi-collapsed whitespace ("yet at" can
|
||||||
// accept space-or-not separators in the prompts and accept paths
|
// become "yetat"), so the captured path MUST start at a clear path-anchor
|
||||||
// without intervening whitespace (e.g. `editinVSCode·~/.claude/...`).
|
// character: `~/`, `/Users/`, `/home/`, `/var/`, or `/tmp/`. Anchoring on
|
||||||
|
// these prefixes prevents earlier non-whitespace characters from being
|
||||||
|
// glommed into the path (real bug seen in the wild: `yetat/Users/...`).
|
||||||
|
const PATH_ANCHOR = '(~\\/|\\/Users\\/|\\/home\\/|\\/var\\/|\\/tmp\\/|\\.\\/)';
|
||||||
const patterns: RegExp[] = [
|
const patterns: RegExp[] = [
|
||||||
/Plan\s*saved\s*to\s*:?\s*(\S+\.md)/i,
|
new RegExp(`Plan\\s*saved\\s*to\\s*:?\\s*(${PATH_ANCHOR}\\S+\\.md)`, 'i'),
|
||||||
/Plan\s*file\s*:?\s*(\S+\.md)/i,
|
new RegExp(`Plan\\s*file\\s*:?\\s*(${PATH_ANCHOR}\\S+\\.md)`, 'i'),
|
||||||
/·\s*(\S+\.claude\/plans\/\S+\.md)/i,
|
new RegExp(`·\\s*(${PATH_ANCHOR}\\S*\\.claude\\/plans\\/\\S+\\.md)`, 'i'),
|
||||||
// Fallback: any reference to a .claude/plans path in the buffer.
|
// Fallback: any path-anchored reference to a .claude/plans .md file.
|
||||||
/(~?\/?\S*\.claude\/plans\/[\w-]+\.md)/i,
|
new RegExp(`(${PATH_ANCHOR}\\S*\\.claude\\/plans\\/[\\w-]+\\.md)`, 'i'),
|
||||||
];
|
];
|
||||||
for (const p of patterns) {
|
for (const p of patterns) {
|
||||||
const m = visible.match(p);
|
const m = visible.match(p);
|
||||||
if (m && m[1]) {
|
if (m && m[1]) {
|
||||||
let raw = m[1];
|
let raw = m[1];
|
||||||
// Some patterns capture trailing punctuation; strip a trailing dot.
|
// Strip trailing punctuation that some patterns may capture.
|
||||||
raw = raw.replace(/\.+$/, '.md').replace(/\.md\.+$/, '.md');
|
raw = raw.replace(/\.+$/, '.md').replace(/\.md\.+$/, '.md');
|
||||||
// Tilde expansion to absolute path.
|
// Tilde expansion to absolute path.
|
||||||
if (raw.startsWith('~')) {
|
if (raw.startsWith('~')) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue