@a-company/paradigm 3.1.4 → 3.1.6

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,168 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-MO4EEYFW.js";
3
+
4
+ // src/commands/plugin/check.ts
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import * as os from "os";
8
+ import { exec } from "child_process";
9
+ import chalk from "chalk";
10
+ function execAsync(cmd, options = {}) {
11
+ return new Promise((resolve, reject) => {
12
+ exec(cmd, { timeout: options.timeout || 5e3, cwd: options.cwd }, (err, stdout) => {
13
+ if (err) return reject(err);
14
+ resolve(stdout.trim());
15
+ });
16
+ });
17
+ }
18
+ function readJsonSafe(filePath) {
19
+ try {
20
+ if (!fs.existsSync(filePath)) return null;
21
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ function discoverPlugins() {
27
+ const plugins = [];
28
+ const claudePluginsDir = path.join(os.homedir(), ".claude", "plugins");
29
+ const marketplacesDir = path.join(claudePluginsDir, "marketplaces");
30
+ const cacheDir = path.join(claudePluginsDir, "cache");
31
+ if (!fs.existsSync(marketplacesDir)) return plugins;
32
+ let marketplaceRepos;
33
+ try {
34
+ marketplaceRepos = fs.readdirSync(marketplacesDir).filter((d) => {
35
+ const fullPath = path.join(marketplacesDir, d);
36
+ return fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, ".git"));
37
+ });
38
+ } catch {
39
+ return plugins;
40
+ }
41
+ for (const repoDir of marketplaceRepos) {
42
+ const marketplacePath = path.join(marketplacesDir, repoDir);
43
+ const pluginsSubdir = path.join(marketplacePath, "plugins");
44
+ if (!fs.existsSync(pluginsSubdir)) continue;
45
+ let pluginDirs;
46
+ try {
47
+ pluginDirs = fs.readdirSync(pluginsSubdir).filter(
48
+ (d) => fs.statSync(path.join(pluginsSubdir, d)).isDirectory()
49
+ );
50
+ } catch {
51
+ continue;
52
+ }
53
+ for (const pluginName of pluginDirs) {
54
+ const pluginJsonPath = path.join(pluginsSubdir, pluginName, ".claude-plugin", "plugin.json");
55
+ const pluginJson = readJsonSafe(pluginJsonPath);
56
+ if (!pluginJson) continue;
57
+ const localVersion = pluginJson.version || "unknown";
58
+ const pluginCacheDir = path.join(cacheDir, repoDir, pluginName);
59
+ let installedVersion = "unknown";
60
+ if (fs.existsSync(pluginCacheDir)) {
61
+ try {
62
+ const versions = fs.readdirSync(pluginCacheDir).filter((d) => fs.statSync(path.join(pluginCacheDir, d)).isDirectory()).sort().reverse();
63
+ if (versions.length > 0) installedVersion = versions[0];
64
+ } catch {
65
+ }
66
+ }
67
+ let repo = repoDir;
68
+ try {
69
+ const gitConfig = fs.readFileSync(path.join(marketplacePath, ".git", "config"), "utf8");
70
+ const match = gitConfig.match(/github\.com[:/]([^/]+\/[^/\s.]+)/);
71
+ if (match) repo = match[1].replace(/\.git$/, "");
72
+ } catch {
73
+ }
74
+ plugins.push({ repo, plugin: pluginName, installedVersion, marketplacePath, localVersion });
75
+ }
76
+ }
77
+ return plugins;
78
+ }
79
+ async function checkPlugin(plugin) {
80
+ let remoteSha = null;
81
+ let localSha = "unknown";
82
+ try {
83
+ localSha = await execAsync("git rev-parse HEAD", { cwd: plugin.marketplacePath });
84
+ } catch {
85
+ }
86
+ try {
87
+ const output = await execAsync("git ls-remote origin HEAD", {
88
+ cwd: plugin.marketplacePath,
89
+ timeout: 5e3
90
+ });
91
+ const match = output.match(/^([a-f0-9]+)/);
92
+ if (match) remoteSha = match[1];
93
+ } catch {
94
+ }
95
+ const hasRemoteUpdate = remoteSha !== null && remoteSha !== localSha;
96
+ const hasCacheStale = plugin.localVersion !== plugin.installedVersion && plugin.installedVersion !== "unknown";
97
+ return {
98
+ repo: plugin.repo,
99
+ plugin: plugin.plugin,
100
+ installedVersion: plugin.installedVersion,
101
+ localVersion: plugin.localVersion,
102
+ remoteSha,
103
+ localSha,
104
+ marketplacePath: plugin.marketplacePath,
105
+ hasRemoteUpdate,
106
+ hasCacheStale
107
+ };
108
+ }
109
+ async function pluginCheckCommand(options = {}) {
110
+ console.log(chalk.blue("\nPlugin Update Check\n"));
111
+ const plugins = discoverPlugins();
112
+ if (plugins.length === 0) {
113
+ console.log(chalk.gray("No Claude Code plugins found in ~/.claude/plugins/marketplaces/"));
114
+ console.log(chalk.gray("Install plugins with: /plugin marketplace add <owner>/<repo>\n"));
115
+ return;
116
+ }
117
+ console.log(chalk.gray(`Checking ${plugins.length} plugin(s)...
118
+ `));
119
+ const results = await Promise.all(plugins.map((p) => checkPlugin(p)));
120
+ const updatable = results.filter((r) => r.hasRemoteUpdate || r.hasCacheStale);
121
+ for (const r of results) {
122
+ const status = r.hasRemoteUpdate ? chalk.yellow("update available") : r.hasCacheStale ? chalk.cyan("restart needed") : chalk.green("up to date");
123
+ const version = r.installedVersion !== "unknown" ? `${r.installedVersion} \u2192 ${r.localVersion}` : r.localVersion;
124
+ console.log(` ${chalk.bold(r.plugin)} ${chalk.gray(`(${r.repo})`)}`);
125
+ console.log(` Version: ${version} Status: ${status}`);
126
+ if (r.hasRemoteUpdate) {
127
+ console.log(chalk.gray(` Local: ${r.localSha?.slice(0, 8) || "?"}`));
128
+ console.log(chalk.gray(` Remote: ${r.remoteSha?.slice(0, 8) || "?"}`));
129
+ }
130
+ if (r.remoteSha === null) {
131
+ console.log(chalk.gray(" (remote check skipped \u2014 network unavailable)"));
132
+ }
133
+ console.log();
134
+ }
135
+ if (updatable.length === 0) {
136
+ console.log(chalk.green("All plugins are up to date.\n"));
137
+ return;
138
+ }
139
+ const remoteUpdates = updatable.filter((r) => r.hasRemoteUpdate);
140
+ if (options.update && remoteUpdates.length > 0) {
141
+ console.log(chalk.blue("Pulling updates...\n"));
142
+ for (const r of remoteUpdates) {
143
+ process.stdout.write(` ${r.plugin}: `);
144
+ try {
145
+ await execAsync("git pull origin main", {
146
+ cwd: r.marketplacePath,
147
+ timeout: 15e3
148
+ });
149
+ console.log(chalk.green("updated"));
150
+ } catch (err) {
151
+ console.log(chalk.red(`failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
152
+ }
153
+ }
154
+ console.log(chalk.yellow("\nRestart your Claude Code session to apply updates.\n"));
155
+ } else if (remoteUpdates.length > 0) {
156
+ console.log(chalk.yellow("To pull updates, run:"));
157
+ for (const r of remoteUpdates) {
158
+ console.log(chalk.gray(` git -C ${r.marketplacePath} pull origin main`));
159
+ }
160
+ console.log(chalk.gray("\nOr run: paradigm plugin check --update"));
161
+ console.log(chalk.yellow("\nAfter updating, restart your Claude Code session.\n"));
162
+ } else {
163
+ console.log(chalk.yellow("Restart your Claude Code session to apply cached updates.\n"));
164
+ }
165
+ }
166
+ export {
167
+ pluginCheckCommand
168
+ };
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+
34
+ // ../paradigm-mcp/src/utils/plugin-update-checker.ts
35
+ import * as fs from "fs";
36
+ import * as path from "path";
37
+ import * as os from "os";
38
+ import { exec } from "child_process";
39
+ var CLAUDE_PLUGINS_DIR = path.join(os.homedir(), ".claude", "plugins");
40
+ var CHECK_STATE_PATH = path.join(os.homedir(), ".paradigm", "plugin-update-check.json");
41
+ var THROTTLE_HOURS = 6;
42
+ function execAsync(cmd, options = {}) {
43
+ return new Promise((resolve, reject) => {
44
+ exec(cmd, { timeout: options.timeout || 3e3, cwd: options.cwd }, (err, stdout) => {
45
+ if (err) return reject(err);
46
+ resolve(stdout.trim());
47
+ });
48
+ });
49
+ }
50
+ function readJsonSafe(filePath) {
51
+ try {
52
+ if (!fs.existsSync(filePath)) return null;
53
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ function loadCheckState() {
59
+ return readJsonSafe(CHECK_STATE_PATH);
60
+ }
61
+ function saveCheckState(state) {
62
+ const dir = path.dirname(CHECK_STATE_PATH);
63
+ if (!fs.existsSync(dir)) {
64
+ fs.mkdirSync(dir, { recursive: true });
65
+ }
66
+ fs.writeFileSync(CHECK_STATE_PATH, JSON.stringify(state, null, 2));
67
+ }
68
+ function isThrottled() {
69
+ const state = loadCheckState();
70
+ if (!state) return false;
71
+ const elapsed = Date.now() - new Date(state.lastCheck).getTime();
72
+ return elapsed < THROTTLE_HOURS * 3600 * 1e3;
73
+ }
74
+ function discoverPlugins() {
75
+ const plugins = [];
76
+ const marketplacesDir = path.join(CLAUDE_PLUGINS_DIR, "marketplaces");
77
+ const cacheDir = path.join(CLAUDE_PLUGINS_DIR, "cache");
78
+ if (!fs.existsSync(marketplacesDir)) return plugins;
79
+ let marketplaceRepos;
80
+ try {
81
+ marketplaceRepos = fs.readdirSync(marketplacesDir).filter((d) => {
82
+ const fullPath = path.join(marketplacesDir, d);
83
+ return fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, ".git"));
84
+ });
85
+ } catch {
86
+ return plugins;
87
+ }
88
+ for (const repoDir of marketplaceRepos) {
89
+ const marketplacePath = path.join(marketplacesDir, repoDir);
90
+ const pluginsSubdir = path.join(marketplacePath, "plugins");
91
+ if (!fs.existsSync(pluginsSubdir)) continue;
92
+ let pluginDirs;
93
+ try {
94
+ pluginDirs = fs.readdirSync(pluginsSubdir).filter(
95
+ (d) => fs.statSync(path.join(pluginsSubdir, d)).isDirectory()
96
+ );
97
+ } catch {
98
+ continue;
99
+ }
100
+ for (const pluginName of pluginDirs) {
101
+ const pluginJsonPath = path.join(pluginsSubdir, pluginName, ".claude-plugin", "plugin.json");
102
+ const pluginJson = readJsonSafe(pluginJsonPath);
103
+ if (!pluginJson) continue;
104
+ const localVersion = pluginJson.version || "unknown";
105
+ const pluginCacheDir = path.join(cacheDir, repoDir, pluginName);
106
+ let installedVersion = "unknown";
107
+ let installedSha = "unknown";
108
+ if (fs.existsSync(pluginCacheDir)) {
109
+ try {
110
+ const versions = fs.readdirSync(pluginCacheDir).filter((d) => fs.statSync(path.join(pluginCacheDir, d)).isDirectory()).sort().reverse();
111
+ if (versions.length > 0) {
112
+ installedVersion = versions[0];
113
+ const cachedPluginJson = readJsonSafe(
114
+ path.join(pluginCacheDir, versions[0], ".claude-plugin", "plugin.json")
115
+ );
116
+ if (cachedPluginJson?.sha) {
117
+ installedSha = cachedPluginJson.sha;
118
+ }
119
+ }
120
+ } catch {
121
+ }
122
+ }
123
+ let repo = repoDir;
124
+ try {
125
+ const remoteUrl = fs.readFileSync(
126
+ path.join(marketplacePath, ".git", "config"),
127
+ "utf8"
128
+ );
129
+ const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^/\s.]+)/);
130
+ if (match) {
131
+ repo = match[1].replace(/\.git$/, "");
132
+ }
133
+ } catch {
134
+ }
135
+ plugins.push({
136
+ repo,
137
+ plugin: pluginName,
138
+ installedVersion,
139
+ installedSha,
140
+ marketplacePath,
141
+ localVersion
142
+ });
143
+ }
144
+ }
145
+ return plugins;
146
+ }
147
+ async function checkPlugin(plugin) {
148
+ let remoteSha = null;
149
+ let localSha = plugin.installedSha;
150
+ try {
151
+ localSha = await execAsync("git rev-parse HEAD", { cwd: plugin.marketplacePath });
152
+ } catch {
153
+ }
154
+ try {
155
+ const output = await execAsync("git ls-remote origin HEAD", {
156
+ cwd: plugin.marketplacePath,
157
+ timeout: 3e3
158
+ });
159
+ const match = output.match(/^([a-f0-9]+)/);
160
+ if (match) {
161
+ remoteSha = match[1];
162
+ }
163
+ } catch {
164
+ }
165
+ const hasRemoteUpdate = remoteSha !== null && remoteSha !== localSha;
166
+ const hasCacheStale = plugin.localVersion !== plugin.installedVersion && plugin.installedVersion !== "unknown";
167
+ return {
168
+ repo: plugin.repo,
169
+ plugin: plugin.plugin,
170
+ installedVersion: plugin.installedVersion,
171
+ installedSha: localSha,
172
+ localVersion: plugin.localVersion,
173
+ remoteSha,
174
+ marketplacePath: plugin.marketplacePath,
175
+ hasRemoteUpdate,
176
+ hasCacheStale
177
+ };
178
+ }
179
+ function getPluginUpdateNotice() {
180
+ const state = loadCheckState();
181
+ if (!state?.results) return null;
182
+ const updatable = state.results.filter((r) => r.hasRemoteUpdate || r.hasCacheStale);
183
+ if (updatable.length === 0) return null;
184
+ const lines = [];
185
+ lines.push("--- PLUGIN UPDATES AVAILABLE ---");
186
+ lines.push("Updates available for installed Claude Code plugins:");
187
+ const pullCmds = [];
188
+ for (const r of updatable) {
189
+ if (r.hasRemoteUpdate) {
190
+ const versionInfo = r.installedVersion !== "unknown" ? `${r.installedVersion} \u2192 ${r.localVersion}` : "newer commits available";
191
+ lines.push(` - ${r.plugin} (${r.repo}): ${versionInfo}`);
192
+ pullCmds.push(`git -C ${r.marketplacePath} pull origin main`);
193
+ } else if (r.hasCacheStale) {
194
+ lines.push(` - ${r.plugin} (${r.repo}): ${r.installedVersion} \u2192 ${r.localVersion} (restart needed)`);
195
+ }
196
+ }
197
+ if (pullCmds.length > 0) {
198
+ lines.push("");
199
+ lines.push("Tell the user about these updates and suggest running:");
200
+ lines.push(` ${pullCmds.join(" && \\\n ")}`);
201
+ lines.push("");
202
+ lines.push("After updating, the user should restart their Claude Code session.");
203
+ } else {
204
+ lines.push("");
205
+ lines.push("Restart the session to apply cached updates.");
206
+ }
207
+ lines.push("---");
208
+ return lines.join("\n");
209
+ }
210
+ function schedulePluginUpdateCheck() {
211
+ if (isThrottled()) return;
212
+ (async () => {
213
+ try {
214
+ const results = await runPluginUpdateCheck();
215
+ saveCheckState({
216
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString(),
217
+ results
218
+ });
219
+ } catch {
220
+ }
221
+ })();
222
+ }
223
+ async function runPluginUpdateCheck() {
224
+ const plugins = discoverPlugins();
225
+ if (plugins.length === 0) return [];
226
+ const results = await Promise.all(plugins.map((p) => checkPlugin(p)));
227
+ saveCheckState({
228
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString(),
229
+ results
230
+ });
231
+ return results;
232
+ }
233
+
234
+ export {
235
+ __require,
236
+ __commonJS,
237
+ __toESM,
238
+ getPluginUpdateNotice,
239
+ schedulePluginUpdateCheck,
240
+ runPluginUpdateCheck
241
+ };
@@ -3,6 +3,7 @@
3
3
  // src/commands/hooks/index.ts
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
+ import * as os from "os";
6
7
  import chalk from "chalk";
7
8
 
8
9
  // src/commands/hooks/generated-hooks.ts
@@ -247,6 +248,8 @@ fi
247
248
  # --- Check 7: Lore entry expected for significant sessions ---
248
249
  if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
249
250
  LORE_RECORDED=false
251
+
252
+ # Check git diff first (covers staged/committed lore)
250
253
  for file in $MODIFIED; do
251
254
  case "$file" in
252
255
  .paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml)
@@ -256,6 +259,17 @@ if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
256
259
  esac
257
260
  done
258
261
 
262
+ # Also check for recent lore on disk (covers MCP-written entries not yet staged)
263
+ if [ "$LORE_RECORDED" = false ]; then
264
+ TODAY=$(date -u +"%Y-%m-%d")
265
+ if [ -d ".paradigm/lore/entries/$TODAY" ]; then
266
+ ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" -name "*.yaml" 2>/dev/null | head -1)
267
+ if [ -n "$ENTRY_COUNT" ]; then
268
+ LORE_RECORDED=true
269
+ fi
270
+ fi
271
+ fi
272
+
259
273
  if [ "$LORE_RECORDED" = false ]; then
260
274
  VIOLATIONS="$VIOLATIONS
261
275
  - You modified $SOURCE_COUNT source files but recorded no lore entry.
@@ -702,6 +716,8 @@ fi
702
716
  # --- Check 7: Lore entry expected for significant sessions ---
703
717
  if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
704
718
  LORE_RECORDED=false
719
+
720
+ # Check git diff first (covers staged/committed lore)
705
721
  for file in $MODIFIED; do
706
722
  case "$file" in
707
723
  .paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml)
@@ -711,6 +727,17 @@ if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
711
727
  esac
712
728
  done
713
729
 
730
+ # Also check for recent lore on disk (covers MCP-written entries not yet staged)
731
+ if [ "$LORE_RECORDED" = false ]; then
732
+ TODAY=$(date -u +"%Y-%m-%d")
733
+ if [ -d ".paradigm/lore/entries/$TODAY" ]; then
734
+ ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" -name "*.yaml" 2>/dev/null | head -1)
735
+ if [ -n "$ENTRY_COUNT" ]; then
736
+ LORE_RECORDED=true
737
+ fi
738
+ fi
739
+ fi
740
+
714
741
  if [ "$LORE_RECORDED" = false ]; then
715
742
  VIOLATIONS="$VIOLATIONS
716
743
  - You modified $SOURCE_COUNT source files but recorded no lore entry.
@@ -918,6 +945,80 @@ exit 0
918
945
  `;
919
946
 
920
947
  // src/commands/hooks/index.ts
948
+ function isParadigmPluginActive() {
949
+ try {
950
+ const globalSettingsPath = path.join(os.homedir(), ".claude", "settings.json");
951
+ if (!fs.existsSync(globalSettingsPath)) return { active: false };
952
+ const settings = JSON.parse(fs.readFileSync(globalSettingsPath, "utf8"));
953
+ const enabled = settings.enabledPlugins?.["paradigm@a-paradigm"];
954
+ if (!enabled) return { active: false };
955
+ const cacheBase = path.join(os.homedir(), ".claude", "plugins", "cache", "a-paradigm", "paradigm");
956
+ if (!fs.existsSync(cacheBase)) return { active: false };
957
+ const versions = fs.readdirSync(cacheBase).filter((d) => fs.statSync(path.join(cacheBase, d)).isDirectory()).sort().reverse();
958
+ if (versions.length === 0) return { active: false };
959
+ const latestCache = path.join(cacheBase, versions[0]);
960
+ const hooksJson = path.join(latestCache, "hooks", "hooks.json");
961
+ if (!fs.existsSync(hooksJson)) return { active: false };
962
+ return { active: true, cacheVersion: versions[0] };
963
+ } catch {
964
+ return { active: false };
965
+ }
966
+ }
967
+ function cleanupProjectClaudeCodeHooks(rootDir) {
968
+ const removed = [];
969
+ const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
970
+ if (fs.existsSync(claudeHooksDir)) {
971
+ for (const hookName of ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
972
+ const hookPath = path.join(claudeHooksDir, hookName);
973
+ if (fs.existsSync(hookPath)) {
974
+ fs.unlinkSync(hookPath);
975
+ removed.push(hookName);
976
+ }
977
+ }
978
+ try {
979
+ const remaining = fs.readdirSync(claudeHooksDir);
980
+ if (remaining.length === 0) {
981
+ fs.rmdirSync(claudeHooksDir);
982
+ }
983
+ } catch {
984
+ }
985
+ }
986
+ const settingsPath = path.join(rootDir, ".claude", "settings.json");
987
+ if (fs.existsSync(settingsPath)) {
988
+ try {
989
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
990
+ const hooks = settings.hooks;
991
+ if (hooks) {
992
+ let modified = false;
993
+ for (const [key, arr] of Object.entries(hooks)) {
994
+ if (!Array.isArray(arr)) continue;
995
+ const filtered = arr.filter(
996
+ (h) => !JSON.stringify(h).includes("paradigm-")
997
+ );
998
+ if (filtered.length !== arr.length) {
999
+ modified = true;
1000
+ if (filtered.length === 0) {
1001
+ delete hooks[key];
1002
+ } else {
1003
+ hooks[key] = filtered;
1004
+ }
1005
+ }
1006
+ }
1007
+ if (modified) {
1008
+ if (Object.keys(hooks).length === 0) {
1009
+ delete settings.hooks;
1010
+ } else {
1011
+ settings.hooks = hooks;
1012
+ }
1013
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1014
+ removed.push("settings.json hooks");
1015
+ }
1016
+ }
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ return { cleaned: removed.length > 0, removed };
1021
+ }
921
1022
  var POST_COMMIT_HOOK = `#!/bin/sh
922
1023
  # Paradigm post-commit hook - captures history from commits
923
1024
  # Installed by: paradigm hooks install
@@ -1073,6 +1174,18 @@ async function hooksInstallCommand(options = {}) {
1073
1174
  }
1074
1175
  }
1075
1176
  async function installClaudeCodeHooks(rootDir, force) {
1177
+ const plugin = isParadigmPluginActive();
1178
+ if (plugin.active) {
1179
+ console.log(chalk.cyan(` Paradigm plugin v${plugin.cacheVersion} is active \u2014 hooks are managed by the plugin.`));
1180
+ const { cleaned, removed } = cleanupProjectClaudeCodeHooks(rootDir);
1181
+ if (cleaned) {
1182
+ console.log(chalk.green(` Cleaned up stale project hooks: ${removed.join(", ")}`));
1183
+ } else {
1184
+ console.log(chalk.gray(" No stale project hooks to clean up."));
1185
+ }
1186
+ console.log(chalk.gray(" Plugin hooks auto-update with each session \u2014 no manual install needed."));
1187
+ return;
1188
+ }
1076
1189
  const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
1077
1190
  fs.mkdirSync(claudeHooksDir, { recursive: true });
1078
1191
  const installed = [];
@@ -1327,32 +1440,60 @@ async function hooksStatusCommand() {
1327
1440
  console.log(chalk.gray("\n Not a git repository (git hooks N/A)\n"));
1328
1441
  }
1329
1442
  console.log(chalk.magenta(" Claude Code Hooks Status\n"));
1330
- const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
1331
- const claudeHooks = ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"];
1332
- for (const hookName of claudeHooks) {
1333
- const hookPath = path.join(claudeHooksDir, hookName);
1334
- if (fs.existsSync(hookPath)) {
1335
- console.log(chalk.green(` ${hookName}: installed`));
1336
- } else {
1337
- console.log(chalk.gray(` ${hookName}: not installed`));
1443
+ const plugin = isParadigmPluginActive();
1444
+ if (plugin.active) {
1445
+ console.log(chalk.cyan(` Plugin: paradigm v${plugin.cacheVersion} (active)`));
1446
+ console.log(chalk.green(" Hooks are managed by the plugin \u2014 auto-updates with each session."));
1447
+ const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
1448
+ const staleHooks = [];
1449
+ for (const hookName of ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
1450
+ if (fs.existsSync(path.join(claudeHooksDir, hookName))) {
1451
+ staleHooks.push(hookName);
1452
+ }
1338
1453
  }
1339
- }
1340
- const settingsPath = path.join(rootDir, ".claude", "settings.json");
1341
- if (fs.existsSync(settingsPath)) {
1342
- try {
1343
- const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
1344
- const hooks = settings.hooks || {};
1345
- const hasStop = JSON.stringify(hooks.Stop || []).includes("paradigm-stop.sh");
1346
- const hasPrecommit = JSON.stringify(hooks.PreToolUse || []).includes("paradigm-precommit.sh");
1347
- const hasPostwrite = JSON.stringify(hooks.PostToolUse || []).includes("paradigm-postwrite.sh");
1348
- console.log(chalk.gray(` settings.json Stop hook: ${hasStop ? "configured" : "missing"}`));
1349
- console.log(chalk.gray(` settings.json PreToolUse hook: ${hasPrecommit ? "configured" : "missing"}`));
1350
- console.log(chalk.gray(` settings.json PostToolUse hook: ${hasPostwrite ? "configured" : "missing"}`));
1351
- } catch {
1352
- console.log(chalk.yellow(" settings.json: parse error"));
1454
+ const settingsPath = path.join(rootDir, ".claude", "settings.json");
1455
+ let hasProjectHookEntries = false;
1456
+ if (fs.existsSync(settingsPath)) {
1457
+ try {
1458
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
1459
+ hasProjectHookEntries = JSON.stringify(settings.hooks || {}).includes("paradigm-");
1460
+ } catch {
1461
+ }
1462
+ }
1463
+ if (staleHooks.length > 0 || hasProjectHookEntries) {
1464
+ console.log(chalk.yellow(` WARNING: Stale project hooks detected (${staleHooks.join(", ")}${hasProjectHookEntries ? ", settings.json entries" : ""})`));
1465
+ console.log(chalk.yellow(" These shadow the plugin hooks and may run outdated logic."));
1466
+ console.log(chalk.gray(" Run `paradigm hooks install --claude-code` to clean them up."));
1353
1467
  }
1354
1468
  } else {
1355
- console.log(chalk.gray(" settings.json: not found"));
1469
+ console.log(chalk.gray(" Plugin: not active (using project-level hooks)"));
1470
+ const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
1471
+ const claudeHooks = ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"];
1472
+ for (const hookName of claudeHooks) {
1473
+ const hookPath = path.join(claudeHooksDir, hookName);
1474
+ if (fs.existsSync(hookPath)) {
1475
+ console.log(chalk.green(` ${hookName}: installed`));
1476
+ } else {
1477
+ console.log(chalk.gray(` ${hookName}: not installed`));
1478
+ }
1479
+ }
1480
+ const settingsPath = path.join(rootDir, ".claude", "settings.json");
1481
+ if (fs.existsSync(settingsPath)) {
1482
+ try {
1483
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
1484
+ const hooks = settings.hooks || {};
1485
+ const hasStop = JSON.stringify(hooks.Stop || []).includes("paradigm-stop.sh");
1486
+ const hasPrecommit = JSON.stringify(hooks.PreToolUse || []).includes("paradigm-precommit.sh");
1487
+ const hasPostwrite = JSON.stringify(hooks.PostToolUse || []).includes("paradigm-postwrite.sh");
1488
+ console.log(chalk.gray(` settings.json Stop hook: ${hasStop ? "configured" : "missing"}`));
1489
+ console.log(chalk.gray(` settings.json PreToolUse hook: ${hasPrecommit ? "configured" : "missing"}`));
1490
+ console.log(chalk.gray(` settings.json PostToolUse hook: ${hasPostwrite ? "configured" : "missing"}`));
1491
+ } catch {
1492
+ console.log(chalk.yellow(" settings.json: parse error"));
1493
+ }
1494
+ } else {
1495
+ console.log(chalk.gray(" settings.json: not found"));
1496
+ }
1356
1497
  }
1357
1498
  console.log(chalk.magenta("\n Cursor Hooks Status\n"));
1358
1499
  const cursorHooksDir = path.join(rootDir, ".cursor", "hooks");
@@ -3,7 +3,7 @@ import {
3
3
  hooksInstallCommand,
4
4
  hooksStatusCommand,
5
5
  hooksUninstallCommand
6
- } from "./chunk-6HZ7PZGG.js";
6
+ } from "./chunk-HXY6AY52.js";
7
7
  import "./chunk-MO4EEYFW.js";
8
8
  export {
9
9
  hooksInstallCommand,
package/dist/index.js CHANGED
@@ -109,7 +109,7 @@ ${chalk2.magenta("\u2569 ")}${chalk2.cyan("\u2534 \u2534\u2534\u2514\u2500\u253
109
109
  program.name("paradigm").description("Unified developer tools ecosystem").version(VERSION).addHelpText("before", banner);
110
110
  program.command("init").description("Initialize Paradigm in the current project").option("-f, --force", "Overwrite existing files").option("--name <name>", "Project name").option("--ide <ide>", "Target IDE: cursor, copilot, windsurf, claude").option("--migrate", "Output migration prompt for existing IDE files").option("--quick", "Non-interactive mode with smart defaults").option("--dry-run", "Show what would be created without creating").action(initCommand);
111
111
  program.command("shift").description("Full project setup in one command (init + team init + scan + sync all IDEs + doctor)").option("-f, --force", "Reinitialize even if already setup").option("-q, --quick", "Skip slow operations (scan)").option("--verify", "Run health checks after setup").option("--ide <ide>", "Target specific IDE instead of all").option("--configure-models", "Force model configuration prompts for team agents").action(async (options) => {
112
- const { shiftCommand } = await import("./shift-OCNF4575.js");
112
+ const { shiftCommand } = await import("./shift-YELZUPYG.js");
113
113
  await shiftCommand(options);
114
114
  });
115
115
  program.command("setup [path]").description("Interactive setup wizard for Paradigm").option("-y, --yes", "Accept all defaults (non-interactive)").option("-f, --force", "Overwrite existing .paradigm config").action(async (path2, options) => {
@@ -277,6 +277,15 @@ teamCmd.action(async () => {
277
277
  const { teamStatusCommand } = await import("./team-NWP2KJAB.js");
278
278
  await teamStatusCommand(void 0, {});
279
279
  });
280
+ var pluginCmd = program.command("plugin").description("Plugin management commands");
281
+ pluginCmd.command("check").description("Check for updates to installed Claude Code plugins").option("-u, --update", "Pull latest changes for all stale marketplace clones").action(async (options) => {
282
+ const { pluginCheckCommand } = await import("./check-UZY647TB.js");
283
+ await pluginCheckCommand(options);
284
+ });
285
+ pluginCmd.action(async () => {
286
+ const { pluginCheckCommand } = await import("./check-UZY647TB.js");
287
+ await pluginCheckCommand({});
288
+ });
280
289
  program.command("doctor").description("Health check - validate Paradigm setup").action(async () => {
281
290
  const { doctorCommand } = await import("./doctor-JBIV5PMN.js");
282
291
  await doctorCommand();
@@ -470,19 +479,19 @@ historyCmd.option("--json", "Output as JSON").action(async (options) => {
470
479
  });
471
480
  var hooksCmd = program.command("hooks").description("Git hooks for automatic history capture");
472
481
  hooksCmd.command("install").description("Install git hooks, Claude Code hooks, and Cursor hooks").option("-f, --force", "Overwrite existing hooks").option("--post-commit", "Only install post-commit hook").option("--pre-push", "Only install pre-push hook").option("--claude-code", "Only install Claude Code hooks (stop + pre-commit)").option("--cursor", "Only install Cursor hooks (.cursor/hooks.json)").action(async (options) => {
473
- const { hooksInstallCommand } = await import("./hooks-QGUF77MB.js");
482
+ const { hooksInstallCommand } = await import("./hooks-RLJFGKPF.js");
474
483
  await hooksInstallCommand(options);
475
484
  });
476
485
  hooksCmd.command("uninstall").description("Remove paradigm hooks (git hooks, or --cursor for Cursor hooks)").option("--cursor", "Remove Cursor hooks instead of git hooks").action(async (options) => {
477
- const { hooksUninstallCommand } = await import("./hooks-QGUF77MB.js");
486
+ const { hooksUninstallCommand } = await import("./hooks-RLJFGKPF.js");
478
487
  await hooksUninstallCommand(options);
479
488
  });
480
489
  hooksCmd.command("status").description("Check git hooks status").action(async () => {
481
- const { hooksStatusCommand } = await import("./hooks-QGUF77MB.js");
490
+ const { hooksStatusCommand } = await import("./hooks-RLJFGKPF.js");
482
491
  await hooksStatusCommand();
483
492
  });
484
493
  hooksCmd.action(async () => {
485
- const { hooksStatusCommand } = await import("./hooks-QGUF77MB.js");
494
+ const { hooksStatusCommand } = await import("./hooks-RLJFGKPF.js");
486
495
  await hooksStatusCommand();
487
496
  });
488
497
  var triageCmd = program.command("triage").description("Semantic error triage - incident management and pattern matching");
package/dist/mcp.js CHANGED
@@ -1,35 +1,11 @@
1
1
  #!/usr/bin/env node
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
- var __commonJS = (cb, mod) => function __require2() {
15
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
- };
17
- var __copyProps = (to, from, except, desc) => {
18
- if (from && typeof from === "object" || typeof from === "function") {
19
- for (let key of __getOwnPropNames(from))
20
- if (!__hasOwnProp.call(to, key) && key !== except)
21
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
- }
23
- return to;
24
- };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
- // If the importer is in node compatibility mode or this is not an ESM
27
- // file that has been converted to a CommonJS file using a Babel-
28
- // compatible transform (i.e. "__esModule" has not been set), then set
29
- // "default" to the CommonJS "module.exports" for node compatibility.
30
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
- mod
32
- ));
2
+ import {
3
+ __commonJS,
4
+ __require,
5
+ __toESM,
6
+ getPluginUpdateNotice,
7
+ schedulePluginUpdateCheck
8
+ } from "./chunk-4G54C4VM.js";
33
9
 
34
10
  // ../../node_modules/sql.js/dist/sql-wasm.js
35
11
  var require_sql_wasm = __commonJS({
@@ -16354,7 +16330,20 @@ function registerTools(server, getContext2, reloadContext2) {
16354
16330
  // Lore tools
16355
16331
  ...getLoreToolsList(),
16356
16332
  // Habits tools
16357
- ...getHabitsToolsList()
16333
+ ...getHabitsToolsList(),
16334
+ // Plugin update check
16335
+ {
16336
+ name: "paradigm_plugin_check",
16337
+ description: "Check for updates to installed Claude Code plugins. Reports which marketplace clones have newer remote commits and which cached versions are stale.",
16338
+ inputSchema: {
16339
+ type: "object",
16340
+ properties: {}
16341
+ },
16342
+ annotations: {
16343
+ readOnlyHint: true,
16344
+ destructiveHint: false
16345
+ }
16346
+ }
16358
16347
  ]
16359
16348
  };
16360
16349
  }
@@ -16368,8 +16357,11 @@ function registerTools(server, getContext2, reloadContext2) {
16368
16357
  const tracker2 = getSessionTracker();
16369
16358
  tracker2.setRootDir(ctx.rootDir);
16370
16359
  let recoveryPreamble = null;
16360
+ let updateNotice = null;
16371
16361
  if (!tracker2.hasRecoveredThisSession()) {
16372
16362
  recoveryPreamble = buildRecoveryPreamble(ctx.rootDir);
16363
+ updateNotice = getPluginUpdateNotice();
16364
+ schedulePluginUpdateCheck();
16373
16365
  tracker2.markRecovered();
16374
16366
  }
16375
16367
  const toolResult = await (async () => {
@@ -16802,6 +16794,37 @@ function registerTools(server, getContext2, reloadContext2) {
16802
16794
  }]
16803
16795
  };
16804
16796
  }
16797
+ case "paradigm_plugin_check": {
16798
+ const { runPluginUpdateCheck } = await import("./plugin-update-checker-EWT7YMDF.js");
16799
+ const results = await runPluginUpdateCheck();
16800
+ const updatable = results.filter((r) => r.hasRemoteUpdate || r.hasCacheStale);
16801
+ if (updatable.length === 0) {
16802
+ const msg = results.length === 0 ? "No Claude Code plugins found in ~/.claude/plugins/marketplaces/." : "All installed plugins are up to date.";
16803
+ trackToolCall(msg.length, name);
16804
+ return { content: [{ type: "text", text: msg }] };
16805
+ }
16806
+ const lines = ["Plugin updates available:\n"];
16807
+ const pullCmds = [];
16808
+ for (const r of updatable) {
16809
+ if (r.hasRemoteUpdate) {
16810
+ lines.push(` ${r.plugin} (${r.repo}): remote has newer commits`);
16811
+ pullCmds.push(`git -C ${r.marketplacePath} pull origin main`);
16812
+ } else if (r.hasCacheStale) {
16813
+ lines.push(` ${r.plugin} (${r.repo}): ${r.installedVersion} \u2192 ${r.localVersion} (restart needed)`);
16814
+ }
16815
+ }
16816
+ if (pullCmds.length > 0) {
16817
+ lines.push(`
16818
+ Update command:
16819
+ ${pullCmds.join(" && \\\n ")}`);
16820
+ lines.push("\nAfter running, restart the session to apply updates.");
16821
+ } else {
16822
+ lines.push("\nRestart the session to apply cached updates.");
16823
+ }
16824
+ const pluginText = lines.join("\n");
16825
+ trackToolCall(pluginText.length, name);
16826
+ return { content: [{ type: "text", text: pluginText }] };
16827
+ }
16805
16828
  default: {
16806
16829
  if (name === "paradigm_navigate") {
16807
16830
  const result = await handleNavigateTool(name, args, ctx);
@@ -16934,10 +16957,11 @@ function registerTools(server, getContext2, reloadContext2) {
16934
16957
  }
16935
16958
  }
16936
16959
  })();
16937
- if (recoveryPreamble) {
16960
+ if (recoveryPreamble || updateNotice) {
16938
16961
  const first = toolResult.content?.[0];
16939
16962
  if (first && typeof first === "object" && "text" in first && typeof first.text === "string") {
16940
- first.text = recoveryPreamble + "\n\n" + first.text;
16963
+ const preamble = [updateNotice, recoveryPreamble].filter(Boolean).join("\n\n");
16964
+ first.text = preamble + "\n\n" + first.text;
16941
16965
  }
16942
16966
  }
16943
16967
  return toolResult;
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getPluginUpdateNotice,
4
+ runPluginUpdateCheck,
5
+ schedulePluginUpdateCheck
6
+ } from "./chunk-4G54C4VM.js";
7
+ export {
8
+ getPluginUpdateNotice,
9
+ runPluginUpdateCheck,
10
+ schedulePluginUpdateCheck
11
+ };
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-PMXRGPRQ.js";
13
13
  import {
14
14
  hooksInstallCommand
15
- } from "./chunk-6HZ7PZGG.js";
15
+ } from "./chunk-HXY6AY52.js";
16
16
  import {
17
17
  indexCommand,
18
18
  initCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-company/paradigm",
3
- "version": "3.1.4",
3
+ "version": "3.1.6",
4
4
  "description": "Unified CLI for Paradigm developer tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",