19 Commits

Author SHA1 Message Date
ayushcodes10 784b1fb1bd Use the replace fallback for skill install writes
Fixes #3508's other reported failure: the Kiro skill install died on
the same WinError 17 while replacing SKILL.md. Covers the version
stamp write too, which shares the same pattern beside it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit e6ac02a49d)
2026-09-12 17:36:09 +01:00
dajiaohuang 496112d6fc fix(install): do not abort the install when always-on registration is unwritable
`install()` copies the skill files first and writes the always-on
registration afterwards, so an unguarded write to `~/.claude/CLAUDE.md`
left a half-completed install plus a traceback whenever that file could
not be written. That is the normal shape for a declaratively managed
dotfile: nix/home-manager symlinks it into a read-only /nix/store, and
chezmoi or stow with read-only sources do the same.

Route the two registrations inside `install()` (CLAUDE.md and
CODEBUDDY.md, which had the identical unguarded pattern) through one
helper that reports the skip on stderr and lets the install finish, so
the skill files that were already laid down stay usable.

Refs #3474

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 47041eef5d)
2026-09-10 22:44:40 +01:00
dr mike c488c0e07d fix(install): write .graphify_version atomically via os.replace
Match the SKILL.md / references/ crash-safety pattern so an interrupted install cannot leave a truncated stamp, and replace managed symlinks instead of writing through them.

Fixes #3286

