@axiomatic-labs/claudeflow 2.43.31 → 2.43.33

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.
package/lib/install.js CHANGED
@@ -102,70 +102,102 @@ function syncClaudeMd(cwd, snippetPath) {
102
102
  // Place + merge the framework from an extracted payload dir into a project. Pure filesystem, no network —
103
103
  // so it is testable on a throwaway dir.
104
104
  function installFromPayload(src, cwd) {
105
- const out = { playbooks: 0, scripts: 0, skills: 0, agents: 0, claudeMd: false };
105
+ const out = { playbooks: 0, scripts: 0, skills: 0, agents: 0, claudeMd: false, failed: [] };
106
+ // Each placement step is ISOLATED: a failure in one (a copy error, a permission hiccup, a weird file in the
107
+ // user's tree) must NOT abort the rest — above all it must not skip the settings/env + CLAUDE.md config, the
108
+ // two steps a user most needs (the env block wires agent-teams + the autocompact window, etc.). Before this,
109
+ // any throw in steps 1–4 silently left settings/CLAUDE.md unconfigured. Each step records its name on failure
110
+ // so `run()` can warn instead of reporting a clean success over a partial install.
111
+ const step = (name, fn) => { try { fn(); } catch { out.failed.push(name); } };
106
112
 
107
113
  // 1) Playbooks — always refreshed (shared system).
108
- const srcPlaybooks = path.join(src, '.claudeflow', 'playbooks');
109
- if (fs.existsSync(srcPlaybooks)) {
110
- const dst = path.join(cwd, '.claudeflow', 'playbooks');
111
- rmrf(dst); copyDir(srcPlaybooks, dst);
112
- out.playbooks = countFiles(dst);
113
- }
114
+ step('playbooks', () => {
115
+ const srcPlaybooks = path.join(src, '.claudeflow', 'playbooks');
116
+ if (fs.existsSync(srcPlaybooks)) {
117
+ const dst = path.join(cwd, '.claudeflow', 'playbooks');
118
+ rmrf(dst); copyDir(srcPlaybooks, dst);
119
+ out.playbooks = countFiles(dst);
120
+ }
121
+ });
114
122
 
115
123
  // 2) Configs — per-project ones create-if-absent (never clobber a customized brand); shared refresh.
116
- fs.mkdirSync(path.join(cwd, '.claudeflow'), { recursive: true });
117
- for (const c of PER_PROJECT_CONFIGS) {
118
- const s = path.join(src, '.claudeflow', c), d = path.join(cwd, '.claudeflow', c);
119
- if (fs.existsSync(s) && !fs.existsSync(d)) fs.copyFileSync(s, d);
120
- }
121
- for (const c of SHARED_CONFIGS) {
122
- const s = path.join(src, '.claudeflow', c);
123
- if (fs.existsSync(s)) fs.copyFileSync(s, path.join(cwd, '.claudeflow', c));
124
- }
124
+ step('configs', () => {
125
+ fs.mkdirSync(path.join(cwd, '.claudeflow'), { recursive: true });
126
+ for (const c of PER_PROJECT_CONFIGS) {
127
+ const s = path.join(src, '.claudeflow', c), d = path.join(cwd, '.claudeflow', c);
128
+ if (fs.existsSync(s) && !fs.existsSync(d)) fs.copyFileSync(s, d);
129
+ }
130
+ for (const c of SHARED_CONFIGS) {
131
+ const s = path.join(src, '.claudeflow', c);
132
+ if (fs.existsSync(s)) fs.copyFileSync(s, path.join(cwd, '.claudeflow', c));
133
+ }
134
+ });
125
135
 
126
- // 3) Scripts (hooks + scans + servers) — always refreshed.
127
- const srcScripts = path.join(src, 'scripts', 'claudeflow');
128
- if (fs.existsSync(srcScripts)) {
129
- const dst = path.join(cwd, 'scripts', 'claudeflow');
130
- fs.mkdirSync(dst, { recursive: true });
131
- for (const e of fs.readdirSync(srcScripts)) { fs.copyFileSync(path.join(srcScripts, e), path.join(dst, e)); out.scripts++; }
132
- }
136
+ // 3) Scripts (hooks + scans + servers) — always refreshed. Per-file isolation so ONE bad file (a socket, a
137
+ // permission denial) can't abort the whole copy — let alone the later settings/CLAUDE.md steps.
138
+ step('scripts', () => {
139
+ const srcScripts = path.join(src, 'scripts', 'claudeflow');
140
+ if (fs.existsSync(srcScripts)) {
141
+ const dst = path.join(cwd, 'scripts', 'claudeflow');
142
+ fs.mkdirSync(dst, { recursive: true });
143
+ for (const e of fs.readdirSync(srcScripts)) {
144
+ try { fs.copyFileSync(path.join(srcScripts, e), path.join(dst, e)); out.scripts++; } catch {}
145
+ }
146
+ }
147
+ });
133
148
 
134
149
  // 4) Skills — replace each shipped claudeflow-* skill; preserve the user's own skills.
135
- const srcSkills = path.join(src, '.claude', 'skills');
136
- if (fs.existsSync(srcSkills)) {
137
- const dst = path.join(cwd, '.claude', 'skills');
138
- fs.mkdirSync(dst, { recursive: true });
139
- for (const e of fs.readdirSync(srcSkills, { withFileTypes: true })) {
140
- if (!e.isDirectory() || !e.name.startsWith('claudeflow-')) continue;
141
- const d = path.join(dst, e.name);
142
- rmrf(d); copyDir(path.join(srcSkills, e.name), d); out.skills++;
150
+ step('skills', () => {
151
+ const srcSkills = path.join(src, '.claude', 'skills');
152
+ if (fs.existsSync(srcSkills)) {
153
+ const dst = path.join(cwd, '.claude', 'skills');
154
+ fs.mkdirSync(dst, { recursive: true });
155
+ for (const e of fs.readdirSync(srcSkills, { withFileTypes: true })) {
156
+ if (!e.isDirectory() || !e.name.startsWith('claudeflow-')) continue;
157
+ const d = path.join(dst, e.name);
158
+ try { rmrf(d); copyDir(path.join(srcSkills, e.name), d); out.skills++; } catch {}
159
+ }
143
160
  }
144
- }
161
+ });
145
162
 
146
163
  // 4b) Agents — replace each shipped claudeflow-* agent; preserve the user's own agents.
147
- const srcAgents = path.join(src, '.claude', 'agents');
148
- if (fs.existsSync(srcAgents)) {
149
- const dst = path.join(cwd, '.claude', 'agents');
150
- fs.mkdirSync(dst, { recursive: true });
151
- for (const e of fs.readdirSync(srcAgents, { withFileTypes: true })) {
152
- if (!e.isFile() || !e.name.startsWith('claudeflow-') || !e.name.endsWith('.md')) continue;
153
- fs.copyFileSync(path.join(srcAgents, e.name), path.join(dst, e.name)); out.agents++;
164
+ step('agents', () => {
165
+ const srcAgents = path.join(src, '.claude', 'agents');
166
+ if (fs.existsSync(srcAgents)) {
167
+ const dst = path.join(cwd, '.claude', 'agents');
168
+ fs.mkdirSync(dst, { recursive: true });
169
+ for (const e of fs.readdirSync(srcAgents, { withFileTypes: true })) {
170
+ if (!e.isFile() || !e.name.startsWith('claudeflow-') || !e.name.endsWith('.md')) continue;
171
+ try { fs.copyFileSync(path.join(srcAgents, e.name), path.join(dst, e.name)); out.agents++; } catch {}
172
+ }
154
173
  }
155
- }
174
+ });
156
175
 
157
- // 5) settings.json hooks — MERGE (never clobber the user's settings).
158
- const srcSettings = path.join(src, '.claude', 'settings.json');
159
- if (fs.existsSync(srcSettings)) {
160
- try { mergeSettings(cwd, JSON.parse(fs.readFileSync(srcSettings, 'utf8'))); } catch {}
161
- }
176
+ // 5) settings.json — MERGE the shipped hooks + env (add-if-absent) into the user's settings (never clobber).
177
+ // ALWAYS runs (isolated above) — this is what wires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS et al.
178
+ step('settings', () => {
179
+ const srcSettings = path.join(src, '.claude', 'settings.json');
180
+ if (fs.existsSync(srcSettings)) {
181
+ mergeSettings(cwd, JSON.parse(fs.readFileSync(srcSettings, 'utf8')));
182
+ }
183
+ });
162
184
 
163
185
  // 6) CLAUDE.md — upsert the managed guidance block between markers (idempotent; updates propagate on re-install).
164
- out.claudeMd = syncClaudeMd(cwd, path.join(src, 'assets', 'CLAUDE.snippet.md'));
186
+ step('claude-md', () => {
187
+ out.claudeMd = syncClaudeMd(cwd, path.join(src, 'assets', 'CLAUDE.snippet.md'));
188
+ });
165
189
 
166
190
  return out;
167
191
  }
168
192
 
193
+ // `a < b` for `x.y.z` versions (numeric, missing parts = 0). Used to detect a STALE npx-cached CLI.
194
+ function semverLt(a, b) {
195
+ const pa = String(a).split('.').map(n => parseInt(n, 10) || 0);
196
+ const pb = String(b).split('.').map(n => parseInt(n, 10) || 0);
197
+ for (let i = 0; i < 3; i++) { if ((pa[i] || 0) < (pb[i] || 0)) return true; if ((pa[i] || 0) > (pb[i] || 0)) return false; }
198
+ return false;
199
+ }
200
+
169
201
  async function run() {
170
202
  const current = readLocalVersion();
171
203
  ui.banner(current || undefined);
@@ -181,6 +213,24 @@ async function run() {
181
213
  const version = (release.tag_name || '').replace(/^v/, '')
182
214
  || asset.name.replace(/^claudeflow-?v?/, '').replace(/\.zip$/, '');
183
215
 
216
+ // STALE-npx-CACHE GUARD. This CLI binary is whatever npx ran — and `npx <pkg> install` (no @version) will
217
+ // happily REUSE a cached OLD CLI without checking npm for a newer one. An old CLI still downloads the fresh
218
+ // release ZIP (so hooks/playbooks/scripts update — looks like it worked) but its `mergeSettings` predates
219
+ // newer placement logic (e.g. the `env` block merge added after v2.42.0), so config like the agent-teams env
220
+ // vars is SILENTLY skipped — hooks land, env never does. If this CLI's own version is OLDER than the release
221
+ // it's installing, that's exactly the stale-cache case → make it LOUD (the old CLI itself can't, but once a
222
+ // user is on a guarded version this catches every future recurrence).
223
+ let staleCli = false;
224
+ try {
225
+ const cliVersion = require('../package.json').version;
226
+ staleCli = semverLt(cliVersion, version);
227
+ if (staleCli) {
228
+ ui.warn(`Stale CLI: npx is running a CACHED v${cliVersion} but the latest release is v${version}.`);
229
+ ui.warn(` An old CLI updates hooks but can SKIP config (the agent-teams env vars, etc.). To install with`);
230
+ ui.warn(` the matching CLI: ${ui.BOLD}npx @axiomatic-labs/claudeflow@latest install${ui.RESET} (or clear the npx cache: ${ui.BOLD}npx clear-npx-cache${ui.RESET}).`);
231
+ }
232
+ } catch {}
233
+
184
234
  ui.step(`Downloading ${asset.name}...`);
185
235
  const zipBuffer = await downloadReleaseAsset(asset, token);
186
236
 
@@ -200,6 +250,9 @@ async function run() {
200
250
  ui.success(`claudeflow v${version} installed`);
201
251
  ui.info(` ${summary.playbooks} playbooks · ${summary.scripts} scripts · ${summary.skills} skills`
202
252
  + (summary.claudeMd ? ' · CLAUDE.md guidance synced' : ''));
253
+ if (summary.failed && summary.failed.length) {
254
+ ui.warn(` some steps did not complete: ${summary.failed.join(', ')}. Re-run \`npx @axiomatic-labs/claudeflow@latest install\` (the @latest avoids a stale npx cache).`);
255
+ }
203
256
  ui.done(`Describe a change and the ${ui.BOLD}claudeflow-implementer${ui.RESET} agent will build it.`);
204
257
  }
205
258
 
package/lib/version.js CHANGED
@@ -31,7 +31,7 @@ async function run() {
31
31
  if (local) {
32
32
  ui.info(`Installed: ${local}`);
33
33
  } else {
34
- ui.warn('Not installed. Run: npx @axiomatic-labs/claudeflow install');
34
+ ui.warn('Not installed. Run: npx @axiomatic-labs/claudeflow@latest install');
35
35
  }
36
36
 
37
37
  const token = getGitHubToken();
@@ -50,7 +50,7 @@ async function run() {
50
50
  if (local && local === latest) {
51
51
  ui.success('Up to date.');
52
52
  } else if (local) {
53
- ui.warn('Update available. Run: npx @axiomatic-labs/claudeflow install');
53
+ ui.warn('Update available. Run: npx @axiomatic-labs/claudeflow@latest install (the @latest avoids a stale npx cache)');
54
54
  }
55
55
  } catch (err) {
56
56
  ui.error(`Could not fetch latest release: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.43.31",
3
+ "version": "2.43.33",
4
4
  "description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
5
5
  "bin": {
6
6
  "claudeflow": "./bin/cli.js"