From 762ffd08b28445fd02aa1d1ed2a857c459c110f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 04:50:46 +0000 Subject: [PATCH] Sync generated provider output --- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- .../impeccable/scripts/lib/staleness-deep.mjs | 65 ++++++++++++++++--- 15 files changed, 840 insertions(+), 135 deletions(-) diff --git a/.agents/skills/impeccable/scripts/lib/staleness-deep.mjs b/.agents/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.agents/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.agents/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.claude/skills/impeccable/scripts/lib/staleness-deep.mjs b/.claude/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.claude/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.claude/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.cursor/skills/impeccable/scripts/lib/staleness-deep.mjs b/.cursor/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.cursor/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.cursor/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.gemini/skills/impeccable/scripts/lib/staleness-deep.mjs b/.gemini/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.gemini/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.gemini/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.github/skills/impeccable/scripts/lib/staleness-deep.mjs b/.github/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.github/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.github/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.grok/skills/impeccable/scripts/lib/staleness-deep.mjs b/.grok/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.grok/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.grok/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.kiro/skills/impeccable/scripts/lib/staleness-deep.mjs b/.kiro/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.kiro/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.kiro/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.opencode/skills/impeccable/scripts/lib/staleness-deep.mjs b/.opencode/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.opencode/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.opencode/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.pi/skills/impeccable/scripts/lib/staleness-deep.mjs b/.pi/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.pi/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.pi/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.qoder/skills/impeccable/scripts/lib/staleness-deep.mjs b/.qoder/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.qoder/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.qoder/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.rovodev/skills/impeccable/scripts/lib/staleness-deep.mjs b/.rovodev/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.rovodev/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.rovodev/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.trae-cn/skills/impeccable/scripts/lib/staleness-deep.mjs b/.trae-cn/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.trae-cn/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.trae-cn/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.trae/skills/impeccable/scripts/lib/staleness-deep.mjs b/.trae/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.trae/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.trae/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/.vibe/skills/impeccable/scripts/lib/staleness-deep.mjs b/.vibe/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/.vibe/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/.vibe/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) { diff --git a/plugin/skills/impeccable/scripts/lib/staleness-deep.mjs b/plugin/skills/impeccable/scripts/lib/staleness-deep.mjs index 3276afc4d..bae03bc98 100644 --- a/plugin/skills/impeccable/scripts/lib/staleness-deep.mjs +++ b/plugin/skills/impeccable/scripts/lib/staleness-deep.mjs @@ -215,12 +215,57 @@ function collectHookCommands(value, out = []) { return out; } -// Pull the script path out of a hook command line. Commands look like -// `node .claude/skills/impeccable/scripts/hook.mjs` and may be quoted or carry -// trailing arguments. -function hookScriptPathFrom(command) { - const match = String(command).match(/(\S*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); - return match ? match[1].replace(/^['"]|['"]$/g, '') : null; +const HOOK_MARKER = /skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs/; + +// Pull the script-path token out of a hook command line, placeholders intact. +// The forms our manifests ship: +// * bare: node "${CLAUDE_PROJECT_DIR}/.../hook.mjs" +// * bundle-relative: node ".agents/.../hook.mjs" +// * legacy unquoted: node .claude/.../hook.mjs +// * guarded (#399): [ ! -f "PATH" ] || node "PATH" (PATH twice, identical) +// * absolute: node "/Users/.../hook.mjs" (user-level installs) +// * github portable: node "$(git rev-parse --show-toplevel)/.../hook.mjs" +// A quoted path wins; the guard's two occurrences are identical, so the first +// quoted match is the path. Otherwise fall back to the whitespace/metachar- +// delimited token that ends at the marker, so we don't absorb `node`, `[`, `!` +// or `||`. Returns the token verbatim; resolution happens separately. +function hookScriptTokenFrom(command) { + const str = String(command); + if (!HOOK_MARKER.test(str)) return null; + const quoted = str.match(/"([^"]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)"/); + if (quoted) return quoted[1]; + const bare = str.match(/([^\s"'|&;()]*skills\/impeccable\/scripts\/hook(?:-before-edit)?\.mjs)/); + return bare ? bare[1] : null; +} + +// Resolve a script token to an absolute path the doctor can existsSync, or null +// when the doctor cannot know where it points — in which case the caller must +// NOT report it missing (a doctor never asserts a negative it cannot verify). +// +// Per-placeholder policy, mirroring what each runtime actually expands: +// ${CLAUDE_PROJECT_DIR} → the project root being scanned. This is exactly the +// runtime mapping (Claude Code sets it to the project +// dir at hook time), so we EXPAND it against `root`. +// Not doing so was the #402 bug: the literal +// `${CLAUDE_PROJECT_DIR}/...` string never exists. +// ${CLAUDE_PLUGIN_ROOT} → plugin-package install dir, set by the harness to +// ${PLUGIN_ROOT} wherever the plugin/codex/grok bundle was unpacked +// ${GROK_PLUGIN_ROOT} (grok aliases CLAUDE_PLUGIN_ROOT). The doctor has no +// way to know that location → SKIP (return null). +// $(...) / backticks → command substitution, e.g. GitHub's +// `$(git rev-parse --show-toplevel)`. Not statically +// resolvable → SKIP. +// any other ${VAR}/$VAR → unknown to the doctor → SKIP. +// A token with no placeholder is a literal path: absolute as-is, else relative +// to `root`. +function resolveHookScriptPath(token, root) { + if (!token) return null; + // Command substitution or backtick expansion we can't evaluate. + if (token.includes('$(') || token.includes('`')) return null; + const expanded = token.replace(/\$\{CLAUDE_PROJECT_DIR\}/g, root); + // Any placeholder or shell variable still present is one we can't map. + if (/\$\{[^}]*\}|\$[A-Za-z_]/.test(expanded)) return null; + return path.isAbsolute(expanded) ? expanded : path.join(root, expanded); } /** @@ -246,9 +291,11 @@ export function checkHookInstallation({ projectRoot, repoRoot, providerId }) { installedAt = toRelative(manifestPath, projectRoot || root); const broken = commands.filter((command) => { - const scriptPath = hookScriptPathFrom(command); - if (!scriptPath) return false; - const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(root, scriptPath); + const token = hookScriptTokenFrom(command); + if (!token) return false; + const abs = resolveHookScriptPath(token, root); + // Unresolvable placeholder or command substitution: never assert missing. + if (!abs) return false; return !fs.existsSync(abs); }); if (broken.length) {