fraim 2.0.305 → 2.0.306
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.
|
@@ -293,8 +293,11 @@ const runSync = async (options) => {
|
|
|
293
293
|
});
|
|
294
294
|
if (result.success) {
|
|
295
295
|
// Local-dev sync bypasses Layer 1 (machine-level sync) below, but scripts
|
|
296
|
-
// still need to land in ~/.fraim/
|
|
296
|
+
// and docs still need to land in ~/.fraim/ or the reported counts are a lie.
|
|
297
297
|
await (0, remote_sync_1.syncScriptsToUserDir)(localRegistryFiles);
|
|
298
|
+
await (0, remote_sync_1.syncDocsToUserDir)(localRegistryFiles);
|
|
299
|
+
const { ensureUserLevelDependencies: ensureLocalUserLevelDependencies } = await Promise.resolve().then(() => __importStar(require('../setup/user-level-sync')));
|
|
300
|
+
ensureLocalUserLevelDependencies();
|
|
298
301
|
console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
|
|
299
302
|
const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
|
|
300
303
|
removeLegacyVersionFromConfig(fraimDir);
|
|
@@ -354,6 +357,12 @@ const runSync = async (options) => {
|
|
|
354
357
|
if (scriptsSynced > 0) {
|
|
355
358
|
console.log(chalk_1.default.green(` Synced ${scriptsSynced} scripts to ~/.fraim/scripts/`));
|
|
356
359
|
}
|
|
360
|
+
// 1b2. Sync docs to ~/.fraim/docs/ (Issue #1508: machine-level docs, e.g.
|
|
361
|
+
// TROUBLESHOOTING.md, must exist on disk the same way scripts do)
|
|
362
|
+
const docsSynced = await (0, remote_sync_1.syncDocsToUserDir)(registryFiles);
|
|
363
|
+
if (docsSynced > 0) {
|
|
364
|
+
console.log(chalk_1.default.green(` Synced ${docsSynced} docs to ~/.fraim/docs/`));
|
|
365
|
+
}
|
|
357
366
|
// 1c. Refresh org home
|
|
358
367
|
await refreshOrgCache(remoteUrl, apiKey);
|
|
359
368
|
// 1d. Refresh manager home
|
|
@@ -361,9 +370,12 @@ const runSync = async (options) => {
|
|
|
361
370
|
// 1e. Refresh MCP proxy launcher
|
|
362
371
|
const { ensureFraimMcpLatestLauncher } = await Promise.resolve().then(() => __importStar(require('../mcp/fraim-mcp-latest-launcher')));
|
|
363
372
|
ensureFraimMcpLatestLauncher();
|
|
364
|
-
// 1f. Ensure user-level directories
|
|
365
|
-
|
|
373
|
+
// 1f. Ensure user-level directories and runtime dependencies (Issue #1508:
|
|
374
|
+
// synced scripts like the DOCX review layer require npm deps such as
|
|
375
|
+
// adm-zip; a plain sync must install them, not just create directories)
|
|
376
|
+
const { ensureUserLevelDirectories, ensureUserLevelDependencies } = await Promise.resolve().then(() => __importStar(require('../setup/user-level-sync')));
|
|
366
377
|
ensureUserLevelDirectories();
|
|
378
|
+
ensureUserLevelDependencies();
|
|
367
379
|
// 1g. Write sync metadata
|
|
368
380
|
writeSyncMetadata('remote', remoteUrl);
|
|
369
381
|
console.log(chalk_1.default.green('✅ Machine-level sync complete.'));
|
|
@@ -12,8 +12,73 @@ exports.getScriptsChecks = getScriptsChecks;
|
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const module_1 = __importDefault(require("module"));
|
|
15
16
|
const child_process_1 = require("child_process");
|
|
16
17
|
const SCRIPTS_DIR = path_1.default.join(os_1.default.homedir(), '.fraim', 'scripts');
|
|
18
|
+
// Conservative static-require extractor: only matches literal
|
|
19
|
+
// require('name') / require("name") calls, not dynamic requires or imports.
|
|
20
|
+
const STATIC_REQUIRE_PATTERN = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
21
|
+
/**
|
|
22
|
+
* Recursively list .js/.cjs files under a directory.
|
|
23
|
+
*/
|
|
24
|
+
function listNodeScripts(dir) {
|
|
25
|
+
const results = [];
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
// Skip vendored dependency trees. A script that ships its own bundled
|
|
37
|
+
// library (e.g. ~/.fraim/scripts/lib/node_modules/@scope/pkg/...) is
|
|
38
|
+
// not itself a FRAIM script requiring dependency resolution — it IS a
|
|
39
|
+
// resolved dependency. Recursing into it produces false-positive
|
|
40
|
+
// warnings about that library's own devDependency-only files (e.g. a
|
|
41
|
+
// benchmark/test file requiring a module never meant to be installed
|
|
42
|
+
// under ~/.fraim/node_modules). Confirmed against a real machine: see
|
|
43
|
+
// issue #1508 manual validation.
|
|
44
|
+
if (entry.name === 'node_modules')
|
|
45
|
+
continue;
|
|
46
|
+
results.push(...listNodeScripts(fullPath));
|
|
47
|
+
}
|
|
48
|
+
else if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.cjs'))) {
|
|
49
|
+
results.push(fullPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract external (non-relative, non-absolute, non-builtin) module names
|
|
56
|
+
* statically required by a script, using its own directory for resolution.
|
|
57
|
+
*/
|
|
58
|
+
function extractExternalRequires(scriptPath) {
|
|
59
|
+
let content;
|
|
60
|
+
try {
|
|
61
|
+
content = fs_1.default.readFileSync(scriptPath, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const modules = new Set();
|
|
67
|
+
let match;
|
|
68
|
+
STATIC_REQUIRE_PATTERN.lastIndex = 0;
|
|
69
|
+
while ((match = STATIC_REQUIRE_PATTERN.exec(content)) !== null) {
|
|
70
|
+
const moduleName = match[1];
|
|
71
|
+
if (moduleName.startsWith('.') ||
|
|
72
|
+
moduleName.startsWith('/') ||
|
|
73
|
+
/^[a-zA-Z]:[\\/]/.test(moduleName) ||
|
|
74
|
+
moduleName.startsWith('node:') ||
|
|
75
|
+
module_1.default.builtinModules.includes(moduleName)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
modules.add(moduleName);
|
|
79
|
+
}
|
|
80
|
+
return Array.from(modules);
|
|
81
|
+
}
|
|
17
82
|
/**
|
|
18
83
|
* Check if scripts directory exists
|
|
19
84
|
*/
|
|
@@ -211,6 +276,73 @@ function checkPythonAvailability() {
|
|
|
211
276
|
}
|
|
212
277
|
};
|
|
213
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Check that synced Node scripts can resolve their external runtime
|
|
281
|
+
* dependencies (e.g. `require('adm-zip')`) from their own directory.
|
|
282
|
+
*
|
|
283
|
+
* Distinguishes delivery (the script file exists) from executability (the
|
|
284
|
+
* script's require statements actually resolve). Issue #1508: `doctor`
|
|
285
|
+
* reported the DOCX review scripts healthy while all six threw
|
|
286
|
+
* `Cannot find module 'adm-zip'` at require time.
|
|
287
|
+
*
|
|
288
|
+
* Does not execute the scripts — only statically parses `require(...)`
|
|
289
|
+
* calls and resolves them the way Node would from the script's directory.
|
|
290
|
+
*/
|
|
291
|
+
function checkNodeScriptDependencies() {
|
|
292
|
+
return {
|
|
293
|
+
name: 'Node script dependencies available',
|
|
294
|
+
category: 'scripts',
|
|
295
|
+
critical: false,
|
|
296
|
+
run: async () => {
|
|
297
|
+
if (!fs_1.default.existsSync(SCRIPTS_DIR)) {
|
|
298
|
+
return {
|
|
299
|
+
status: 'passed',
|
|
300
|
+
message: 'No scripts synced — Node dependency check skipped'
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const scripts = listNodeScripts(SCRIPTS_DIR);
|
|
304
|
+
if (scripts.length === 0) {
|
|
305
|
+
return {
|
|
306
|
+
status: 'passed',
|
|
307
|
+
message: 'No Node scripts synced — Node dependency check skipped'
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const unresolved = {};
|
|
311
|
+
for (const scriptPath of scripts) {
|
|
312
|
+
const externalModules = extractExternalRequires(scriptPath);
|
|
313
|
+
const relativeScriptPath = path_1.default.relative(SCRIPTS_DIR, scriptPath);
|
|
314
|
+
for (const moduleName of externalModules) {
|
|
315
|
+
try {
|
|
316
|
+
require.resolve(moduleName, { paths: [path_1.default.dirname(scriptPath)] });
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
if (!unresolved[moduleName])
|
|
320
|
+
unresolved[moduleName] = [];
|
|
321
|
+
unresolved[moduleName].push(relativeScriptPath);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const unresolvedModules = Object.keys(unresolved);
|
|
326
|
+
if (unresolvedModules.length === 0) {
|
|
327
|
+
return {
|
|
328
|
+
status: 'passed',
|
|
329
|
+
message: `All Node script dependencies resolve (${scripts.length} scripts checked)`,
|
|
330
|
+
details: { scriptCount: scripts.length }
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const summary = unresolvedModules
|
|
334
|
+
.map((moduleName) => `${moduleName} (${unresolved[moduleName].join(', ')})`)
|
|
335
|
+
.join('; ');
|
|
336
|
+
return {
|
|
337
|
+
status: 'warning',
|
|
338
|
+
message: `Unresolved Node script dependencies: ${summary}`,
|
|
339
|
+
suggestion: 'Run fraim sync to install user-level runtime dependencies.',
|
|
340
|
+
command: 'fraim sync',
|
|
341
|
+
details: { unresolved }
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
214
346
|
/**
|
|
215
347
|
* Get all scripts checks
|
|
216
348
|
*/
|
|
@@ -219,6 +351,7 @@ function getScriptsChecks() {
|
|
|
219
351
|
checkScriptsDirectoryExists(),
|
|
220
352
|
checkScriptsSynced(),
|
|
221
353
|
checkScriptsExecutable(),
|
|
354
|
+
checkNodeScriptDependencies(),
|
|
222
355
|
checkPythonAvailability()
|
|
223
356
|
];
|
|
224
357
|
}
|
|
@@ -69,6 +69,7 @@ const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
|
69
69
|
*/
|
|
70
70
|
const USER_LEVEL_RUNTIME_DEPS = {
|
|
71
71
|
'node-edge-tts': '*', // used by scripts/author-audio.js
|
|
72
|
+
'adm-zip': '^0.6.0', // used by scripts/communication/*.js (DOCX review layer) — matches root/packages/* manifests
|
|
72
73
|
};
|
|
73
74
|
/**
|
|
74
75
|
* Ensure the user-level FRAIM directory structure exists.
|
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.SYNCED_CONTENT_BANNER_MARKER = void 0;
|
|
15
15
|
exports.fetchRegistryFiles = fetchRegistryFiles;
|
|
16
16
|
exports.syncScriptsToUserDir = syncScriptsToUserDir;
|
|
17
|
+
exports.syncDocsToUserDir = syncDocsToUserDir;
|
|
17
18
|
exports.syncFromRemote = syncFromRemote;
|
|
18
19
|
const axios_1 = __importDefault(require("axios"));
|
|
19
20
|
const fs_1 = require("fs");
|
|
@@ -188,32 +189,51 @@ async function fetchRegistryFiles(remoteUrl, apiKey) {
|
|
|
188
189
|
return response.data.files || [];
|
|
189
190
|
}
|
|
190
191
|
/**
|
|
191
|
-
* Sync
|
|
192
|
-
*
|
|
192
|
+
* Sync a registry file subset (filtered by `type`) into a machine-level
|
|
193
|
+
* `~/.fraim/<subdir>/` directory: clean that directory, then write each
|
|
194
|
+
* matching file. Shared by syncScriptsToUserDir and syncDocsToUserDir so the
|
|
195
|
+
* clean-then-write and path-safety logic exists in exactly one place.
|
|
193
196
|
*/
|
|
194
|
-
async function
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
+
async function syncTypedFilesToUserDir(files, type, subdir, label) {
|
|
198
|
+
const matchingFiles = files.filter(f => f.type === type);
|
|
199
|
+
if (matchingFiles.length === 0)
|
|
197
200
|
return 0;
|
|
198
201
|
const userDir = (0, script_sync_utils_1.getUserFraimDir)();
|
|
199
|
-
const
|
|
200
|
-
if (!(0, fs_1.existsSync)(
|
|
201
|
-
(0, fs_1.mkdirSync)(
|
|
202
|
+
const targetDir = (0, path_1.join)(userDir, subdir);
|
|
203
|
+
if (!(0, fs_1.existsSync)(targetDir)) {
|
|
204
|
+
(0, fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
202
205
|
}
|
|
203
|
-
cleanDirectory(
|
|
204
|
-
if ((0, path_1.resolve)(candidatePath) !== (0, path_1.resolve)(
|
|
205
|
-
assertPathInsideDirectory(
|
|
206
|
+
cleanDirectory(targetDir, (candidatePath) => {
|
|
207
|
+
if ((0, path_1.resolve)(candidatePath) !== (0, path_1.resolve)(targetDir)) {
|
|
208
|
+
assertPathInsideDirectory(targetDir, candidatePath, `${label} directory`);
|
|
206
209
|
}
|
|
207
210
|
});
|
|
208
|
-
for (const file of
|
|
209
|
-
const { filePath } = resolveUserRegistryFile(
|
|
211
|
+
for (const file of matchingFiles) {
|
|
212
|
+
const { filePath } = resolveUserRegistryFile(targetDir, file.path, `${label} file`);
|
|
210
213
|
const fileDir = (0, path_1.dirname)(filePath);
|
|
211
214
|
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
212
215
|
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
213
216
|
}
|
|
214
217
|
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
215
218
|
}
|
|
216
|
-
return
|
|
219
|
+
return matchingFiles.length;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Sync script files to the user-level ~/.fraim/scripts/ directory.
|
|
223
|
+
* Extracted from syncFromRemote so it can run without a project root.
|
|
224
|
+
*/
|
|
225
|
+
async function syncScriptsToUserDir(files) {
|
|
226
|
+
return syncTypedFilesToUserDir(files, 'script', 'scripts', 'script');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Sync docs files to the user-level ~/.fraim/docs/ directory.
|
|
230
|
+
* Mirrors syncScriptsToUserDir so machine-level docs (e.g. TROUBLESHOOTING.md)
|
|
231
|
+
* are materialized on disk the same way machine-level scripts are.
|
|
232
|
+
* Issue #1508: agent guidance promises ~/.fraim/docs/TROUBLESHOOTING.md exists
|
|
233
|
+
* after sync, but only project-level fraim/docs/ was ever written.
|
|
234
|
+
*/
|
|
235
|
+
async function syncDocsToUserDir(files) {
|
|
236
|
+
return syncTypedFilesToUserDir(files, 'docs', 'docs', 'docs');
|
|
217
237
|
}
|
|
218
238
|
/**
|
|
219
239
|
* Sync jobs and scripts from remote FRAIM server
|