aibridge-context 1.5.2 → 2.0.0
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/bin/cli.js +469 -170
- package/core/briefingGenerator.js +368 -0
- package/core/codeDiff.js +304 -0
- package/core/fileSnapshot.js +178 -0
- package/core/stateManager.js +803 -1369
- package/core/watcher.js +78 -49
- package/index.js +23 -9
- package/package.json +7 -3
- package/server/routes.js +30 -34
package/core/watcher.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const path
|
|
3
|
+
const path = require('path');
|
|
4
4
|
const chokidar = require('chokidar');
|
|
5
5
|
|
|
6
|
-
const { syncContextToGit }
|
|
6
|
+
const { syncContextToGit } = require('./gitSync');
|
|
7
|
+
const { captureSnapshot, deleteSnapshot } = require('./fileSnapshot');
|
|
7
8
|
const {
|
|
8
9
|
createDebouncedStateUpdater,
|
|
9
10
|
loadRuntimeConfig,
|
|
@@ -12,82 +13,110 @@ const {
|
|
|
12
13
|
updateProjectState
|
|
13
14
|
} = require('./stateManager');
|
|
14
15
|
|
|
15
|
-
function
|
|
16
|
-
return path.relative(
|
|
16
|
+
function normPath(root, filePath) {
|
|
17
|
+
return path.relative(root, filePath).split(path.sep).join('/');
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
async function startWatcher(projectRoot, options) {
|
|
20
|
-
const settings
|
|
21
|
-
const logger
|
|
22
|
-
const config
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
debounceMs:
|
|
21
|
+
const settings = Object.assign({ logger: null }, options);
|
|
22
|
+
const logger = settings.logger;
|
|
23
|
+
const config = await loadRuntimeConfig(projectRoot);
|
|
24
|
+
const syncCb = async () => syncContextToGit(projectRoot, config.gitSync, logger);
|
|
25
|
+
const debounced = createDebouncedStateUpdater(projectRoot, {
|
|
26
|
+
debounceMs: config.debounceMs,
|
|
26
27
|
logger,
|
|
27
|
-
syncCallback
|
|
28
|
+
syncCallback: syncCb
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
const watcher = chokidar.watch(projectRoot, {
|
|
31
32
|
ignored(filePath) {
|
|
32
|
-
const
|
|
33
|
-
return shouldIgnoreProjectFile(
|
|
33
|
+
const rel = normPath(projectRoot, filePath);
|
|
34
|
+
return shouldIgnoreProjectFile(rel);
|
|
34
35
|
},
|
|
35
36
|
ignoreInitial: true,
|
|
36
|
-
persistent:
|
|
37
|
+
persistent: true,
|
|
38
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 30 }
|
|
37
39
|
});
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
// ── ADD: capture snapshot BEFORE chokidar fires for 'change'
|
|
42
|
+
// We pre-read when the watch starts for existing files via 'ready' + 'add'
|
|
43
|
+
watcher.on('add', (filePath) => {
|
|
44
|
+
const rel = normPath(projectRoot, filePath);
|
|
45
|
+
if (!rel || shouldIgnoreProjectFile(rel) || scoreEvent(rel) < 2) return;
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
47
|
+
// Snapshot what the file looks like when first seen
|
|
48
|
+
captureSnapshot(filePath);
|
|
45
49
|
|
|
46
|
-
if (logger) {
|
|
47
|
-
logger.debug(`Queued meaningful ${action} for ${normalizedPath}`);
|
|
48
|
-
}
|
|
50
|
+
if (logger) logger.debug(`Tracking new file: ${rel}`);
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
timestamp:
|
|
52
|
-
action,
|
|
53
|
-
file:
|
|
52
|
+
debounced.enqueue({
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
action: 'add',
|
|
55
|
+
file: rel,
|
|
56
|
+
oldContent: null // brand new file
|
|
57
|
+
// newContent left undefined → watcher will read it fresh
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
watcher.on('change', (filePath) => {
|
|
62
|
+
const rel = normPath(projectRoot, filePath);
|
|
63
|
+
if (!rel || shouldIgnoreProjectFile(rel) || scoreEvent(rel) < 2) return;
|
|
64
|
+
|
|
65
|
+
// Grab the PREVIOUS content BEFORE it's overwritten on disk
|
|
66
|
+
const oldContent = require('./fileSnapshot').getSnapshot(filePath);
|
|
67
|
+
|
|
68
|
+
// Immediately update the snapshot to the latest saved version
|
|
69
|
+
captureSnapshot(filePath);
|
|
70
|
+
|
|
71
|
+
if (logger) logger.debug(`Change detected: ${rel}`);
|
|
72
|
+
|
|
73
|
+
debounced.enqueue({
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
action: 'change',
|
|
76
|
+
file: rel,
|
|
77
|
+
oldContent // what it looked like before this save
|
|
78
|
+
// newContent left undefined → stateManager reads from disk (just written)
|
|
54
79
|
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
watcher.on('add', (filePath) => handleEvent('add', filePath));
|
|
58
|
-
watcher.on('change', (filePath) => handleEvent('change', filePath));
|
|
59
|
-
watcher.on('unlink', (filePath) => handleEvent('delete', filePath));
|
|
60
|
-
watcher.on('error', (error) => {
|
|
61
|
-
if (logger) {
|
|
62
|
-
logger.error(`Watcher error: ${error.message}`);
|
|
63
|
-
}
|
|
64
80
|
});
|
|
65
81
|
|
|
82
|
+
watcher.on('unlink', (filePath) => {
|
|
83
|
+
const rel = normPath(projectRoot, filePath);
|
|
84
|
+
if (!rel || shouldIgnoreProjectFile(rel)) return;
|
|
85
|
+
|
|
86
|
+
deleteSnapshot(filePath);
|
|
87
|
+
|
|
88
|
+
if (logger) logger.debug(`Deleted: ${rel}`);
|
|
89
|
+
|
|
90
|
+
debounced.enqueue({
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
action: 'delete',
|
|
93
|
+
file: rel,
|
|
94
|
+
oldContent: null,
|
|
95
|
+
newContent: ''
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
watcher.on('error', (err) => {
|
|
100
|
+
if (logger) logger.error(`Watcher error: ${err.message}`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Initial state update on startup
|
|
66
104
|
await updateProjectState(
|
|
67
105
|
projectRoot,
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
action: 'watcher_started',
|
|
71
|
-
file: '.'
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
logger,
|
|
75
|
-
syncCallback
|
|
76
|
-
}
|
|
106
|
+
{ timestamp: new Date().toISOString(), action: 'watcher_started', file: '.' },
|
|
107
|
+
{ logger, syncCallback: syncCb }
|
|
77
108
|
);
|
|
78
109
|
|
|
79
110
|
return {
|
|
80
111
|
watcher,
|
|
81
112
|
async close() {
|
|
82
|
-
await
|
|
113
|
+
await debounced.flushNow();
|
|
83
114
|
await watcher.close();
|
|
84
115
|
},
|
|
85
116
|
async flush() {
|
|
86
|
-
await
|
|
117
|
+
await debounced.flushNow();
|
|
87
118
|
}
|
|
88
119
|
};
|
|
89
120
|
}
|
|
90
121
|
|
|
91
|
-
module.exports = {
|
|
92
|
-
startWatcher
|
|
93
|
-
};
|
|
122
|
+
module.exports = { startWatcher };
|
package/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { initProject }
|
|
4
|
-
const { startWatcher }
|
|
3
|
+
const { initProject } = require('./core/init');
|
|
4
|
+
const { startWatcher } = require('./core/watcher');
|
|
5
|
+
const { generateBriefing } = require('./core/briefingGenerator');
|
|
5
6
|
const {
|
|
6
7
|
bootstrapProjectAnalysis,
|
|
7
8
|
updateProjectState,
|
|
8
9
|
loadRuntimeConfig,
|
|
9
|
-
updateRuntimeConfig
|
|
10
|
+
updateRuntimeConfig,
|
|
11
|
+
createDefaultState,
|
|
12
|
+
getContextPaths
|
|
10
13
|
} = require('./core/stateManager');
|
|
11
|
-
const { startServer }
|
|
14
|
+
const { startServer } = require('./server/server');
|
|
12
15
|
const {
|
|
13
16
|
buildPublicAiUrls,
|
|
14
17
|
ensureGitInitialized,
|
|
@@ -17,14 +20,25 @@ const {
|
|
|
17
20
|
} = require('./core/gitSync');
|
|
18
21
|
|
|
19
22
|
module.exports = {
|
|
20
|
-
|
|
21
|
-
ensureGitInitialized,
|
|
23
|
+
// Core lifecycle
|
|
22
24
|
initProject,
|
|
23
|
-
linkGithubRepository,
|
|
24
25
|
startWatcher,
|
|
26
|
+
startServer,
|
|
27
|
+
|
|
28
|
+
// State management
|
|
25
29
|
updateProjectState,
|
|
26
30
|
loadRuntimeConfig,
|
|
27
31
|
updateRuntimeConfig,
|
|
28
|
-
|
|
32
|
+
createDefaultState,
|
|
33
|
+
getContextPaths,
|
|
34
|
+
bootstrapProjectAnalysis,
|
|
35
|
+
|
|
36
|
+
// Briefing
|
|
37
|
+
generateBriefing,
|
|
38
|
+
|
|
39
|
+
// Git
|
|
40
|
+
buildPublicAiUrls,
|
|
41
|
+
ensureGitInitialized,
|
|
42
|
+
linkGithubRepository,
|
|
29
43
|
syncContextToGit
|
|
30
|
-
};
|
|
44
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aibridge-context",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Zero-config CLI
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Zero-config CLI that auto-generates an AI briefing file for any project. Tracks code changes with real diffs, errors, fixes, file structure, API routes and dependencies. One command: npx aibridge-context start",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"aibridge-context": "./bin/cli.js",
|
|
@@ -29,7 +29,11 @@
|
|
|
29
29
|
"watcher",
|
|
30
30
|
"express",
|
|
31
31
|
"developer-tools",
|
|
32
|
-
"git"
|
|
32
|
+
"git",
|
|
33
|
+
"briefing",
|
|
34
|
+
"diff",
|
|
35
|
+
"changelog",
|
|
36
|
+
"code-tracking"
|
|
33
37
|
],
|
|
34
38
|
"author": "",
|
|
35
39
|
"license": "MIT",
|
package/server/routes.js
CHANGED
|
@@ -1,50 +1,46 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const path
|
|
3
|
+
const path = require('path');
|
|
4
4
|
const express = require('express');
|
|
5
5
|
const { getContextPaths } = require('../core/stateManager');
|
|
6
6
|
|
|
7
7
|
function createRoutes(projectRoot, logger) {
|
|
8
8
|
const router = express.Router();
|
|
9
|
-
const paths
|
|
9
|
+
const paths = getContextPaths(projectRoot);
|
|
10
10
|
|
|
11
11
|
function sendFile(filePath, contentType) {
|
|
12
|
-
return function routeHandler(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!error) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (logger) {
|
|
25
|
-
logger.error(`Failed to serve ${request.path}: ${error.message}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (response.headersSent) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
response.status(error.statusCode || 500).json({
|
|
33
|
-
error: 'Unable to read AI context file.'
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
);
|
|
12
|
+
return function routeHandler(req, res) {
|
|
13
|
+
res.type(contentType);
|
|
14
|
+
res.sendFile(path.resolve(filePath), { dotfiles: 'allow' }, (err) => {
|
|
15
|
+
if (!err) return;
|
|
16
|
+
if (logger) logger.error(`Failed to serve ${req.path}: ${err.message}`);
|
|
17
|
+
if (res.headersSent) return;
|
|
18
|
+
res.status(err.statusCode || 500).json({ error: 'Unable to read AI context file.' });
|
|
19
|
+
});
|
|
37
20
|
};
|
|
38
21
|
}
|
|
39
22
|
|
|
40
|
-
router.get('/state.json',
|
|
41
|
-
router.get('/brain.txt',
|
|
42
|
-
router.get('/context.md',
|
|
43
|
-
router.get('/changelog.json',
|
|
23
|
+
router.get('/state.json', sendFile(paths.stateFile, 'application/json'));
|
|
24
|
+
router.get('/brain.txt', sendFile(paths.brainFile, 'text/plain'));
|
|
25
|
+
router.get('/context.md', sendFile(paths.contextFile, 'text/markdown'));
|
|
26
|
+
router.get('/changelog.json',sendFile(paths.changelogFile, 'application/json'));
|
|
27
|
+
router.get('/briefing.md', sendFile(paths.briefingFile, 'text/markdown'));
|
|
28
|
+
|
|
29
|
+
// Convenience root — lists all available endpoints
|
|
30
|
+
router.get('/', (req, res) => {
|
|
31
|
+
res.json({
|
|
32
|
+
project: require('../core/stateManager').detectProjectMetadata(projectRoot).project,
|
|
33
|
+
endpoints: {
|
|
34
|
+
briefing: '/briefing.md – Full AI briefing (paste into any AI)',
|
|
35
|
+
state: '/state.json – Machine-readable full state',
|
|
36
|
+
changelog: '/changelog.json – Code change history with diffs',
|
|
37
|
+
brain: '/brain.txt – AI instructions',
|
|
38
|
+
context: '/context.md – Human-readable project summary'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
44
42
|
|
|
45
43
|
return router;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
module.exports = {
|
|
49
|
-
createRoutes
|
|
50
|
-
};
|
|
46
|
+
module.exports = { createRoutes };
|