create-byan-agent 2.9.7 → 2.9.9
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/CHANGELOG.md +20 -0
- package/install/lib/claude-native-setup.js +12 -3
- package/package.json +2 -1
- package/update-byan-agent/lib/analyzer.js +136 -0
- package/update-byan-agent/lib/backup.js +204 -0
- package/update-byan-agent/lib/conflict-resolver.js +7 -0
- package/update-byan-agent/lib/customization-detector.js +75 -0
- package/update-byan-agent/lib/validator.js +7 -0
- package/update-byan-agent/package.json +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [2.9.9] - 2026-04-21
|
|
11
|
+
|
|
12
|
+
### Fixed - MCP copy filter broke on global npm install (root cause of 2.9.6 bug)
|
|
13
|
+
|
|
14
|
+
- **`copyMcpServer` filter now resolves paths relative to the template src** — the previous filter `(s) => !s.includes('node_modules')` inspected the absolute path, so when BYAN was installed globally (e.g. `/usr/local/lib/node_modules/create-byan-agent/...`), every template file looked like it lived under `node_modules` and was silently skipped. The empty `_byan/mcp/byan-mcp-server/` dossier observed on 2.9.6/2.9.7/2.9.8 had this cause — the post-copy assertion added in 2.9.7 only surfaced the symptom.
|
|
15
|
+
- **`makeNodeModulesFilter(srcRoot)` extracted and exported** — used by `copyMcpServer`; splits the relative path on path separators and skips any component named `node_modules`.
|
|
16
|
+
- **Regression test** added that builds a filter rooted at `/usr/local/lib/node_modules/create-byan-agent/.../byan-mcp-server` and confirms `server.js` passes while a nested `node_modules/` subdir is rejected.
|
|
17
|
+
- **Observed on**: `sudo npm install -g create-byan-agent@2.9.8` → `npx create-byan-agent` → `MCP server copy produced no server.js`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [2.9.8] - 2026-04-21
|
|
22
|
+
|
|
23
|
+
### Fixed - `update-byan-agent` CLI broken on fresh npm install
|
|
24
|
+
|
|
25
|
+
- **`update-byan-agent/` added to `package.json` `files` array** — previously only `update-byan-agent/bin/` shipped (because it was listed in `bin`), but `update-byan-agent/lib/` (analyzer, backup, customization-detector) was missing. Running `update-byan-agent` crashed with `Cannot find module '../lib/analyzer'`.
|
|
26
|
+
- **Observed on**: npm install 2.9.7, `cd ~/byan_web && update-byan-agent` → `MODULE_NOT_FOUND`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
10
30
|
## [2.9.7] - 2026-04-21
|
|
11
31
|
|
|
12
32
|
### Fixed - MCP server empty-directory install bug
|
|
@@ -50,14 +50,22 @@ async function copyClaudeSettings(projectRoot) {
|
|
|
50
50
|
return { copied: true };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function makeNodeModulesFilter(srcRoot) {
|
|
54
|
+
// Checks the path RELATIVE to srcRoot: a global install path like
|
|
55
|
+
// /usr/local/lib/node_modules/create-byan-agent/... would otherwise make
|
|
56
|
+
// every file look like it lives under node_modules and get skipped.
|
|
57
|
+
return (s) => {
|
|
58
|
+
const rel = path.relative(srcRoot, s);
|
|
59
|
+
return !rel.split(path.sep).includes('node_modules');
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
async function copyMcpServer(projectRoot) {
|
|
54
64
|
const src = path.join(TEMPLATE_ROOT, '_byan', 'mcp', 'byan-mcp-server');
|
|
55
65
|
const dst = path.join(projectRoot, '_byan', 'mcp', 'byan-mcp-server');
|
|
56
66
|
if (!(await fs.pathExists(src))) return { copied: false };
|
|
57
67
|
await fs.ensureDir(dst);
|
|
58
|
-
await fs.copy(src, dst, { overwrite: true, filter: (
|
|
59
|
-
// Post-copy sanity check: server.js must exist, otherwise the MCP client will
|
|
60
|
-
// fail on next Claude Code launch with "Cannot find module" (seen on 2.9.6).
|
|
68
|
+
await fs.copy(src, dst, { overwrite: true, filter: makeNodeModulesFilter(src) });
|
|
61
69
|
const serverFile = path.join(dst, 'server.js');
|
|
62
70
|
if (!(await fs.pathExists(serverFile))) {
|
|
63
71
|
throw new Error(
|
|
@@ -179,6 +187,7 @@ module.exports = {
|
|
|
179
187
|
copyClaudeSkills,
|
|
180
188
|
copyClaudeSettings,
|
|
181
189
|
copyMcpServer,
|
|
190
|
+
makeNodeModulesFilter,
|
|
182
191
|
generateMcpConfig,
|
|
183
192
|
installMcpDependencies,
|
|
184
193
|
setupClaudeNative,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.9",
|
|
4
4
|
"description": "BYAN v2.8 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher + native Claude Code integration (hooks, skills, MCP server). Multi-platform (Copilot CLI, Claude Code, Codex). Merise Agile + TDD + 64 Mantras. ~54% LLM cost savings.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"files": [
|
|
49
49
|
"src/",
|
|
50
50
|
"install/",
|
|
51
|
+
"update-byan-agent/",
|
|
51
52
|
"README.md",
|
|
52
53
|
"CHANGELOG.md",
|
|
53
54
|
"LICENSE"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const yaml = require('js-yaml');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Analyzer - Version checking and comparison module
|
|
8
|
+
* Validates current BYAN installation version vs npm registry
|
|
9
|
+
*/
|
|
10
|
+
class Analyzer {
|
|
11
|
+
constructor(installPath) {
|
|
12
|
+
this.installPath = installPath;
|
|
13
|
+
this.configPath = path.join(installPath, '_byan', 'bmb', 'config.yaml');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check current version from _byan/bmb/config.yaml
|
|
18
|
+
* @returns {Promise<string|null>} Current version or null if not found
|
|
19
|
+
*/
|
|
20
|
+
async checkCurrentVersion() {
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(this.configPath)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const configContent = fs.readFileSync(this.configPath, 'utf8');
|
|
27
|
+
const config = yaml.load(configContent);
|
|
28
|
+
|
|
29
|
+
return config.byan_version || null;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(`Erreur lecture config.yaml: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fetch latest version from npm registry
|
|
37
|
+
* @returns {Promise<string>} Latest version available on npm
|
|
38
|
+
*/
|
|
39
|
+
async fetchLatestVersion() {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const options = {
|
|
42
|
+
hostname: 'registry.npmjs.org',
|
|
43
|
+
path: '/create-byan-agent/latest',
|
|
44
|
+
method: 'GET',
|
|
45
|
+
headers: {
|
|
46
|
+
'Accept': 'application/json'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const req = https.request(options, (res) => {
|
|
51
|
+
let data = '';
|
|
52
|
+
|
|
53
|
+
res.on('data', (chunk) => {
|
|
54
|
+
data += chunk;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
res.on('end', () => {
|
|
58
|
+
try {
|
|
59
|
+
const json = JSON.parse(data);
|
|
60
|
+
resolve(json.version);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
reject(new Error(`Erreur parsing npm response: ${error.message}`));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
req.on('error', (error) => {
|
|
68
|
+
reject(new Error(`Erreur connexion npm registry: ${error.message}`));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
req.end();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compare two semver versions
|
|
77
|
+
* @param {string} current - Current version (e.g., "2.6.0")
|
|
78
|
+
* @param {string} latest - Latest version (e.g., "2.6.1")
|
|
79
|
+
* @returns {number} -1 if current < latest, 0 if equal, 1 if current > latest
|
|
80
|
+
*/
|
|
81
|
+
compare(current, latest) {
|
|
82
|
+
const parseCurrent = this._parseSemver(current);
|
|
83
|
+
const parseLatest = this._parseSemver(latest);
|
|
84
|
+
|
|
85
|
+
if (parseCurrent.major !== parseLatest.major) {
|
|
86
|
+
return parseCurrent.major < parseLatest.major ? -1 : 1;
|
|
87
|
+
}
|
|
88
|
+
if (parseCurrent.minor !== parseLatest.minor) {
|
|
89
|
+
return parseCurrent.minor < parseLatest.minor ? -1 : 1;
|
|
90
|
+
}
|
|
91
|
+
if (parseCurrent.patch !== parseLatest.patch) {
|
|
92
|
+
return parseCurrent.patch < parseLatest.patch ? -1 : 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse semver string to object
|
|
100
|
+
* @param {string} version - Version string (e.g., "2.6.1")
|
|
101
|
+
* @returns {{major: number, minor: number, patch: number}}
|
|
102
|
+
*/
|
|
103
|
+
_parseSemver(version) {
|
|
104
|
+
const parts = version.split('.').map(Number);
|
|
105
|
+
return {
|
|
106
|
+
major: parts[0] || 0,
|
|
107
|
+
minor: parts[1] || 0,
|
|
108
|
+
patch: parts[2] || 0
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if update is available
|
|
114
|
+
* @returns {Promise<{current: string, latest: string, needsUpdate: boolean}>}
|
|
115
|
+
*/
|
|
116
|
+
async checkVersion() {
|
|
117
|
+
const current = await this.checkCurrentVersion();
|
|
118
|
+
const latest = await this.fetchLatestVersion();
|
|
119
|
+
|
|
120
|
+
if (!current) {
|
|
121
|
+
throw new Error('Version actuelle introuvable dans config.yaml');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const comparison = this.compare(current, latest);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
current,
|
|
128
|
+
latest,
|
|
129
|
+
needsUpdate: comparison < 0,
|
|
130
|
+
upToDate: comparison === 0,
|
|
131
|
+
ahead: comparison > 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = Analyzer;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Backup - Safe backup and restore operations for BYAN updates
|
|
7
|
+
* Creates timestamped backups and manages rollback functionality
|
|
8
|
+
*/
|
|
9
|
+
class Backup {
|
|
10
|
+
constructor(installPath) {
|
|
11
|
+
this.installPath = installPath;
|
|
12
|
+
this.byanDir = path.join(installPath, '_byan');
|
|
13
|
+
this.backupBaseDir = path.join(installPath, '_byan.backup');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create backup of _byan directory with timestamp
|
|
18
|
+
* @returns {Promise<string>} Path to created backup
|
|
19
|
+
*/
|
|
20
|
+
async create() {
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(this.byanDir)) {
|
|
23
|
+
throw new Error('Directory _byan/ introuvable');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create backup directory with timestamp
|
|
27
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0] +
|
|
28
|
+
'_' + new Date().toTimeString().split(' ')[0].replace(/:/g, '-');
|
|
29
|
+
const backupPath = path.join(this.backupBaseDir, `backup-${timestamp}`);
|
|
30
|
+
|
|
31
|
+
// Create backup base directory if not exists
|
|
32
|
+
if (!fs.existsSync(this.backupBaseDir)) {
|
|
33
|
+
fs.mkdirSync(this.backupBaseDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Copy _byan to backup location
|
|
37
|
+
this._copyRecursive(this.byanDir, backupPath);
|
|
38
|
+
|
|
39
|
+
// Save backup metadata
|
|
40
|
+
const metadataPath = path.join(backupPath, '.backup-metadata.json');
|
|
41
|
+
const metadata = {
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
originalPath: this.byanDir,
|
|
44
|
+
backupPath: backupPath
|
|
45
|
+
};
|
|
46
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
47
|
+
|
|
48
|
+
return backupPath;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`Erreur creation backup: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Restore from backup directory
|
|
56
|
+
* @param {string} backupPath - Path to backup directory (optional, uses latest if not provided)
|
|
57
|
+
* @returns {Promise<boolean>} Success status
|
|
58
|
+
*/
|
|
59
|
+
async restore(backupPath = null) {
|
|
60
|
+
try {
|
|
61
|
+
// If no backup path provided, find latest
|
|
62
|
+
if (!backupPath) {
|
|
63
|
+
backupPath = await this.getLatestBackup();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(backupPath)) {
|
|
67
|
+
throw new Error(`Backup introuvable: ${backupPath}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Remove current _byan directory
|
|
71
|
+
if (fs.existsSync(this.byanDir)) {
|
|
72
|
+
this._removeRecursive(this.byanDir);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Restore from backup
|
|
76
|
+
this._copyRecursive(backupPath, this.byanDir);
|
|
77
|
+
|
|
78
|
+
// Remove backup metadata file from restored directory
|
|
79
|
+
const metadataPath = path.join(this.byanDir, '.backup-metadata.json');
|
|
80
|
+
if (fs.existsSync(metadataPath)) {
|
|
81
|
+
fs.unlinkSync(metadataPath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`Erreur restoration backup: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get latest backup path
|
|
92
|
+
* @returns {Promise<string|null>} Latest backup path or null
|
|
93
|
+
*/
|
|
94
|
+
async getLatestBackup() {
|
|
95
|
+
if (!fs.existsSync(this.backupBaseDir)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const backups = fs.readdirSync(this.backupBaseDir)
|
|
100
|
+
.filter(name => name.startsWith('backup-'))
|
|
101
|
+
.map(name => path.join(this.backupBaseDir, name))
|
|
102
|
+
.filter(p => fs.statSync(p).isDirectory())
|
|
103
|
+
.sort((a, b) => {
|
|
104
|
+
const statA = fs.statSync(a);
|
|
105
|
+
const statB = fs.statSync(b);
|
|
106
|
+
return statB.mtime.getTime() - statA.mtime.getTime();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return backups.length > 0 ? backups[0] : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* List all available backups
|
|
114
|
+
* @returns {Promise<Array>} List of backup info objects
|
|
115
|
+
*/
|
|
116
|
+
async listBackups() {
|
|
117
|
+
if (!fs.existsSync(this.backupBaseDir)) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const backups = fs.readdirSync(this.backupBaseDir)
|
|
122
|
+
.filter(name => name.startsWith('backup-'))
|
|
123
|
+
.map(name => {
|
|
124
|
+
const backupPath = path.join(this.backupBaseDir, name);
|
|
125
|
+
const metadataPath = path.join(backupPath, '.backup-metadata.json');
|
|
126
|
+
|
|
127
|
+
let metadata = {};
|
|
128
|
+
if (fs.existsSync(metadataPath)) {
|
|
129
|
+
metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const stats = fs.statSync(backupPath);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
name,
|
|
136
|
+
path: backupPath,
|
|
137
|
+
timestamp: metadata.timestamp || stats.mtime.toISOString(),
|
|
138
|
+
size: this._getDirectorySize(backupPath)
|
|
139
|
+
};
|
|
140
|
+
})
|
|
141
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
142
|
+
|
|
143
|
+
return backups;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Copy directory recursively
|
|
148
|
+
* @param {string} src - Source directory
|
|
149
|
+
* @param {string} dest - Destination directory
|
|
150
|
+
*/
|
|
151
|
+
_copyRecursive(src, dest) {
|
|
152
|
+
if (!fs.existsSync(dest)) {
|
|
153
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
157
|
+
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const srcPath = path.join(src, entry.name);
|
|
160
|
+
const destPath = path.join(dest, entry.name);
|
|
161
|
+
|
|
162
|
+
if (entry.isDirectory()) {
|
|
163
|
+
this._copyRecursive(srcPath, destPath);
|
|
164
|
+
} else {
|
|
165
|
+
fs.copyFileSync(srcPath, destPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Remove directory recursively
|
|
172
|
+
* @param {string} dirPath - Directory to remove
|
|
173
|
+
*/
|
|
174
|
+
_removeRecursive(dirPath) {
|
|
175
|
+
if (fs.existsSync(dirPath)) {
|
|
176
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get directory size in bytes
|
|
182
|
+
* @param {string} dirPath - Directory path
|
|
183
|
+
* @returns {number} Size in bytes
|
|
184
|
+
*/
|
|
185
|
+
_getDirectorySize(dirPath) {
|
|
186
|
+
let size = 0;
|
|
187
|
+
|
|
188
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
189
|
+
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
192
|
+
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
size += this._getDirectorySize(entryPath);
|
|
195
|
+
} else {
|
|
196
|
+
size += fs.statSync(entryPath).size;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return size;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = Backup;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CustomizationDetector - Detects user customizations in BYAN files
|
|
6
|
+
* Helps preserve user changes during updates
|
|
7
|
+
*/
|
|
8
|
+
class CustomizationDetector {
|
|
9
|
+
constructor(installPath) {
|
|
10
|
+
this.installPath = installPath;
|
|
11
|
+
this.byanDir = path.join(installPath, '_byan');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect customized files that should be preserved
|
|
16
|
+
* @returns {Promise<Array>} List of customized file paths
|
|
17
|
+
*/
|
|
18
|
+
async detectCustomizations() {
|
|
19
|
+
const customizations = [];
|
|
20
|
+
|
|
21
|
+
// Preserve config.yaml
|
|
22
|
+
const configPath = path.join(this.byanDir, 'bmb', 'config.yaml');
|
|
23
|
+
if (fs.existsSync(configPath)) {
|
|
24
|
+
customizations.push({
|
|
25
|
+
path: configPath,
|
|
26
|
+
type: 'config',
|
|
27
|
+
preserve: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Preserve _memory directory
|
|
32
|
+
const memoryDir = path.join(this.byanDir, '_memory');
|
|
33
|
+
if (fs.existsSync(memoryDir)) {
|
|
34
|
+
customizations.push({
|
|
35
|
+
path: memoryDir,
|
|
36
|
+
type: 'memory',
|
|
37
|
+
preserve: true
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Preserve _byan-output directory
|
|
42
|
+
const outputDir = path.join(this.installPath, '_byan-output');
|
|
43
|
+
if (fs.existsSync(outputDir)) {
|
|
44
|
+
customizations.push({
|
|
45
|
+
path: outputDir,
|
|
46
|
+
type: 'output',
|
|
47
|
+
preserve: true
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return customizations;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if a file has been modified by user
|
|
56
|
+
* @param {string} filePath - File to check
|
|
57
|
+
* @returns {Promise<boolean>} True if modified
|
|
58
|
+
*/
|
|
59
|
+
async isModified(filePath) {
|
|
60
|
+
// Simple check based on modification time
|
|
61
|
+
// In a more advanced version, could compare with original checksums
|
|
62
|
+
if (!fs.existsSync(filePath)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const stats = fs.statSync(filePath);
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const hoursSinceModification = (now - stats.mtime.getTime()) / (1000 * 60 * 60);
|
|
69
|
+
|
|
70
|
+
// If modified in last 24h, consider it customized
|
|
71
|
+
return hoursSinceModification < 24;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = CustomizationDetector;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "update-byan-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for managing BYAN updates with intelligent conflict detection and customization preservation",
|
|
5
|
+
"bin": {
|
|
6
|
+
"update-byan-agent": "bin/update-byan-agent.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node bin/update-byan-agent.js",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"lint": "eslint bin/ lib/",
|
|
12
|
+
"prepublish": "npm test"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"byan",
|
|
16
|
+
"update",
|
|
17
|
+
"version-management",
|
|
18
|
+
"conflict-resolution",
|
|
19
|
+
"backup",
|
|
20
|
+
"merge",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"bmad",
|
|
23
|
+
"merise",
|
|
24
|
+
"tdd"
|
|
25
|
+
],
|
|
26
|
+
"author": "Yan",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"commander": "^11.1.0",
|
|
31
|
+
"diff": "^5.1.0",
|
|
32
|
+
"fs-extra": "^11.2.0",
|
|
33
|
+
"inquirer": "^8.2.5",
|
|
34
|
+
"js-yaml": "^4.1.0",
|
|
35
|
+
"ora": "^5.4.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"eslint": "^8.50.0",
|
|
39
|
+
"jest": "^29.7.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"bin/",
|
|
46
|
+
"lib/",
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
]
|
|
51
|
+
}
|