create-byan-agent 2.9.6 → 2.9.8
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/bin/create-byan-agent-v2.js +6 -1
- package/install/lib/claude-native-setup.js +9 -0
- 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.8] - 2026-04-21
|
|
11
|
+
|
|
12
|
+
### Fixed - `update-byan-agent` CLI broken on fresh npm install
|
|
13
|
+
|
|
14
|
+
- **`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'`.
|
|
15
|
+
- **Observed on**: npm install 2.9.7, `cd ~/byan_web && update-byan-agent` → `MODULE_NOT_FOUND`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## [2.9.7] - 2026-04-21
|
|
20
|
+
|
|
21
|
+
### Fixed - MCP server empty-directory install bug
|
|
22
|
+
|
|
23
|
+
- **`copyMcpServer` now asserts `server.js` exists after copy** — previously, a partial copy could leave `_byan/mcp/byan-mcp-server/` empty, causing Claude Code to fail with `Cannot find module '.../server.js'` on the next launch. The post-copy check now throws a clear error instead of silently succeeding.
|
|
24
|
+
- **`create-byan-agent-v2.js` surfaces Claude native-setup failures in red** — prior behavior showed a yellow "partial" warning that users missed; now the failure is explicit and points at the MCP directory to inspect.
|
|
25
|
+
- **Regression test** added in `claude-native-setup.test.js` that mocks `fs.copy` to a no-op and verifies the post-copy assertion throws.
|
|
26
|
+
- **Observed on**: byan_web install with 2.9.6, dossier `_byan/mcp/byan-mcp-server/` vide, MCP failed in Claude Code.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
10
30
|
## [2.7.0] - 2026-02-21
|
|
11
31
|
|
|
12
32
|
### Added - Soul System + Tao System
|
|
@@ -1349,7 +1349,12 @@ async function install() {
|
|
|
1349
1349
|
try {
|
|
1350
1350
|
await setupClaudeNative(projectRoot);
|
|
1351
1351
|
} catch (error) {
|
|
1352
|
-
console.log(chalk.
|
|
1352
|
+
console.log(chalk.red(` ✘ Claude native setup failed: ${error.message}`));
|
|
1353
|
+
console.log(
|
|
1354
|
+
chalk.yellow(
|
|
1355
|
+
` → MCP, hooks or skills may be incomplete. Inspect _byan/mcp/byan-mcp-server/ and re-run if empty.`
|
|
1356
|
+
)
|
|
1357
|
+
);
|
|
1353
1358
|
}
|
|
1354
1359
|
}
|
|
1355
1360
|
|
|
@@ -56,6 +56,15 @@ async function copyMcpServer(projectRoot) {
|
|
|
56
56
|
if (!(await fs.pathExists(src))) return { copied: false };
|
|
57
57
|
await fs.ensureDir(dst);
|
|
58
58
|
await fs.copy(src, dst, { overwrite: true, filter: (s) => !s.includes('node_modules') });
|
|
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).
|
|
61
|
+
const serverFile = path.join(dst, 'server.js');
|
|
62
|
+
if (!(await fs.pathExists(serverFile))) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`MCP server copy produced no server.js at ${serverFile}. ` +
|
|
65
|
+
`Template source: ${src}. Re-run install or copy manually.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
59
68
|
return { copied: true, path: dst };
|
|
60
69
|
}
|
|
61
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.8",
|
|
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
|
+
}
|