monoai 0.2.6 ā 0.2.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/README.md +15 -0
- package/dist/commands/login.js +12 -12
- package/dist/commands/push.js +184 -49
- package/dist/index.js +1 -1
- package/dist/utils/ast-extractor.js +24 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -56,4 +56,19 @@ coverage
|
|
|
56
56
|
*.log
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## .monoaiwhitelist (optional, include-only)
|
|
60
|
+
|
|
61
|
+
`monoai push` can also read `.monoaiwhitelist` as an include-only filter.
|
|
62
|
+
When this file exists and has rules, only matched paths are scanned and uploaded.
|
|
63
|
+
|
|
64
|
+
Use this when you run `monoai push` from a monorepo root but want to sync only one app (for example `step4.vite-web-migration`).
|
|
65
|
+
|
|
66
|
+
```gitignore
|
|
67
|
+
# Example: sync only step4 project
|
|
68
|
+
step4.vite-web-migration/**
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Behavior summary:
|
|
72
|
+
- `.monoaiwhitelist` limits scope first (include-only).
|
|
73
|
+
- `.gitignore` and `.monoaiignore` still exclude paths inside that scope.
|
|
59
74
|
|
package/dist/commands/login.js
CHANGED
|
@@ -11,25 +11,25 @@ const CONVEX_SITE_URL = process.env.MONOAI_CONVEX_SITE_URL ||
|
|
|
11
11
|
'https://majestic-crane-609.convex.site';
|
|
12
12
|
const WEB_URL = 'https://monoai.space';
|
|
13
13
|
export const loginCommand = new Command('login')
|
|
14
|
-
.description('
|
|
14
|
+
.description('Sign in to MonoAI')
|
|
15
15
|
.action(async () => {
|
|
16
|
-
console.log(chalk.blue('š Starting
|
|
16
|
+
console.log(chalk.blue('š Starting sign-in...'));
|
|
17
17
|
try {
|
|
18
18
|
// 1. Init Session
|
|
19
|
-
const initSpinner = ora('
|
|
19
|
+
const initSpinner = ora('Preparing secure sign-in...').start();
|
|
20
20
|
const initRes = await axios.post(`${CONVEX_SITE_URL}/cli/auth/init`, {
|
|
21
21
|
deviceDescription: process.platform
|
|
22
22
|
});
|
|
23
23
|
initSpinner.succeed();
|
|
24
24
|
const { tempCode } = initRes.data;
|
|
25
25
|
const loginUrl = `${WEB_URL}/auth/cli?code=${tempCode}`;
|
|
26
|
-
console.log(chalk.yellow(`\nš Verification
|
|
27
|
-
console.log(chalk.dim(` Opening browser
|
|
26
|
+
console.log(chalk.yellow(`\nš Verification code: ${chalk.bold(tempCode)}`));
|
|
27
|
+
console.log(chalk.dim(` Opening your browser. If it does not open, visit:`));
|
|
28
28
|
console.log(chalk.underline(loginUrl));
|
|
29
29
|
console.log('\n');
|
|
30
30
|
await open(loginUrl);
|
|
31
31
|
// 2. Poll Status
|
|
32
|
-
const pollSpinner = ora('Waiting for approval in browser...').start();
|
|
32
|
+
const pollSpinner = ora('Waiting for approval in your browser...').start();
|
|
33
33
|
let attempts = 0;
|
|
34
34
|
const maxAttempts = 60; // 2 minutes (2s * 60)
|
|
35
35
|
const pollInterval = setInterval(async () => {
|
|
@@ -41,30 +41,30 @@ export const loginCommand = new Command('login')
|
|
|
41
41
|
config.set('auth_token', token);
|
|
42
42
|
config.set('user_id', userId);
|
|
43
43
|
config.set('convex_url', CONVEX_SITE_URL); // Store for future use
|
|
44
|
-
pollSpinner.succeed(chalk.green('ā
|
|
45
|
-
console.log(chalk.dim(`
|
|
44
|
+
pollSpinner.succeed(chalk.green('ā
Sign-in complete.'));
|
|
45
|
+
console.log(chalk.dim(` Credentials saved to ${config.path}`));
|
|
46
46
|
process.exit(0);
|
|
47
47
|
}
|
|
48
48
|
else if (status === 'expired' || status === 'rejected') {
|
|
49
49
|
clearInterval(pollInterval);
|
|
50
|
-
pollSpinner.fail(chalk.red(`ā
|
|
50
|
+
pollSpinner.fail(chalk.red(`ā Sign-in ${status}. Please try again.`));
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
else {
|
|
54
54
|
attempts++;
|
|
55
55
|
if (attempts >= maxAttempts) {
|
|
56
56
|
clearInterval(pollInterval);
|
|
57
|
-
pollSpinner.fail(chalk.red('ā
|
|
57
|
+
pollSpinner.fail(chalk.red('ā Sign-in timed out.'));
|
|
58
58
|
process.exit(1);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
catch (err) {
|
|
63
|
-
// Ignore
|
|
63
|
+
// Ignore temporary polling errors
|
|
64
64
|
}
|
|
65
65
|
}, 2000);
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
-
console.error(chalk.red('\nā
|
|
68
|
+
console.error(chalk.red('\nā Sign-in failed:'), error.message);
|
|
69
69
|
}
|
|
70
70
|
});
|
package/dist/commands/push.js
CHANGED
|
@@ -10,6 +10,7 @@ import { extractSkeleton } from '../utils/ast-extractor.js';
|
|
|
10
10
|
const git = simpleGit();
|
|
11
11
|
const config = new Conf({ projectName: 'monoai' });
|
|
12
12
|
const MONOAIIGNORE_FILENAME = '.monoaiignore';
|
|
13
|
+
const MONOAIWHITELIST_FILENAME = '.monoaiwhitelist';
|
|
13
14
|
const DEFAULT_MONOAIIGNORE = `# MonoAI AST scan ignore rules
|
|
14
15
|
# Uses .gitignore-style patterns.
|
|
15
16
|
|
|
@@ -26,67 +27,180 @@ coverage
|
|
|
26
27
|
**/.agent/**
|
|
27
28
|
*.log
|
|
28
29
|
`;
|
|
30
|
+
function loadMonoaiWhitelist(cwd) {
|
|
31
|
+
const whitelistPath = path.join(cwd, MONOAIWHITELIST_FILENAME);
|
|
32
|
+
if (!fs.existsSync(whitelistPath)) {
|
|
33
|
+
return { matcher: null, ruleCount: 0 };
|
|
34
|
+
}
|
|
35
|
+
const rules = fs.readFileSync(whitelistPath, 'utf8')
|
|
36
|
+
.split(/\r?\n/g)
|
|
37
|
+
.map((line) => line.trim())
|
|
38
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
39
|
+
if (rules.length === 0) {
|
|
40
|
+
return { matcher: null, ruleCount: 0 };
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
matcher: ignore().add(rules),
|
|
44
|
+
ruleCount: rules.length,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function normalizeGitFilePath(value) {
|
|
48
|
+
return String(value || "")
|
|
49
|
+
.trim()
|
|
50
|
+
.replace(/\\/g, "/")
|
|
51
|
+
.replace(/^\.?\//, "");
|
|
52
|
+
}
|
|
53
|
+
function scopePrefix(filePath, depth) {
|
|
54
|
+
const parts = normalizeGitFilePath(filePath).split("/").filter(Boolean);
|
|
55
|
+
if (parts.length === 0)
|
|
56
|
+
return "";
|
|
57
|
+
return parts.slice(0, Math.min(depth, parts.length)).join("/");
|
|
58
|
+
}
|
|
59
|
+
function asNumber(value) {
|
|
60
|
+
const n = Number(value);
|
|
61
|
+
if (!Number.isFinite(n))
|
|
62
|
+
return 0;
|
|
63
|
+
return Math.max(0, Math.round(n));
|
|
64
|
+
}
|
|
65
|
+
function buildChangedFileSignals(diffFiles) {
|
|
66
|
+
const rows = Array.isArray(diffFiles) ? diffFiles : [];
|
|
67
|
+
return rows
|
|
68
|
+
.map((file) => {
|
|
69
|
+
const normalizedPath = normalizeGitFilePath(String(file?.file || ""));
|
|
70
|
+
if (!normalizedPath)
|
|
71
|
+
return null;
|
|
72
|
+
const insertions = asNumber(file?.insertions);
|
|
73
|
+
const deletions = asNumber(file?.deletions);
|
|
74
|
+
const changes = Math.max(asNumber(file?.changes), insertions + deletions);
|
|
75
|
+
const ext = path.extname(normalizedPath).replace(".", "").toLowerCase() || "none";
|
|
76
|
+
return {
|
|
77
|
+
path: normalizedPath,
|
|
78
|
+
insertions,
|
|
79
|
+
deletions,
|
|
80
|
+
changes,
|
|
81
|
+
scope1: scopePrefix(normalizedPath, 1),
|
|
82
|
+
scope2: scopePrefix(normalizedPath, 2),
|
|
83
|
+
extension: ext,
|
|
84
|
+
};
|
|
85
|
+
})
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
}
|
|
88
|
+
function buildGraphInsightsFromChanges(files) {
|
|
89
|
+
if (!Array.isArray(files) || files.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
const byScope = new Map();
|
|
92
|
+
const byExtension = new Map();
|
|
93
|
+
for (const row of files) {
|
|
94
|
+
const scope = row.scope2 || row.scope1 || "root";
|
|
95
|
+
const churn = Math.max(1, row.changes);
|
|
96
|
+
const currentScope = byScope.get(scope) || { files: 0, churn: 0 };
|
|
97
|
+
currentScope.files += 1;
|
|
98
|
+
currentScope.churn += churn;
|
|
99
|
+
byScope.set(scope, currentScope);
|
|
100
|
+
const extCount = byExtension.get(row.extension) || 0;
|
|
101
|
+
byExtension.set(row.extension, extCount + 1);
|
|
102
|
+
}
|
|
103
|
+
const hotFiles = files
|
|
104
|
+
.slice()
|
|
105
|
+
.sort((a, b) => b.changes - a.changes)
|
|
106
|
+
.slice(0, 8)
|
|
107
|
+
.map((row) => `changed_file:${row.path} (+${row.insertions}/-${row.deletions})`);
|
|
108
|
+
const hotScopes = Array.from(byScope.entries())
|
|
109
|
+
.sort((a, b) => {
|
|
110
|
+
if (b[1].churn !== a[1].churn)
|
|
111
|
+
return b[1].churn - a[1].churn;
|
|
112
|
+
return b[1].files - a[1].files;
|
|
113
|
+
})
|
|
114
|
+
.slice(0, 6)
|
|
115
|
+
.map(([scope, stat]) => `changed_scope:${scope} files=${stat.files} churn=${stat.churn}`);
|
|
116
|
+
const extensionMix = Array.from(byExtension.entries())
|
|
117
|
+
.sort((a, b) => b[1] - a[1])
|
|
118
|
+
.slice(0, 4)
|
|
119
|
+
.map(([ext, count]) => `extension_mix:${ext}=${count}`);
|
|
120
|
+
return Array.from(new Set([
|
|
121
|
+
`changed_total_files:${files.length}`,
|
|
122
|
+
...hotScopes,
|
|
123
|
+
...hotFiles,
|
|
124
|
+
...extensionMix,
|
|
125
|
+
])).slice(0, 24);
|
|
126
|
+
}
|
|
29
127
|
export const pushCommand = new Command('push')
|
|
30
|
-
.description('
|
|
31
|
-
.
|
|
128
|
+
.description('Sync your codebase structure to MonoAI')
|
|
129
|
+
.option('-v, --verbose', 'Show internal pipeline logs')
|
|
130
|
+
.option('-f, --force', 'Force sync even if the same commit was already uploaded')
|
|
131
|
+
.action(async (options) => {
|
|
132
|
+
const verbose = !!options?.verbose;
|
|
133
|
+
const force = !!options?.force;
|
|
32
134
|
const totalStart = Date.now();
|
|
33
135
|
const stageTimes = [];
|
|
136
|
+
const logDetail = (message) => {
|
|
137
|
+
if (verbose)
|
|
138
|
+
console.log(chalk.dim(message));
|
|
139
|
+
};
|
|
34
140
|
const track = (stage, fn) => {
|
|
35
141
|
const start = Date.now();
|
|
36
142
|
return fn().then((result) => {
|
|
37
143
|
const ms = Date.now() - start;
|
|
38
144
|
stageTimes.push({ stage, ms });
|
|
39
|
-
|
|
145
|
+
logDetail(` ā± ${stage}: ${(ms / 1000).toFixed(2)}s`);
|
|
40
146
|
return result;
|
|
41
147
|
});
|
|
42
148
|
};
|
|
43
149
|
try {
|
|
44
|
-
console.log(chalk.blue('šļø Starting
|
|
150
|
+
console.log(chalk.blue('šļø Starting codebase sync...'));
|
|
45
151
|
// 0. Auth Check
|
|
46
152
|
const token = config.get('auth_token');
|
|
47
153
|
if (!token) {
|
|
48
|
-
console.error(chalk.red('ā
|
|
154
|
+
console.error(chalk.red('ā You are not signed in. Please run:'));
|
|
49
155
|
console.error(chalk.white(' npx monoai login'));
|
|
50
156
|
return;
|
|
51
157
|
}
|
|
52
158
|
const isRepo = await git.checkIsRepo();
|
|
53
159
|
if (!isRepo) {
|
|
54
|
-
console.error(chalk.red('ā
|
|
160
|
+
console.error(chalk.red('ā This folder is not a Git repository.'));
|
|
55
161
|
return;
|
|
56
162
|
}
|
|
163
|
+
const { matcher: whitelistMatcher, ruleCount: whitelistRuleCount } = loadMonoaiWhitelist(process.cwd());
|
|
164
|
+
const isWhitelisted = (relativePath) => !whitelistMatcher || whitelistMatcher.ignores(normalizeGitFilePath(relativePath));
|
|
165
|
+
if (whitelistMatcher) {
|
|
166
|
+
console.log(chalk.blue(`šÆ Applying ${MONOAIWHITELIST_FILENAME} (${whitelistRuleCount} rule${whitelistRuleCount > 1 ? 's' : ''})`));
|
|
167
|
+
}
|
|
57
168
|
// 1. Git Metadata (Zero-HITL Intent)
|
|
58
|
-
const { lastCommit, branch, changedScopes } = await track('git metadata', async () => {
|
|
169
|
+
const { lastCommit, branch, changedScopes, graphInsights } = await track('git metadata', async () => {
|
|
59
170
|
const log = await git.log({ maxCount: 1 });
|
|
60
171
|
const lastCommit = log.latest;
|
|
61
172
|
const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
62
173
|
if (!lastCommit) {
|
|
63
174
|
throw new Error('No commits found.');
|
|
64
175
|
}
|
|
65
|
-
let
|
|
176
|
+
let changedFiles = [];
|
|
66
177
|
try {
|
|
67
178
|
const diffSummary = await git.diffSummary(['HEAD~1', 'HEAD']);
|
|
68
|
-
|
|
69
|
-
const parts = f.file.split('/');
|
|
70
|
-
return parts.length > 1 ? parts[0] : f.file;
|
|
71
|
-
})));
|
|
179
|
+
changedFiles = buildChangedFileSignals(diffSummary?.files || []);
|
|
72
180
|
}
|
|
73
181
|
catch {
|
|
74
|
-
|
|
182
|
+
changedFiles = [];
|
|
75
183
|
}
|
|
76
|
-
|
|
184
|
+
changedFiles = changedFiles.filter((row) => isWhitelisted(row.path));
|
|
185
|
+
const changedScopes = Array.from(new Set(changedFiles
|
|
186
|
+
.flatMap((row) => [row.scope2, row.scope1])
|
|
187
|
+
.map((row) => String(row || "").trim())
|
|
188
|
+
.filter(Boolean))).slice(0, 24);
|
|
189
|
+
const graphInsights = buildGraphInsightsFromChanges(changedFiles);
|
|
190
|
+
return { lastCommit, branch, changedScopes, graphInsights };
|
|
77
191
|
});
|
|
78
192
|
const shortCommitId = lastCommit.hash.substring(0, 7);
|
|
79
193
|
const snapshotId = `${branch}@${shortCommitId}`;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// 2. Scan
|
|
83
|
-
console.log(chalk.blue('š
|
|
84
|
-
const { skeleton } = await track('
|
|
194
|
+
logDetail(` Branch: ${chalk.white(branch)}`);
|
|
195
|
+
logDetail(` Commit: ${chalk.white(shortCommitId)}`);
|
|
196
|
+
// 2. Scan and extract source structure
|
|
197
|
+
console.log(chalk.blue('š Scanning codebase structure...'));
|
|
198
|
+
const { skeleton } = await track('structure scan', async () => {
|
|
85
199
|
const ig = ignore();
|
|
86
200
|
const monoaiIgnorePath = path.join(process.cwd(), MONOAIIGNORE_FILENAME);
|
|
87
201
|
if (!fs.existsSync(monoaiIgnorePath)) {
|
|
88
202
|
fs.writeFileSync(monoaiIgnorePath, DEFAULT_MONOAIIGNORE, 'utf8');
|
|
89
|
-
|
|
203
|
+
logDetail(` Created ${MONOAIIGNORE_FILENAME} template`);
|
|
90
204
|
}
|
|
91
205
|
if (fs.existsSync('.gitignore')) {
|
|
92
206
|
ig.add(fs.readFileSync('.gitignore').toString());
|
|
@@ -101,30 +215,32 @@ export const pushCommand = new Command('push')
|
|
|
101
215
|
const items = fs.readdirSync(dir);
|
|
102
216
|
for (const item of items) {
|
|
103
217
|
const fullPath = path.join(dir, item);
|
|
104
|
-
const relativePath = path.relative(process.cwd(), fullPath);
|
|
218
|
+
const relativePath = normalizeGitFilePath(path.relative(process.cwd(), fullPath));
|
|
105
219
|
if (ig.ignores(relativePath))
|
|
106
220
|
continue;
|
|
107
221
|
if (fs.statSync(fullPath).isDirectory()) {
|
|
108
222
|
scanDir(fullPath);
|
|
109
223
|
}
|
|
110
224
|
else if (/\.(ts|tsx|js|jsx)$/.test(item)) {
|
|
225
|
+
if (!isWhitelisted(relativePath))
|
|
226
|
+
continue;
|
|
111
227
|
filesToAnalyze.push(fullPath);
|
|
112
228
|
}
|
|
113
229
|
}
|
|
114
230
|
};
|
|
115
231
|
scanDir(process.cwd());
|
|
116
232
|
const skeleton = extractSkeleton(filesToAnalyze);
|
|
117
|
-
console.log(chalk.dim(` Files
|
|
233
|
+
console.log(chalk.dim(` Files scanned: ${filesToAnalyze.length}`));
|
|
118
234
|
return { skeleton };
|
|
119
235
|
});
|
|
120
236
|
const CONVEX_SITE_URL = process.env.MONOAI_CONVEX_SITE_URL ||
|
|
121
237
|
process.env.MONOAI_CONVEX_URL ||
|
|
122
238
|
config.get('convex_url') ||
|
|
123
239
|
'https://majestic-crane-609.convex.site';
|
|
124
|
-
// 3. AST-only upload. Knowledge
|
|
125
|
-
console.log(chalk.blue('š¦ Preparing
|
|
240
|
+
// 3. AST-only upload. Knowledge Graph processing is handled server-side.
|
|
241
|
+
console.log(chalk.blue('š¦ Preparing data...'));
|
|
126
242
|
// 4. Payload Construction
|
|
127
|
-
const payload =
|
|
243
|
+
const payload = {
|
|
128
244
|
name: path.basename(process.cwd()),
|
|
129
245
|
snapshotId,
|
|
130
246
|
branch: branch,
|
|
@@ -132,13 +248,15 @@ export const pushCommand = new Command('push')
|
|
|
132
248
|
commitMessage: lastCommit.message,
|
|
133
249
|
structure: JSON.stringify(skeleton), // Structured AST
|
|
134
250
|
changedScopes,
|
|
251
|
+
graphInsights,
|
|
135
252
|
syncStatus: 'processing',
|
|
136
|
-
}
|
|
253
|
+
};
|
|
137
254
|
// 5. Send to Navigator (Convex)
|
|
138
|
-
console.log(chalk.blue('š”
|
|
255
|
+
console.log(chalk.blue('š” Uploading to MonoAI...'));
|
|
139
256
|
const transmitResult = await track('transmit', async () => {
|
|
140
257
|
const response = await axios.post(`${CONVEX_SITE_URL}/cli/git-commit`, {
|
|
141
|
-
codebaseData: payload
|
|
258
|
+
codebaseData: payload,
|
|
259
|
+
force,
|
|
142
260
|
}, {
|
|
143
261
|
headers: {
|
|
144
262
|
'Authorization': `Bearer ${token}`
|
|
@@ -146,12 +264,24 @@ export const pushCommand = new Command('push')
|
|
|
146
264
|
});
|
|
147
265
|
return response.data;
|
|
148
266
|
});
|
|
267
|
+
if (transmitResult?.deduped) {
|
|
268
|
+
console.log(chalk.yellow('ā This commit was already synced. No new snapshot was created.'));
|
|
269
|
+
if (transmitResult.message && verbose) {
|
|
270
|
+
console.log(chalk.dim(` ${transmitResult.message}`));
|
|
271
|
+
}
|
|
272
|
+
const totalMs = Date.now() - totalStart;
|
|
273
|
+
console.log(chalk.blue(`ā± Total time: ${(totalMs / 1000).toFixed(2)}s`));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (force) {
|
|
277
|
+
logDetail(' Force mode enabled: duplicate commit dedupe was bypassed.');
|
|
278
|
+
}
|
|
149
279
|
if (transmitResult?.graphJobId) {
|
|
150
280
|
const terminalStatuses = new Set(['done', 'error']);
|
|
151
281
|
const waitStart = Date.now();
|
|
152
282
|
const timeoutMs = 180000;
|
|
153
283
|
const pollIntervalMs = 2000;
|
|
154
|
-
console.log(chalk.blue(
|
|
284
|
+
console.log(chalk.blue('š§ Building Knowledge Graph...'));
|
|
155
285
|
let lastStatus = 'uploaded';
|
|
156
286
|
let finalJob = null;
|
|
157
287
|
while (Date.now() - waitStart < timeoutMs) {
|
|
@@ -160,54 +290,59 @@ export const pushCommand = new Command('push')
|
|
|
160
290
|
if (!job)
|
|
161
291
|
break;
|
|
162
292
|
finalJob = job;
|
|
163
|
-
if (job.status !== lastStatus) {
|
|
164
|
-
console.log(chalk.dim(` ā³
|
|
165
|
-
lastStatus = job.status;
|
|
293
|
+
if (job.status !== lastStatus && verbose) {
|
|
294
|
+
console.log(chalk.dim(` ā³ Knowledge Graph status: ${job.status}`));
|
|
166
295
|
}
|
|
296
|
+
lastStatus = job.status;
|
|
167
297
|
if (terminalStatuses.has(job.status)) {
|
|
168
298
|
break;
|
|
169
299
|
}
|
|
170
300
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
171
301
|
}
|
|
172
302
|
const waitMs = Date.now() - waitStart;
|
|
173
|
-
stageTimes.push({ stage: '
|
|
174
|
-
|
|
303
|
+
stageTimes.push({ stage: 'knowledge graph wait', ms: waitMs });
|
|
304
|
+
logDetail(` ā± knowledge graph wait: ${(waitMs / 1000).toFixed(2)}s`);
|
|
175
305
|
if (finalJob) {
|
|
176
306
|
const fmt = (v) => (typeof v === 'number' ? `${(v / 1000).toFixed(2)}s` : 'n/a');
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
307
|
+
if (verbose) {
|
|
308
|
+
console.log(chalk.blue('š Knowledge Graph timing'));
|
|
309
|
+
console.log(chalk.dim(` - queue wait: ${fmt(finalJob.queueWaitMs)}`));
|
|
310
|
+
console.log(chalk.dim(` - graph build: ${fmt(finalJob.cogneeMs)}`));
|
|
311
|
+
console.log(chalk.dim(` - callback: ${fmt(finalJob.callbackMs)}`));
|
|
312
|
+
console.log(chalk.dim(` - service total: ${fmt(finalJob.workerTotalMs)}`));
|
|
313
|
+
console.log(chalk.dim(` - total service time: ${fmt(finalJob.totalPipelineMs)}`));
|
|
314
|
+
}
|
|
183
315
|
if (finalJob.status === 'error' && finalJob.error) {
|
|
184
|
-
console.log(chalk.red(
|
|
316
|
+
console.log(chalk.red(`ā Could not build Knowledge Graph: ${finalJob.error}`));
|
|
317
|
+
}
|
|
318
|
+
if (finalJob.status === 'done') {
|
|
319
|
+
console.log(chalk.green('ā
Knowledge Graph is ready.'));
|
|
185
320
|
}
|
|
186
321
|
}
|
|
187
322
|
else {
|
|
188
|
-
console.log(chalk.yellow('
|
|
323
|
+
console.log(chalk.yellow('ā Knowledge Graph status is unavailable. It may still be processing in the background.'));
|
|
189
324
|
}
|
|
190
325
|
}
|
|
191
|
-
console.log(chalk.green('āØ
|
|
192
|
-
|
|
326
|
+
console.log(chalk.green('⨠Sync complete. Check your dashboard for updated insights.'));
|
|
327
|
+
logDetail(` Message: ${lastCommit.message.split('\n')[0]}`);
|
|
193
328
|
const totalMs = Date.now() - totalStart;
|
|
194
|
-
console.log(chalk.blue(`ā± Total: ${(totalMs / 1000).toFixed(2)}s`));
|
|
329
|
+
console.log(chalk.blue(`ā± Total time: ${(totalMs / 1000).toFixed(2)}s`));
|
|
195
330
|
}
|
|
196
331
|
catch (error) {
|
|
197
332
|
const totalMs = Date.now() - totalStart;
|
|
198
333
|
if (error.response?.status === 401) {
|
|
199
|
-
console.error(chalk.red('ā
|
|
334
|
+
console.error(chalk.red('ā Your sign-in has expired. Please run:'));
|
|
200
335
|
console.error(chalk.white(' npx monoai login'));
|
|
201
336
|
}
|
|
202
337
|
else {
|
|
203
338
|
console.error(chalk.red('ā Sync failed:'), error.message);
|
|
204
339
|
}
|
|
205
|
-
if (stageTimes.length > 0) {
|
|
206
|
-
console.log(chalk.yellow('\nā± Stage timing
|
|
340
|
+
if (verbose && stageTimes.length > 0) {
|
|
341
|
+
console.log(chalk.yellow('\nā± Stage timing details'));
|
|
207
342
|
for (const item of stageTimes) {
|
|
208
343
|
console.log(chalk.dim(` - ${item.stage}: ${(item.ms / 1000).toFixed(2)}s`));
|
|
209
344
|
}
|
|
210
345
|
}
|
|
211
|
-
console.log(chalk.blue(`ā± Total: ${(totalMs / 1000).toFixed(2)}s`));
|
|
346
|
+
console.log(chalk.blue(`ā± Total time: ${(totalMs / 1000).toFixed(2)}s`));
|
|
212
347
|
}
|
|
213
348
|
});
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,29 @@ const SECRET_PATTERNS = [
|
|
|
7
7
|
/AIza[0-9A-Za-z-_]{35}/g, // Google Cloud style
|
|
8
8
|
/ghp_[a-zA-Z0-9]{36}/g // GitHub Personal Access Token
|
|
9
9
|
];
|
|
10
|
+
function toRepoRelativePath(rawPath) {
|
|
11
|
+
const normalized = String(rawPath || "").replace(/\\/g, "/").trim();
|
|
12
|
+
if (!normalized)
|
|
13
|
+
return "";
|
|
14
|
+
const cwd = process.cwd().replace(/\\/g, "/");
|
|
15
|
+
if (normalized.startsWith(cwd + "/")) {
|
|
16
|
+
return normalized.slice(cwd.length + 1);
|
|
17
|
+
}
|
|
18
|
+
if (normalized === cwd) {
|
|
19
|
+
return ".";
|
|
20
|
+
}
|
|
21
|
+
const relative = path.relative(process.cwd(), rawPath).replace(/\\/g, "/");
|
|
22
|
+
if (relative && !relative.startsWith("../") && relative !== "..") {
|
|
23
|
+
return relative.replace(/^\.?\//, "");
|
|
24
|
+
}
|
|
25
|
+
// Fallback for older absolute paths captured under this mono repo.
|
|
26
|
+
const repoMarker = "/Web_AIChat/";
|
|
27
|
+
const markerIdx = normalized.lastIndexOf(repoMarker);
|
|
28
|
+
if (markerIdx >= 0) {
|
|
29
|
+
return normalized.slice(markerIdx + repoMarker.length).replace(/^\.?\//, "");
|
|
30
|
+
}
|
|
31
|
+
return normalized.replace(/^\.?\//, "");
|
|
32
|
+
}
|
|
10
33
|
export function extractSkeleton(filePaths) {
|
|
11
34
|
const project = new Project();
|
|
12
35
|
// š”ļø Security: File Filter
|
|
@@ -46,7 +69,7 @@ export function extractSkeleton(filePaths) {
|
|
|
46
69
|
}
|
|
47
70
|
}
|
|
48
71
|
});
|
|
49
|
-
const filePath = sourceFile.getFilePath();
|
|
72
|
+
const filePath = toRepoRelativePath(sourceFile.getFilePath());
|
|
50
73
|
const skeleton = {
|
|
51
74
|
functions: [],
|
|
52
75
|
classes: [],
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monoai",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"description": "MonoAI CLI for syncing codebase history",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"monoai": "
|
|
8
|
+
"monoai": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"commander": "^11.1.0",
|
|
23
23
|
"conf": "^15.1.0",
|
|
24
24
|
"ignore": "^7.0.5",
|
|
25
|
+
"monoai": "^0.2.7",
|
|
25
26
|
"open": "^9.1.0",
|
|
26
27
|
"ora": "^7.0.1",
|
|
27
28
|
"simple-git": "^3.21.0",
|