@graphit/cli 0.1.30 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ export type SkillInstallKind = "claude-code" | "codex" | "cursor";
2
+ export type SkillInstallScope = "global" | "project";
3
+ export interface SkillInstallTarget {
4
+ kind: SkillInstallKind;
5
+ scope: SkillInstallScope;
6
+ path: string;
7
+ }
8
+ export interface SkillInstallStatus extends SkillInstallTarget {
9
+ installedVersion: string | null;
10
+ reason: "legacy-copy-present" | "missing-version" | "older-version";
11
+ }
12
+ interface FindOutdatedOptions {
13
+ homeDir?: string;
14
+ cwd?: string;
15
+ }
16
+ export declare function readSkillVersion(path: string): string | null;
17
+ export declare function getSkillInstallTargets(options?: FindOutdatedOptions): SkillInstallTarget[];
18
+ export declare function findOutdatedSkillInstalls(currentVersion: string, options?: FindOutdatedOptions): SkillInstallStatus[];
19
+ export declare function formatSkillStalenessBanner(statuses: SkillInstallStatus[], currentVersion: string): string | null;
20
+ export {};
@@ -0,0 +1,139 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ function compareVersions(left, right) {
5
+ const leftParts = left.split(".").map(Number);
6
+ const rightParts = right.split(".").map(Number);
7
+ for (let index = 0; index < 3; index += 1) {
8
+ const leftPart = leftParts[index] ?? 0;
9
+ const rightPart = rightParts[index] ?? 0;
10
+ if (leftPart > rightPart)
11
+ return 1;
12
+ if (leftPart < rightPart)
13
+ return -1;
14
+ }
15
+ return 0;
16
+ }
17
+ function readVersionJson(path) {
18
+ try {
19
+ if (!existsSync(path))
20
+ return null;
21
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
22
+ return typeof parsed.version === "string" ? parsed.version : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export function readSkillVersion(path) {
29
+ try {
30
+ const content = readFileSync(path, "utf-8");
31
+ const match = content.match(/^skill_version:\s*["']?([^"'\n]+)["']?\s*$/m);
32
+ return match?.[1] ?? null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ function readInstalledVersion(target) {
39
+ if (!existsSync(target.path))
40
+ return null;
41
+ if (target.kind !== "cursor") {
42
+ const versionJson = readVersionJson(join(target.path.replace(/SKILL\.md$/, ""), "VERSION.json"));
43
+ if (versionJson)
44
+ return versionJson;
45
+ }
46
+ return readSkillVersion(target.path);
47
+ }
48
+ function isPluginManagedKind(kind) {
49
+ return kind === "claude-code" || kind === "codex";
50
+ }
51
+ export function getSkillInstallTargets(options = {}) {
52
+ const home = options.homeDir ?? homedir();
53
+ const cwd = options.cwd ?? process.cwd();
54
+ return [
55
+ {
56
+ kind: "claude-code",
57
+ scope: "global",
58
+ path: join(home, ".claude", "skills", "graphit", "SKILL.md"),
59
+ },
60
+ {
61
+ kind: "codex",
62
+ scope: "global",
63
+ path: join(home, ".codex", "skills", "graphit", "SKILL.md"),
64
+ },
65
+ {
66
+ kind: "cursor",
67
+ scope: "global",
68
+ path: join(home, ".cursor", "rules", "graphit.mdc"),
69
+ },
70
+ {
71
+ kind: "claude-code",
72
+ scope: "project",
73
+ path: join(cwd, ".claude", "skills", "graphit", "SKILL.md"),
74
+ },
75
+ {
76
+ kind: "codex",
77
+ scope: "project",
78
+ path: join(cwd, ".codex", "skills", "graphit", "SKILL.md"),
79
+ },
80
+ {
81
+ kind: "cursor",
82
+ scope: "project",
83
+ path: join(cwd, ".cursor", "rules", "graphit.mdc"),
84
+ },
85
+ ];
86
+ }
87
+ export function findOutdatedSkillInstalls(currentVersion, options = {}) {
88
+ const statuses = [];
89
+ for (const target of getSkillInstallTargets(options)) {
90
+ if (!existsSync(target.path))
91
+ continue;
92
+ const installedVersion = readInstalledVersion(target);
93
+ if (isPluginManagedKind(target.kind)) {
94
+ statuses.push({ ...target, installedVersion, reason: "legacy-copy-present" });
95
+ continue;
96
+ }
97
+ if (!installedVersion) {
98
+ statuses.push({ ...target, installedVersion: null, reason: "missing-version" });
99
+ continue;
100
+ }
101
+ if (compareVersions(installedVersion, currentVersion) < 0) {
102
+ statuses.push({ ...target, installedVersion, reason: "older-version" });
103
+ }
104
+ }
105
+ return statuses;
106
+ }
107
+ function formatKind(kind) {
108
+ switch (kind) {
109
+ case "claude-code":
110
+ return "Claude Code";
111
+ case "codex":
112
+ return "Codex";
113
+ case "cursor":
114
+ return "Cursor";
115
+ }
116
+ }
117
+ export function formatSkillStalenessBanner(statuses, currentVersion) {
118
+ if (statuses.length === 0)
119
+ return null;
120
+ const lines = statuses.map((status) => {
121
+ const installed = status.installedVersion ?? "unversioned";
122
+ if (status.reason === "legacy-copy-present") {
123
+ return ` - ${formatKind(status.kind)} ${status.scope}: legacy copied snapshot present (${installed})`;
124
+ }
125
+ return ` - ${formatKind(status.kind)} ${status.scope}: ${installed} → ${currentVersion}`;
126
+ });
127
+ const hasPluginManagedCopies = statuses.some((status) => isPluginManagedKind(status.kind));
128
+ const hasCursorCopies = statuses.some((status) => status.kind === "cursor");
129
+ return ("\n Legacy copied Graphit assistant files need attention:\n" +
130
+ `${lines.join("\n")}\n` +
131
+ (hasPluginManagedCopies
132
+ ? " Claude Code/Codex should use the Graphit plugin bundle. Remove old copied snapshots with `graphit setup --remove-legacy-copies` after confirming the plugin is installed.\n"
133
+ : "") +
134
+ (hasCursorCopies
135
+ ? " Cursor currently uses copied rules; refresh them with `graphit setup --editor cursor --update`.\n"
136
+ : "") +
137
+ " Update Graphit through your assistant's plugin manager whenever plugin install is available.\n\n");
138
+ }
139
+ //# sourceMappingURL=skill-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-version.js","sourceRoot":"","sources":["../src/skill-version.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
4
  import { createRequire } from "module";
5
+ import { findOutdatedSkillInstalls, formatSkillStalenessBanner, } from "./skill-version.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  const pkg = require("../package.json");
7
8
  const REGISTRY_URL = "https://registry.npmjs.org/@graphit/cli/latest";
@@ -45,13 +46,16 @@ export function getCurrentVersion() {
45
46
  }
46
47
  export function printUpdateBanner() {
47
48
  const cache = readCache();
48
- if (!cache)
49
- return;
50
49
  const current = getCurrentVersion();
51
- if (!isNewer(cache.latestVersion, current))
52
- return;
53
- process.stderr.write(`\n Update available: ${current} ${cache.latestVersion}\n` +
54
- ` Run \`npm update -g @graphit/cli\` to update\n\n`);
50
+ if (cache && isNewer(cache.latestVersion, current)) {
51
+ process.stderr.write(`\n Update available: ${current} → ${cache.latestVersion}\n` +
52
+ " Update Graphit through your assistant's plugin manager when available.\n" +
53
+ " Standalone npm users can run `npm update -g @graphit/cli`.\n\n");
54
+ }
55
+ const skillBanner = formatSkillStalenessBanner(findOutdatedSkillInstalls(current), current);
56
+ if (skillBanner) {
57
+ process.stderr.write(skillBanner);
58
+ }
55
59
  }
56
60
  export function fireBackgroundCheck() {
57
61
  const cache = readCache();
@@ -1 +1 @@
1
- {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EACL,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAE9D,MAAM,YAAY,GAAG,gDAAgD,CAAC;AACtE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;AACxD,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAOnC,SAAS,SAAS;IAChB,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB;IACnC,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,MAAc,EAAE,OAAe;IAC9C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAC9C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IAEpC,IAAI,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yBAAyB,OAAO,MAAM,KAAK,CAAC,aAAa,IAAI;YAC3D,4EAA4E;YAC5E,kEAAkE,CACrE,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,0BAA0B,CAC5C,yBAAyB,CAAC,OAAO,CAAC,EAClC,OAAO,CACR,CAAC;IACF,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,MAAM;QAAE,OAAO;IAE3D,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;SACvD,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO;QACpB,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;YAC1D,UAAU,CAAC;gBACT,aAAa,EAAG,IAA4B,CAAC,OAAO;gBACpD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE;QACV,2CAA2C;IAC7C,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,30 @@
1
+ {
2
+ "description": "Graphit plugin status checks",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "matcher": "startup|resume|clear|compact",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node",
11
+ "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/plugin-status.mjs", "--hook", "SessionStart"],
12
+ "timeout": 5
13
+ }
14
+ ]
15
+ }
16
+ ],
17
+ "UserPromptSubmit": [
18
+ {
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "node",
23
+ "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/plugin-status.mjs", "--hook", "UserPromptSubmit"],
24
+ "timeout": 5
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
30
+ }
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@graphit/cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.38",
4
4
  "description": "Graphit CLI - Build custom dashboards from any AI coding assistant",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "graphit": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsc",
10
+ "build": "npm run sync:version && tsc",
11
11
  "dev": "tsx src/index.ts",
12
- "lint": "tsc --noEmit"
12
+ "lint": "npm run check:version && tsc --noEmit",
13
+ "test": "npm run build && node --test test/*.test.mjs",
14
+ "sync:version": "node scripts/sync-plugin-version.mjs",
15
+ "check:version": "node scripts/sync-plugin-version.mjs --check"
13
16
  },
14
17
  "engines": {
15
18
  "node": ">=18"
@@ -17,6 +20,8 @@
17
20
  "files": [
18
21
  "dist",
19
22
  "skills",
23
+ "hooks",
24
+ "scripts",
20
25
  ".claude-plugin",
21
26
  ".codex-plugin"
22
27
  ],
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const REGISTRY_URL = "https://registry.npmjs.org/@graphit/cli/latest";
9
+ const TTL_MS = 24 * 60 * 60 * 1000;
10
+ const args = new Set(process.argv.slice(2));
11
+ const hookIndex = process.argv.indexOf("--hook");
12
+ const hookEvent = hookIndex >= 0 ? process.argv[hookIndex + 1] : null;
13
+ const pluginRoot =
14
+ process.env.CLAUDE_PLUGIN_ROOT ??
15
+ process.env.CODEX_PLUGIN_ROOT ??
16
+ process.env.GRAPHIT_PLUGIN_ROOT ??
17
+ dirname(dirname(fileURLToPath(import.meta.url)));
18
+ const cacheRoot =
19
+ process.env.CLAUDE_PLUGIN_DATA ??
20
+ process.env.CODEX_PLUGIN_DATA ??
21
+ process.env.GRAPHIT_PLUGIN_DATA ??
22
+ join(homedir(), ".graphit");
23
+
24
+ function readJson(path) {
25
+ return JSON.parse(readFileSync(path, "utf-8"));
26
+ }
27
+
28
+ function tryReadJson(path) {
29
+ try {
30
+ if (!existsSync(path)) return null;
31
+ return readJson(path);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function readFrontmatterVersion(path) {
38
+ try {
39
+ const content = readFileSync(path, "utf-8");
40
+ return content.match(/^skill_version:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1] ?? null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function compareVersions(left, right) {
47
+ const leftParts = left.split(".").map(Number);
48
+ const rightParts = right.split(".").map(Number);
49
+
50
+ for (let index = 0; index < 3; index += 1) {
51
+ const leftPart = leftParts[index] ?? 0;
52
+ const rightPart = rightParts[index] ?? 0;
53
+ if (leftPart > rightPart) return 1;
54
+ if (leftPart < rightPart) return -1;
55
+ }
56
+
57
+ return 0;
58
+ }
59
+
60
+ function copiedSkillTargets(home = homedir(), cwd = process.cwd()) {
61
+ return [
62
+ { kind: "claude-code", label: "Claude Code global", scope: "global", path: join(home, ".claude", "skills", "graphit", "SKILL.md") },
63
+ { kind: "codex", label: "Codex global", scope: "global", path: join(home, ".codex", "skills", "graphit", "SKILL.md") },
64
+ { kind: "cursor", label: "Cursor global", scope: "global", path: join(home, ".cursor", "rules", "graphit.mdc") },
65
+ { kind: "claude-code", label: "Claude Code project", scope: "project", path: join(cwd, ".claude", "skills", "graphit", "SKILL.md") },
66
+ { kind: "codex", label: "Codex project", scope: "project", path: join(cwd, ".codex", "skills", "graphit", "SKILL.md") },
67
+ { kind: "cursor", label: "Cursor project", scope: "project", path: join(cwd, ".cursor", "rules", "graphit.mdc") },
68
+ ];
69
+ }
70
+
71
+ function isPluginManagedKind(kind) {
72
+ return kind === "claude-code" || kind === "codex";
73
+ }
74
+
75
+ function readCopiedSkillVersion(path) {
76
+ if (!existsSync(path)) return null;
77
+ const versionJson = path.endsWith("SKILL.md")
78
+ ? tryReadJson(join(dirname(path), "VERSION.json"))
79
+ : null;
80
+ return versionJson?.version ?? readFrontmatterVersion(path);
81
+ }
82
+
83
+ function readCache(path) {
84
+ try {
85
+ if (!existsSync(path)) return null;
86
+ return readJson(path);
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ function writeCache(path, entry) {
93
+ try {
94
+ mkdirSync(dirname(path), { recursive: true });
95
+ writeFileSync(path, JSON.stringify(entry), "utf-8");
96
+ } catch {
97
+ // Cache writes should never break startup hooks.
98
+ }
99
+ }
100
+
101
+ async function getLatestVersion(currentVersion) {
102
+ if (args.has("--skip-network")) return null;
103
+
104
+ const cachePath = join(cacheRoot, "plugin-status.json");
105
+ const cache = readCache(cachePath);
106
+ if (cache?.latestVersion && Date.now() - cache.checkedAt < TTL_MS) {
107
+ return cache.latestVersion;
108
+ }
109
+
110
+ try {
111
+ const response = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1500) });
112
+ if (!response.ok) return cache?.latestVersion ?? null;
113
+ const data = await response.json();
114
+ if (data && typeof data.version === "string") {
115
+ writeCache(cachePath, { latestVersion: data.version, checkedAt: Date.now() });
116
+ return data.version;
117
+ }
118
+ } catch {
119
+ return cache?.latestVersion ?? null;
120
+ }
121
+
122
+ return currentVersion;
123
+ }
124
+
125
+ function shouldRunForPrompt() {
126
+ if (hookEvent !== "UserPromptSubmit") return true;
127
+
128
+ let input = "";
129
+ try {
130
+ input = readFileSync(0, "utf-8");
131
+ } catch {
132
+ return false;
133
+ }
134
+
135
+ const parsed = tryParseJson(input);
136
+ const prompt = String(parsed?.prompt ?? parsed?.user_prompt ?? input).toLowerCase();
137
+ return (
138
+ prompt.includes("graphit") &&
139
+ /\b(update|version|status|doctor|plugin|stale|staleness)\b/.test(prompt)
140
+ );
141
+ }
142
+
143
+ function tryParseJson(value) {
144
+ try {
145
+ return JSON.parse(value);
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
150
+
151
+ async function collectStatus() {
152
+ const packageJson = readJson(join(pluginRoot, "package.json"));
153
+ const currentVersion = packageJson.version;
154
+ const findings = [];
155
+ const metadata = [];
156
+ const claudePlugin = tryReadJson(join(pluginRoot, ".claude-plugin", "plugin.json"));
157
+ const codexPlugin = tryReadJson(join(pluginRoot, ".codex-plugin", "plugin.json"));
158
+ const marketplace = tryReadJson(join(pluginRoot, ".claude-plugin", "marketplace.json"));
159
+ const marketplacePlugin = marketplace?.plugins?.find((plugin) => plugin.name === "graphit");
160
+
161
+ const versionChecks = [
162
+ [".claude-plugin/plugin.json", claudePlugin?.version],
163
+ [".codex-plugin/plugin.json", codexPlugin?.version],
164
+ [".claude-plugin/marketplace.json metadata", marketplace?.metadata?.version],
165
+ [".claude-plugin/marketplace.json plugin", marketplacePlugin?.version],
166
+ ["skills/graphit/VERSION.json", tryReadJson(join(pluginRoot, "skills", "graphit", "VERSION.json"))?.version],
167
+ ["skills/graphit/SKILL.md", readFrontmatterVersion(join(pluginRoot, "skills", "graphit", "SKILL.md"))],
168
+ ["skills/graphit/graphit.mdc", readFrontmatterVersion(join(pluginRoot, "skills", "graphit", "graphit.mdc"))],
169
+ ];
170
+
171
+ for (const [label, version] of versionChecks) {
172
+ if (version !== currentVersion) {
173
+ findings.push({
174
+ type: "metadata-drift",
175
+ message: `${label} is ${version ?? "missing"}, expected ${currentVersion}`,
176
+ remediation: "Run `npm run sync:version` before publishing @graphit/cli.",
177
+ });
178
+ }
179
+ metadata.push({ label, version: version ?? null });
180
+ }
181
+
182
+ const marketplaceSource = marketplacePlugin?.source;
183
+ const sourceMatches =
184
+ marketplaceSource?.source === "npm" &&
185
+ marketplaceSource.package === packageJson.name &&
186
+ marketplaceSource.version === currentVersion;
187
+ metadata.push({
188
+ label: ".claude-plugin/marketplace.json source",
189
+ version: marketplaceSource?.version ?? null,
190
+ source: marketplaceSource ?? null,
191
+ });
192
+ if (!sourceMatches) {
193
+ findings.push({
194
+ type: "metadata-drift",
195
+ message: `.claude-plugin/marketplace.json source is ${
196
+ marketplaceSource ? JSON.stringify(marketplaceSource) : "missing"
197
+ }, expected npm source ${packageJson.name}@${currentVersion}`,
198
+ remediation: "Run `npm run sync:version` before publishing @graphit/cli.",
199
+ });
200
+ }
201
+
202
+ const latestVersion = await getLatestVersion(currentVersion);
203
+ if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
204
+ findings.push({
205
+ type: "package-update",
206
+ message: `@graphit/cli update available: ${currentVersion} -> ${latestVersion}`,
207
+ remediation: "Update Graphit through your assistant's plugin manager. Claude Code users can run `claude plugin update graphit`; Codex plugin users should use the Codex plugin update command. Standalone npm users can run `npm update -g @graphit/cli`.",
208
+ });
209
+ }
210
+
211
+ for (const target of copiedSkillTargets()) {
212
+ const { label, path } = target;
213
+ if (!existsSync(path)) continue;
214
+ const version = readCopiedSkillVersion(path);
215
+ if (isPluginManagedKind(target.kind)) {
216
+ findings.push({
217
+ type: "legacy-copied-skill-present",
218
+ message: `${label} legacy copied skill exists at ${path} (${version ?? "unversioned"})`,
219
+ remediation: "Claude Code and Codex should use the Graphit plugin bundle. Remove legacy copied snapshots with `graphit setup --remove-legacy-copies` after confirming the plugin is installed.",
220
+ });
221
+ continue;
222
+ }
223
+
224
+ if (!version || compareVersions(version, currentVersion) < 0) {
225
+ findings.push({
226
+ type: "copied-skill-stale",
227
+ message: `${label} copied skill is ${version ?? "unversioned"}, expected ${currentVersion}`,
228
+ remediation: "Cursor currently uses copied rules; refresh them with `graphit setup --editor cursor --update`.",
229
+ });
230
+ }
231
+ }
232
+
233
+ return {
234
+ packageName: packageJson.name,
235
+ sourceOfTruth: "plugin-bundle",
236
+ currentVersion,
237
+ latestVersion,
238
+ metadata,
239
+ findings,
240
+ };
241
+ }
242
+
243
+ function formatPlain(status) {
244
+ if (status.findings.length === 0) {
245
+ return `Graphit plugin status OK (${status.currentVersion}).`;
246
+ }
247
+
248
+ return [
249
+ `Graphit plugin status needs attention (${status.currentVersion}):`,
250
+ ...status.findings.map((finding) => `- ${finding.message}\n ${finding.remediation}`),
251
+ ].join("\n");
252
+ }
253
+
254
+ function formatHookContext(status) {
255
+ if (status.findings.length === 0) return null;
256
+
257
+ return [
258
+ "Graphit plugin status check found actionable update/version information.",
259
+ ...status.findings.map((finding) => `- ${finding.message}. ${finding.remediation}`),
260
+ ].join("\n");
261
+ }
262
+
263
+ if (hookEvent && !shouldRunForPrompt()) {
264
+ process.exit(0);
265
+ }
266
+
267
+ const status = await collectStatus();
268
+
269
+ if (args.has("--json")) {
270
+ console.log(JSON.stringify(status, null, 2));
271
+ } else if (hookEvent) {
272
+ const additionalContext = formatHookContext(status);
273
+ if (additionalContext) {
274
+ console.log(JSON.stringify({
275
+ hookSpecificOutput: {
276
+ hookEventName: hookEvent,
277
+ additionalContext,
278
+ },
279
+ }));
280
+ }
281
+ } else if (!args.has("--quiet") || status.findings.length > 0) {
282
+ console.log(formatPlain(status));
283
+ }
284
+
285
+ process.exit(status.findings.length > 0 && args.has("--fail-on-findings") ? 1 : 0);