@axiomatic-labs/claudeflow 2.10.71 → 2.10.72
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 +273 -1
- package/package.json +1 -1
package/lib/install.js
CHANGED
|
@@ -9,11 +9,17 @@ const ui = require('./ui.js');
|
|
|
9
9
|
// Template skills are shipped inside the ZIP. Copy every claudeflow-* skill
|
|
10
10
|
// from the release so template subskills stay in sync, while preserving any
|
|
11
11
|
// user-generated skills that are not part of the shipped asset.
|
|
12
|
+
const TEMPLATE_AGENTS = new Set(['claudeflow-explorer', 'claudeflow-planner', 'example-agent']);
|
|
13
|
+
const TEMPLATE_MANAGED_MANIFEST = path.join('.claude', 'config', 'template-managed-manifest.json');
|
|
14
|
+
const CLAUDE_BACKUP_ROOT = '.claudeflow-backups';
|
|
12
15
|
|
|
13
16
|
async function run() {
|
|
14
17
|
const current = readLocalVersion();
|
|
15
18
|
const isUpdate = !!current;
|
|
16
19
|
let copiedTemplateSkillCount = 0;
|
|
20
|
+
let staleRemovedCount = 0;
|
|
21
|
+
let managedPathCount = 0;
|
|
22
|
+
let backupPath = null;
|
|
17
23
|
|
|
18
24
|
ui.banner(current || undefined);
|
|
19
25
|
|
|
@@ -41,11 +47,27 @@ async function run() {
|
|
|
41
47
|
fs.mkdirSync(tmpExtract, { recursive: true });
|
|
42
48
|
|
|
43
49
|
const cwd = process.cwd();
|
|
50
|
+
backupPath = createPreInstallBackup(cwd, {
|
|
51
|
+
isUpdate,
|
|
52
|
+
currentVersion: current,
|
|
53
|
+
});
|
|
44
54
|
|
|
45
55
|
try {
|
|
46
56
|
ui.step('Extracting template files...');
|
|
47
57
|
execSync(`unzip -o "${tmpZip}" -d "${tmpExtract}"`, { stdio: 'pipe' });
|
|
48
58
|
const srcClaude = path.join(tmpExtract, '.claude');
|
|
59
|
+
const nextManagedPaths = collectManagedPathsFromRelease(tmpExtract);
|
|
60
|
+
const previousManifest = readTemplateManagedManifest(cwd);
|
|
61
|
+
const previousManagedPaths = previousManifest
|
|
62
|
+
? previousManifest.managedPaths
|
|
63
|
+
: collectLegacyManagedPathsFromDisk(cwd);
|
|
64
|
+
|
|
65
|
+
if (isUpdate && !previousManifest) {
|
|
66
|
+
ui.info('No template-managed manifest found. Running aggressive legacy prune for managed paths.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const staleManagedPaths = [...previousManagedPaths].filter((relPath) => !nextManagedPaths.has(relPath));
|
|
70
|
+
staleRemovedCount = pruneStaleManagedPaths(cwd, staleManagedPaths);
|
|
49
71
|
|
|
50
72
|
// Copy settings.json
|
|
51
73
|
const srcSettings = path.join(srcClaude, 'settings.json');
|
|
@@ -91,7 +113,6 @@ async function run() {
|
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
// Copy template agents (only template-managed, preserve user agents)
|
|
94
|
-
const TEMPLATE_AGENTS = new Set(['claudeflow-explorer', 'claudeflow-planner', 'example-agent']);
|
|
95
116
|
const srcAgents = path.join(srcClaude, 'agents');
|
|
96
117
|
const dstAgents = path.join(cwd, '.claude', 'agents');
|
|
97
118
|
if (fs.existsSync(srcAgents)) {
|
|
@@ -128,6 +149,11 @@ async function run() {
|
|
|
128
149
|
|
|
129
150
|
// Write version file
|
|
130
151
|
writeLocalVersion(version);
|
|
152
|
+
nextManagedPaths.add(normalizeRelativePath(path.join('.claude', '.claudeflow-version')));
|
|
153
|
+
|
|
154
|
+
// Persist managed path manifest for deterministic stale-file pruning on update
|
|
155
|
+
writeTemplateManagedManifest(cwd, version, nextManagedPaths);
|
|
156
|
+
managedPathCount = nextManagedPaths.size;
|
|
131
157
|
|
|
132
158
|
} finally {
|
|
133
159
|
fs.unlinkSync(tmpZip);
|
|
@@ -161,6 +187,11 @@ async function run() {
|
|
|
161
187
|
ui.success(`${skillCount} skills (${copiedTemplateSkillCount} template skills updated, user skills preserved)`);
|
|
162
188
|
ui.success(`${hookCount} hooks updated`);
|
|
163
189
|
ui.success(`${docCount} docs updated`);
|
|
190
|
+
ui.success(`${staleRemovedCount} stale template-managed files removed`);
|
|
191
|
+
ui.success(`Template-managed manifest updated (${managedPathCount} paths)`);
|
|
192
|
+
if (backupPath) {
|
|
193
|
+
ui.success(`Backup created: ${path.relative(cwd, backupPath)}`);
|
|
194
|
+
}
|
|
164
195
|
|
|
165
196
|
if (current === version) {
|
|
166
197
|
ui.done(`Claudeflow ${version} reinstalled.`);
|
|
@@ -171,6 +202,10 @@ async function run() {
|
|
|
171
202
|
ui.success(`${skillCount} skills installed`);
|
|
172
203
|
ui.success(`${hookCount} hooks installed`);
|
|
173
204
|
ui.success(`${docCount} docs installed`);
|
|
205
|
+
ui.success(`Template-managed manifest written (${managedPathCount} paths)`);
|
|
206
|
+
if (backupPath) {
|
|
207
|
+
ui.success(`Backup created: ${path.relative(cwd, backupPath)}`);
|
|
208
|
+
}
|
|
174
209
|
|
|
175
210
|
ui.done(`Claudeflow ${version} installed.`);
|
|
176
211
|
|
|
@@ -387,6 +422,243 @@ function homedir() {
|
|
|
387
422
|
return require('os').homedir();
|
|
388
423
|
}
|
|
389
424
|
|
|
425
|
+
function createPreInstallBackup(projectRoot, options = {}) {
|
|
426
|
+
const sourceClaudeDir = path.join(projectRoot, '.claude');
|
|
427
|
+
if (!fs.existsSync(sourceClaudeDir) || !fs.statSync(sourceClaudeDir).isDirectory()) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
432
|
+
const modeLabel = options.isUpdate ? 'update' : 'install';
|
|
433
|
+
const fromVersion = sanitizeForPath(options.currentVersion || 'none');
|
|
434
|
+
const backupDir = path.join(
|
|
435
|
+
projectRoot,
|
|
436
|
+
CLAUDE_BACKUP_ROOT,
|
|
437
|
+
`${timestamp}-${modeLabel}-from-${fromVersion}`,
|
|
438
|
+
'.claude',
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
ui.step('Creating safety backup of .claude...');
|
|
442
|
+
fs.mkdirSync(path.dirname(backupDir), { recursive: true });
|
|
443
|
+
copyDirSync(sourceClaudeDir, backupDir);
|
|
444
|
+
return path.dirname(backupDir);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function sanitizeForPath(value) {
|
|
448
|
+
return String(value || 'none').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/-+/g, '-');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function normalizeRelativePath(value) {
|
|
452
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\/+/, '');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function isPathInside(candidatePath, parentPath) {
|
|
456
|
+
const relative = path.relative(parentPath, candidatePath);
|
|
457
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function collectFilesRecursive(rootDir) {
|
|
461
|
+
if (!fs.existsSync(rootDir)) return [];
|
|
462
|
+
const files = [];
|
|
463
|
+
const stack = [rootDir];
|
|
464
|
+
while (stack.length > 0) {
|
|
465
|
+
const current = stack.pop();
|
|
466
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
467
|
+
for (const entry of entries) {
|
|
468
|
+
const fullPath = path.join(current, entry.name);
|
|
469
|
+
if (entry.isDirectory()) {
|
|
470
|
+
stack.push(fullPath);
|
|
471
|
+
} else if (entry.isFile()) {
|
|
472
|
+
files.push(fullPath);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return files;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function addFilesFromDir(set, rootBase, targetDir) {
|
|
480
|
+
for (const filePath of collectFilesRecursive(targetDir)) {
|
|
481
|
+
const relPath = normalizeRelativePath(path.relative(rootBase, filePath));
|
|
482
|
+
if (relPath.startsWith('.claude/')) {
|
|
483
|
+
set.add(relPath);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function collectManagedPathsFromRelease(extractRoot) {
|
|
489
|
+
const srcClaude = path.join(extractRoot, '.claude');
|
|
490
|
+
const managed = new Set();
|
|
491
|
+
|
|
492
|
+
const settingsPath = path.join(srcClaude, 'settings.json');
|
|
493
|
+
if (fs.existsSync(settingsPath)) {
|
|
494
|
+
managed.add('.claude/settings.json');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
['runtime', 'hooks', 'templates', 'docs'].forEach((folder) => {
|
|
498
|
+
addFilesFromDir(managed, extractRoot, path.join(srcClaude, folder));
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const skillsDir = path.join(srcClaude, 'skills');
|
|
502
|
+
if (fs.existsSync(skillsDir)) {
|
|
503
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
504
|
+
for (const entry of entries) {
|
|
505
|
+
if (entry.isDirectory() && entry.name.startsWith('claudeflow-')) {
|
|
506
|
+
addFilesFromDir(managed, extractRoot, path.join(skillsDir, entry.name));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const agentsDir = path.join(srcClaude, 'agents');
|
|
512
|
+
if (fs.existsSync(agentsDir)) {
|
|
513
|
+
for (const agentName of TEMPLATE_AGENTS) {
|
|
514
|
+
const filePath = path.join(agentsDir, `${agentName}.md`);
|
|
515
|
+
if (fs.existsSync(filePath)) {
|
|
516
|
+
managed.add(normalizeRelativePath(path.join('.claude', 'agents', `${agentName}.md`)));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const tmpDir = path.join(srcClaude, 'tmp');
|
|
522
|
+
if (fs.existsSync(tmpDir)) {
|
|
523
|
+
const entries = fs.readdirSync(tmpDir, { withFileTypes: true });
|
|
524
|
+
for (const entry of entries) {
|
|
525
|
+
if (entry.isFile()) {
|
|
526
|
+
managed.add(normalizeRelativePath(path.join('.claude', 'tmp', entry.name)));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const versionFile = path.join(srcClaude, '.claudeflow-version');
|
|
532
|
+
if (fs.existsSync(versionFile)) {
|
|
533
|
+
managed.add('.claude/.claudeflow-version');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return managed;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function collectLegacyManagedPathsFromDisk(projectRoot) {
|
|
540
|
+
const claudeflowRoot = path.join(projectRoot, '.claude');
|
|
541
|
+
const managed = new Set();
|
|
542
|
+
|
|
543
|
+
const settingsPath = path.join(claudeflowRoot, 'settings.json');
|
|
544
|
+
if (fs.existsSync(settingsPath)) {
|
|
545
|
+
managed.add('.claude/settings.json');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
['runtime', 'hooks', 'templates', 'docs'].forEach((folder) => {
|
|
549
|
+
addFilesFromDir(managed, projectRoot, path.join(claudeflowRoot, folder));
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const skillsDir = path.join(claudeflowRoot, 'skills');
|
|
553
|
+
if (fs.existsSync(skillsDir)) {
|
|
554
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
555
|
+
for (const entry of entries) {
|
|
556
|
+
if (entry.isDirectory() && entry.name.startsWith('claudeflow-')) {
|
|
557
|
+
addFilesFromDir(managed, projectRoot, path.join(skillsDir, entry.name));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const agentsDir = path.join(claudeflowRoot, 'agents');
|
|
563
|
+
if (fs.existsSync(agentsDir)) {
|
|
564
|
+
for (const agentName of TEMPLATE_AGENTS) {
|
|
565
|
+
const filePath = path.join(agentsDir, `${agentName}.md`);
|
|
566
|
+
if (fs.existsSync(filePath)) {
|
|
567
|
+
managed.add(normalizeRelativePath(path.join('.claude', 'agents', `${agentName}.md`)));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const tmpDir = path.join(claudeflowRoot, 'tmp');
|
|
573
|
+
if (fs.existsSync(tmpDir)) {
|
|
574
|
+
const entries = fs.readdirSync(tmpDir, { withFileTypes: true });
|
|
575
|
+
for (const entry of entries) {
|
|
576
|
+
if (entry.isFile() && entry.name.endsWith('.py')) {
|
|
577
|
+
managed.add(normalizeRelativePath(path.join('.claude', 'tmp', entry.name)));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const versionFile = path.join(claudeflowRoot, '.claudeflow-version');
|
|
583
|
+
if (fs.existsSync(versionFile)) {
|
|
584
|
+
managed.add('.claude/.claudeflow-version');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return managed;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function readTemplateManagedManifest(projectRoot) {
|
|
591
|
+
const manifestPath = path.join(projectRoot, TEMPLATE_MANAGED_MANIFEST);
|
|
592
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
596
|
+
if (!Array.isArray(parsed.managed_paths)) return null;
|
|
597
|
+
const managedPaths = new Set(
|
|
598
|
+
parsed.managed_paths
|
|
599
|
+
.filter((entry) => typeof entry === 'string')
|
|
600
|
+
.map((entry) => normalizeRelativePath(entry))
|
|
601
|
+
.filter((entry) => entry.startsWith('.claude/')),
|
|
602
|
+
);
|
|
603
|
+
return {
|
|
604
|
+
version: typeof parsed.version === 'string' ? parsed.version : null,
|
|
605
|
+
managedPaths,
|
|
606
|
+
};
|
|
607
|
+
} catch {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function writeTemplateManagedManifest(projectRoot, version, managedPaths) {
|
|
613
|
+
const manifestPath = path.join(projectRoot, TEMPLATE_MANAGED_MANIFEST);
|
|
614
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
615
|
+
const payload = {
|
|
616
|
+
schema_version: 1,
|
|
617
|
+
version,
|
|
618
|
+
updated_at: new Date().toISOString(),
|
|
619
|
+
managed_paths: [...managedPaths].sort(),
|
|
620
|
+
};
|
|
621
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function pruneStaleManagedPaths(projectRoot, staleRelativePaths) {
|
|
625
|
+
if (!Array.isArray(staleRelativePaths) || staleRelativePaths.length === 0) {
|
|
626
|
+
return 0;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const claudeflowRoot = path.resolve(projectRoot, '.claude');
|
|
630
|
+
let removedCount = 0;
|
|
631
|
+
const uniquePaths = [...new Set(staleRelativePaths.map((entry) => normalizeRelativePath(entry)))]
|
|
632
|
+
.filter((entry) => entry.startsWith('.claude/'))
|
|
633
|
+
.sort((a, b) => b.length - a.length);
|
|
634
|
+
|
|
635
|
+
for (const relPath of uniquePaths) {
|
|
636
|
+
const absolutePath = path.resolve(projectRoot, relPath);
|
|
637
|
+
if (!isPathInside(absolutePath, claudeflowRoot)) continue;
|
|
638
|
+
if (!fs.existsSync(absolutePath)) continue;
|
|
639
|
+
|
|
640
|
+
fs.rmSync(absolutePath, { recursive: true, force: true });
|
|
641
|
+
removedCount += 1;
|
|
642
|
+
pruneEmptyAncestors(path.dirname(absolutePath), claudeflowRoot);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return removedCount;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function pruneEmptyAncestors(startDir, stopDir) {
|
|
649
|
+
let current = startDir;
|
|
650
|
+
while (isPathInside(current, stopDir) && current !== stopDir) {
|
|
651
|
+
if (!fs.existsSync(current)) {
|
|
652
|
+
current = path.dirname(current);
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
const entries = fs.readdirSync(current);
|
|
656
|
+
if (entries.length > 0) break;
|
|
657
|
+
fs.rmdirSync(current);
|
|
658
|
+
current = path.dirname(current);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
390
662
|
// Recursively copy a directory, creating parents as needed
|
|
391
663
|
function copyDirSync(src, dst) {
|
|
392
664
|
fs.mkdirSync(dst, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.72",
|
|
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"
|