(cherry picked from commit 030c6372d1)
2026-09-05 01:18:59 +01:00
abhay-codes07 4d8cb4c803 fix(install): actionable stale-skill warning; back up a diverged SKILL.md (#3144)
When the installed SKILL.md has diverged (user edit or old version), back it up beside the
install target before the atomic overwrite and print an actionable warning naming the stale
path and the exact reinstall command; a clean up-to-date reinstall backs up nothing (no
churn), and only the installed copy is touched, never the generated sources.
2026-08-30 15:11:40 +01:00
davidbhoward 35c63493e5 fix(install): emit a bare hook command for project-scoped installs (#3129)
A --project (committed/shared) install pinned the absolute interpreter path into the hook
command, so the committed hook churned across machines. Emit a bare `graphify <sub>` command
for project installs (resolved at hook-run time via the PATH/pin/uv-tool probes), while the
global install still emits the absolute path (preserving the Windows VS Code Codex case).
Uninstall still strips the bare command.
2026-08-29 18:36:56 +01:00
Ousama Ben Younes a59a4e812e fix(install): stamp .graphify_version only for the platform being installed (#2694)
install() called _refresh_all_version_stamps(), which stamped every installed
platform's .graphify_version with the current version WITHOUT rewriting that
platform's skill content — so a genuinely stale platform was stamped current
and its staleness warning was suppressed. Removes that bogus refresh; each
platform's stamp now advances only when its own content is (re)written via
_copy_skill_file, so a not-reinstalled stale platform correctly still warns.
Completes #2694 (part 1 shipped in 0.9.44 via #2753).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-16 18:30:13 +01:00
AromalBiju1 4910dccc4a fix(install): honor CLAUDE_CONFIG_DIR in always-on CLAUDE.md registration
The always-on registration wrote `~/.claude/CLAUDE.md` unconditionally, so
installing from a Claude profile relocated via CLAUDE_CONFIG_DIR mutated the
DEFAULT profile instead — the same fix _platform_skill_destination already
applies to the skill-copy path, missed for this path. The global branch now
writes to $CLAUDE_CONFIG_DIR/CLAUDE.md with a matching skill reference; env
unset/blank still falls back to ~/.claude. Refs #2694 (part 1; the version-stamp
half is unaddressed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 21:49:01 +01:00
Benjamin S. Leveritt 613c45cc7e fix(install): make the staged skill bundle writable so a read-only package installs (#2453) 2026-08-13 14:17:42 +01:00
safishamsi a426d63414 feat(install): point users to the hosted platform after install
Print a one-time note about free early access to app.graphify.com at the
end of the install summary. Not shown on any other command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 01:41:12 +01:00
MalikHaroonKhokhar bdf62ce275 fix(install): stop hardcoding the global skill path in the Antigravity workflow (#2319)
`_ANTIGRAVITY_WORKFLOW` is shared by the global and project-scoped installs,
which write SKILL.md to different locations (~/.gemini/config/skills vs
.agents/skills). The template named the global path unconditionally, so
`install --project --platform antigravity` (and `antigravity install
--project`) emitted a workflow pointing at a file that scope never writes -
a dangling reference on any machine without a prior global install.

Refer to the skill by name rather than by location, matching what every
block in always_on/ already does. Antigravity resolves it from the
`name: graphify` frontmatter in either scope, so no path interpolation is
needed and neither scope can dangle.

The existing tests only asserted the workflow file exists, which is why this
shipped; add coverage that the generated workflow names no skill path, in
both project and global scope.
2026-07-30 17:13:48 +01:00
safishamsi bdb678858b fix(install): scope uninstall to project_dir instead of always deleting the global skill (#2215)
claude_uninstall/gemini_uninstall/codebuddy_uninstall accepted project_dir
but, with the default project=False, still deleted the user-global skill
tree, so a library/test caller passing a project_dir nuked ~/.claude et al
(the API trap behind #2168). They now take remove_user_skill and treat a
passed project_dir as authoritative; uninstall_all opts in explicitly to
preserve 'graphify uninstall' behavior. Also fixes a live CLI bug where
'uninstall --project' deleted the global codebuddy skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 10:46:47 +01:00
Souptik Chakraborty 911d58178f fix: document the Codex PreToolUse hook as an intentional no-op (#2165)
`graphify codex install` registers `graphify hook-check` in .codex/hooks.json,
and #2165 reported that as a stale/unrecognized subcommand producing a silent
no-op. `hook-check` is in fact a real, deliberate no-op command: Codex Desktop
rejects hookSpecificOutput.additionalContext on PreToolUse, so the Codex hook
intentionally does nothing and AGENTS.md carries the always-on guidance
(cli.py dispatches `hook-check`; __main__.py lists it in _silent_cmds).
Repointing the installer at `hook-guard` would reintroduce the #522-class
breakage on Codex Desktop, so the behavior is left as is.

What actually misled the report was the documentation and the installer's own
output, which both describe the Codex hook as if it enforced graph usage:

- README: the Codex row claimed a PreToolUse hook that "fires before every Bash
  tool call, same always-on mechanism as Claude Code". It now states that the
  hook is a deliberate no-op, why (Codex Desktop rejects additionalContext), and
  that AGENTS.md is the always-on mechanism on this platform.
- `_install_codex_hook` printed "PreToolUse hook registered (... hook-check)"
  with no hint that the entry is inert. It now says so inline.

Also adds the regression guard the issue implicitly asks for: a test that reads
the command out of the generated .codex/hooks.json and asserts its subcommand is
one the CLI actually dispatches. A genuinely renamed/stale hook command now
fails the suite instead of shipping a permanently dead hook.

Note: contrary to the report, an unrecognized subcommand already exits non-zero
(`graphify totally-bogus-subcommand` -> "error: unknown command", exit 1), so no
change was needed there.
2026-07-26 11:10:35 +01:00
safishamsi 05ee568969 fix(install): never clobber an unparseable settings file; back up before write (#2167)
The hook installers fell back to settings={} on any JSON parse error and
then overwrote the whole file, destroying the user's config (the likely
trigger is a UTF-8 BOM, same class as #2163). All four installers now
read utf-8-sig, refuse to modify a file that isn't a JSON object (naming
the path) instead of clobbering it, back up to <name>.graphify-bak before
any modifying write, skip the write when content is unchanged, and guard
the PreToolUse filter against non-dict entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 22:53:43 +01:00
safishamsi b96effd3aa fix(install): uninstall must not delete a user's ### graphify H3 section (#2062)
The uninstall strip used an unanchored regex `## graphify`, which matched inside
a user's `### graphify` heading and deleted hand-written content; the
`marker not in content` guard was a substring test that passed on the same
mention. Add a shared `_remove_marker_section` helper that matches the heading
only when a line is exactly the marker (mirroring the install-side #1688
hardening), running each section to the next same-level heading or EOF, and
returning None (leave the file untouched) when no exact heading exists. Replace
all six strip sites: GEMINI.md, copilot-instructions.md, AGENTS.md, CLAUDE.md
(_strip_graphify_md_section), CODEBUDDY.md, and the H1 skill-registration
(_remove_claude_skill_registration, which had the same bug with `# graphify`).
2026-07-21 12:40:49 +01:00
shazeb 0224bcaea4 fix(install): fire the search nudge on Claude Code's Grep tool, not just Bash (#1986)
ee1df22 narrowed the Claude Code search-guard matcher from "Glob|Grep" to
"Bash" on the premise that dedicated search tools were removed and searches
go through Bash. Current Claude Code routes content search through its
first-class Grep tool (its Bash tool description actively steers away from
shell grep), so the graphify-first nudge never fired on the agent's primary
exploration path and the graph was silently bypassed.

Three-part fix, per the issue's analysis:

- Matcher: "Bash" -> "Bash|Grep" in _claude_pretooluse_hooks. Glob already
  fires the read nudge via "Read|Glob", so Grep was the only orphaned tool.
- Guard body: the hook-guard search branch only inspected tool_input.command,
  which a Grep call doesn't carry (it has pattern/path/glob). A Grep-shaped
  input (pattern present, no command) is now treated as a search — it IS one
  by definition — and nudges whenever a fresh graph exists. The Bash
  token-matching path is unchanged, and a command-carrying input never
  triggers the Grep shape, so non-search Bash calls stay silent.
- Idempotency: "Bash|Grep" added to the four install/uninstall dedup filters
  (claude + codebuddy), so upgrading replaces the stale "Bash" hook in place
  instead of appending a duplicate — verified against a pre-fix settings.json.

Tests: new regression tests feed Grep-shaped tool_input through
hook-guard search and assert the nudge (with graph), silence (without),
valid PreToolUse JSON, and no blocking; plus a guard that a non-search Bash
command with a stray pattern key does not nudge. Existing matcher assertions
updated across test_search_hook/test_install/test_claude_md/test_codebuddy/
test_hook_strict. Hook+install suites: 397 passed. Full suite: 3224 passed;
the 13 failures are pre-existing on clean v8 in this environment.

Fixes #1986
2026-07-18 19:07:16 +01:00
varuntej07 faa0ac2984 fix(install): emit hook exe paths with forward slashes so Git Bash does not strip them
Claude Code runs command-type PreToolUse hooks through Git Bash by default on
Windows. The resolved exe was emitted as a raw backslash path and quoted only
when it contained a space, so a space-free path like C:\Users\me\graphify.EXE
reached settings.json unquoted. Git Bash treats the unquoted backslashes as
escapes and strips them, producing 'C:Usersmegraphify.EXE: command not found',
so every graph guard silently fails. Normalize the path to forward slashes in
_resolve_graphify_exe (a no-op on POSIX), fixing the Claude, Gemini, and Codex
hook emitters at once.
2026-07-18 19:06:51 +01:00
safishamsi 689dd6ccfd feat(hook): opt-in strict PreToolUse guard + stop crying wolf (#1840)
Agents routinely ignore the advisory "run graphify query first" nudge and read
raw files anyway. `graphify install --project --strict` (or `graphify claude
install --strict`) now installs a hook that BLOCKS the first raw source read of a
session via permissionDecision:"deny" with a redirect to graphify query, then
downgrades to the soft nudge — it fires at most once per session (atomic
per-session marker) so it can never strand the agent, and a recent
query/explain/path refreshes a stamp that suppresses it. Claude Code only;
Bash-grep and Glob stay nudge-only; Gemini/Codex/OpenCode are unchanged.
GRAPHIFY_HOOK_STRICT=1/0 toggles at runtime without a reinstall; default installs
are byte-identical (soft nudge).

Also fixes #1840 for the default soft nudge: the guard no longer fires for reads
of out-of-project files, and softens to a non-mandatory nudge when the graph is
stale for the target file. Gating is ~3 stat calls (no corpus walk) and fails
open. Begins the 0.9.19 cycle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 17:10:41 +01:00
tpateeq 97a289c531 refactor(main): move install/platform CLI dispatch into install.dispatch_install_cli
main() carried a 256-line if/elif chain routing every install-family command
(install, uninstall, and the 22 per-platform targets: claude, codebuddy, gemini,
cursor, vscode, copilot, kilo, kiro, devin, pi, amp, agents/skills,
aider/codex/opencode/claw/droid/trae/trae-cn/hermes, antigravity). That block
only ever reads sys.argv and calls functions that already live in install.py, so
move it there verbatim as a guarded dispatch_install_cli(cmd) -> bool; main() now
does `if dispatch_install_cli(cmd): return`.

The command set is derived from the block's own conditions (so none is missed),
and the branch's early `return` becomes `return True` so the help path still
short-circuits. Verified end-to-end: `graphify claude install/uninstall`, unknown
command, and the install test suite all behave identically.

__main__.py 3,641 -> 3,388 LOC (5,368 at branch start, -37%). Full suite
unchanged: 3036 passed, 29 skipped; skillgen --check OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:42:12 +05:30
tpateeq cc2d3c13a6 refactor(main): extract install/uninstall subsystem into graphify/install.py
__main__.py was 5,368 LOC, more than half of it per-platform install/uninstall
machinery interleaved with the CLI dispatcher. Move that subsystem — 68
functions (all *_install/*_uninstall, _copy_skill_file, _platform_skill_destination,
_always_on, etc.) plus 21 module constants (_PLATFORM_CONFIG and the platform
skill/hook payload constants) — into a new graphify/install.py, extracted verbatim.

Behavior-preserving:
- install.py lives in the same package dir as __main__ so packaged-asset lookups
  via Path(__file__).parent ("always_on"/"skills") resolve unchanged.
- __main__ re-exports all 89 moved names, so `from graphify.__main__ import
  claude_install` (and every other import, incl. private helpers used by tests)
  keeps working, with object identity preserved.
- The install cluster was verified (AST) to have zero back-references into
  __main__, so no circular import; __main__ imports _PLATFORM_CONFIG et al. one-way.

__main__.py drops 5,368 -> 3,642 LOC. Full suite unchanged: 3036 passed, 29
skipped (excluding the env-only openai tests). ruff check clean; skillgen --check OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 00:20:48 +05:30