mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 16:46:31 +03:00
windows: check the grok global-install manifests as JSON
The Windows hook command carries the JSON-quoted launcher path, so the path's backslashes are escaped once inside the command and again by the manifest file itself. Read the manifest as JSON and look for either quoting form instead of counting escaping layers in a raw substring match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
76428b37f1
commit
3ed4910d5f
@@ -465,13 +465,23 @@ fn inferred_home_rooted_updates_refresh_stale_or_missing_copilot_user_agents() {
|
||||
|
||||
// ─── Grok project hooks on global installs (49571365, #642) ──────────────────
|
||||
|
||||
/// The launcher path the installer writes into a hook manifest, in the host's
|
||||
/// path form and escaped the way it lands inside the JSON file (a Windows path
|
||||
/// carries backslashes, which JSON doubles).
|
||||
fn manifest_skill_path(home: &str, provider: &str) -> String {
|
||||
/// True when a hook manifest names this provider's launcher. The file is read
|
||||
/// as JSON so the check does not depend on how many escaping layers the host's
|
||||
/// path form needs: a Windows command carries the JSON-quoted path (its
|
||||
/// backslashes doubled), the sh form carries the path verbatim.
|
||||
fn manifest_names_launcher(manifest: &str, home: &str, provider: &str) -> bool {
|
||||
fn any_string(v: &Value, pred: &dyn Fn(&str) -> bool) -> bool {
|
||||
match v {
|
||||
Value::String(s) => pred(s),
|
||||
Value::Array(a) => a.iter().any(|c| any_string(c, pred)),
|
||||
Value::Object(o) => o.values().any(|c| any_string(c, pred)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
let p = jsp::join(&[home, provider, "skills", "impeccable", "scripts", "impeccable"]);
|
||||
let quoted = serde_json::to_string(&p).unwrap();
|
||||
quoted[1..quoted.len() - 1].to_string()
|
||||
let escaped = p.replace('\\', "\\\\");
|
||||
let value: Value = serde_json::from_str(&read(manifest)).unwrap();
|
||||
any_string(&value, &|s: &str| s.contains(&p) || s.contains(&escaped))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -504,12 +514,13 @@ fn global_install_rewrites_grok_project_hooks_to_the_global_skill_path() {
|
||||
}
|
||||
// Launcher-era adaptation: the JS asserted the rewritten hook.mjs path;
|
||||
// the engine writes the launcher form pointing at the same global root.
|
||||
assert!(read(&format!("{tmp}/.claude/settings.local.json")).contains(&manifest_skill_path(&home, ".claude")));
|
||||
assert!(read(&format!("{tmp}/.codex/hooks.json")).contains(&manifest_skill_path(&home, ".agents")));
|
||||
assert!(read(&format!("{tmp}/.cursor/hooks.json")).contains(&manifest_skill_path(&home, ".cursor")));
|
||||
let grok_hooks = read(&format!("{tmp}/.grok/hooks/impeccable.json"));
|
||||
assert!(manifest_names_launcher(&format!("{tmp}/.claude/settings.local.json"), &home, ".claude"));
|
||||
assert!(manifest_names_launcher(&format!("{tmp}/.codex/hooks.json"), &home, ".agents"));
|
||||
assert!(manifest_names_launcher(&format!("{tmp}/.cursor/hooks.json"), &home, ".cursor"));
|
||||
let grok_manifest = format!("{tmp}/.grok/hooks/impeccable.json");
|
||||
let grok_hooks = read(&grok_manifest);
|
||||
assert!(
|
||||
grok_hooks.contains(&manifest_skill_path(&home, ".grok")),
|
||||
manifest_names_launcher(&grok_manifest, &home, ".grok"),
|
||||
"grok hook not rewritten to the global skill path: {grok_hooks}"
|
||||
);
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user