@ai-sdlc/mcp-advisor 0.1.2 → 0.3.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/dist/bin.js +2 -1
- package/dist/resources/index.js +2 -0
- package/dist/resources/updates.d.ts +4 -0
- package/dist/resources/updates.js +40 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +80 -3
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/list-repos.d.ts +13 -0
- package/dist/tools/list-repos.js +23 -0
- package/dist/tools/session-start.d.ts +1 -0
- package/dist/tools/session-start.js +23 -1
- package/dist/types.d.ts +15 -0
- package/dist/version-check.d.ts +34 -0
- package/dist/version-check.js +175 -0
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { createMcpServer } from './server.js';
|
|
4
|
-
const
|
|
4
|
+
const workspacePath = process.env['AI_SDLC_WORKSPACE'] || undefined;
|
|
5
|
+
const { server } = await createMcpServer({ workspacePath });
|
|
5
6
|
await server.connect(new StdioServerTransport());
|
|
6
7
|
//# sourceMappingURL=bin.js.map
|
package/dist/resources/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { registerHotspotsResource } from './hotspots.js';
|
|
|
4
4
|
import { registerMyTasksResource } from './my-tasks.js';
|
|
5
5
|
import { registerBudgetResource } from './budget.js';
|
|
6
6
|
import { registerHistoryResource } from './history.js';
|
|
7
|
+
import { registerUpdatesResource } from './updates.js';
|
|
7
8
|
export function registerAllResources(server, deps) {
|
|
8
9
|
registerCodebaseProfileResource(server, deps);
|
|
9
10
|
registerConventionsResource(server, deps);
|
|
@@ -11,5 +12,6 @@ export function registerAllResources(server, deps) {
|
|
|
11
12
|
registerMyTasksResource(server, deps);
|
|
12
13
|
registerBudgetResource(server, deps);
|
|
13
14
|
registerHistoryResource(server, deps);
|
|
15
|
+
registerUpdatesResource(server, deps);
|
|
14
16
|
}
|
|
15
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { checkForUpdatesCached } from '../version-check.js';
|
|
2
|
+
export function registerUpdatesResource(server, deps) {
|
|
3
|
+
server.resource('updates', 'ai-sdlc://updates', async () => {
|
|
4
|
+
const projectDirs = deps.workspace ? deps.workspace.repos.map((r) => r.path) : [deps.repoPath];
|
|
5
|
+
const result = await checkForUpdatesCached({ projectDirs });
|
|
6
|
+
if (!result.hasUpdates) {
|
|
7
|
+
return {
|
|
8
|
+
contents: [
|
|
9
|
+
{
|
|
10
|
+
uri: 'ai-sdlc://updates',
|
|
11
|
+
mimeType: 'text/plain',
|
|
12
|
+
text: 'All @ai-sdlc packages are up to date.',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const lines = [];
|
|
18
|
+
if (result.serverUpdateAvailable) {
|
|
19
|
+
lines.push(`MCP server: ${result.serverVersion} → ${result.serverLatest} (restart to apply)`);
|
|
20
|
+
}
|
|
21
|
+
for (const u of result.projectUpdates) {
|
|
22
|
+
if (u.updateAvailable) {
|
|
23
|
+
lines.push(` ${u.package}: ${u.current} → ${u.latest} (${u.location})`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (result.autoUpdated.length > 0) {
|
|
27
|
+
lines.push('', `Auto-updated: ${result.autoUpdated.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
contents: [
|
|
31
|
+
{
|
|
32
|
+
uri: 'ai-sdlc://updates',
|
|
33
|
+
mimeType: 'text/plain',
|
|
34
|
+
text: lines.join('\n') || 'All @ai-sdlc packages are up to date.',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=updates.js.map
|
package/dist/server.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface CreateServerOptions {
|
|
|
12
12
|
db?: InstanceType<typeof Database>;
|
|
13
13
|
/** Repository path. Defaults to cwd. */
|
|
14
14
|
repoPath?: string;
|
|
15
|
+
/** Workspace root path for multi-repo mode. Overrides repoPath for workspace detection. */
|
|
16
|
+
workspacePath?: string;
|
|
15
17
|
/** Plugins to register additional tools/resources. */
|
|
16
18
|
plugins?: McpAdvisorPlugin[];
|
|
17
19
|
}
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* MCP server factory — creates and wires the advisor server.
|
|
3
3
|
*/
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
6
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
7
|
import Database from 'better-sqlite3';
|
|
7
8
|
import { StateStore } from '@ai-sdlc/orchestrator/state';
|
|
@@ -9,12 +10,83 @@ import { CostTracker, loadConfig } from '@ai-sdlc/orchestrator';
|
|
|
9
10
|
import { SessionManager } from './session.js';
|
|
10
11
|
import { registerAllTools } from './tools/index.js';
|
|
11
12
|
import { registerAllResources } from './resources/index.js';
|
|
13
|
+
function loadWorkspaceRepos(workspacePath) {
|
|
14
|
+
const yamlPath = join(workspacePath, '.ai-sdlc', 'workspace.yaml');
|
|
15
|
+
if (!existsSync(yamlPath))
|
|
16
|
+
return [];
|
|
17
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
18
|
+
// Simple extraction — repos are listed as "- name: X\n path: Y"
|
|
19
|
+
const repos = [];
|
|
20
|
+
const nameRegex = /- name:\s*(.+)/g;
|
|
21
|
+
const pathRegex = /path:\s*(.+)/g;
|
|
22
|
+
const names = [];
|
|
23
|
+
const paths = [];
|
|
24
|
+
let m;
|
|
25
|
+
while ((m = nameRegex.exec(content)) !== null)
|
|
26
|
+
names.push(m[1].trim());
|
|
27
|
+
while ((m = pathRegex.exec(content)) !== null)
|
|
28
|
+
paths.push(m[1].trim());
|
|
29
|
+
for (let i = 0; i < names.length && i < paths.length; i++) {
|
|
30
|
+
repos.push({ name: names[i], path: paths[i] });
|
|
31
|
+
}
|
|
32
|
+
return repos;
|
|
33
|
+
}
|
|
34
|
+
function buildWorkspaceContext(workspacePath) {
|
|
35
|
+
const repoSpecs = loadWorkspaceRepos(workspacePath);
|
|
36
|
+
if (repoSpecs.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
// Shared workspace-level DB
|
|
39
|
+
const sharedDbPath = join(workspacePath, '.ai-sdlc', 'state.db');
|
|
40
|
+
const sharedDb = new Database(sharedDbPath);
|
|
41
|
+
const sharedStore = StateStore.open(sharedDb);
|
|
42
|
+
const repos = [];
|
|
43
|
+
for (const spec of repoSpecs) {
|
|
44
|
+
const repoAbsPath = join(workspacePath, spec.path.replace(/^\.\//, ''));
|
|
45
|
+
const dbPath = join(repoAbsPath, '.ai-sdlc', 'state.db');
|
|
46
|
+
// Only include repos that have been initialized
|
|
47
|
+
if (!existsSync(join(repoAbsPath, '.ai-sdlc')))
|
|
48
|
+
continue;
|
|
49
|
+
const db = new Database(dbPath);
|
|
50
|
+
const store = StateStore.open(db);
|
|
51
|
+
const costTracker = new CostTracker(store);
|
|
52
|
+
let config;
|
|
53
|
+
try {
|
|
54
|
+
config = loadConfig(join(repoAbsPath, '.ai-sdlc'));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Config not available
|
|
58
|
+
}
|
|
59
|
+
repos.push({
|
|
60
|
+
name: spec.name,
|
|
61
|
+
path: repoAbsPath,
|
|
62
|
+
store,
|
|
63
|
+
costTracker,
|
|
64
|
+
config,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (repos.length === 0)
|
|
68
|
+
return undefined;
|
|
69
|
+
return { workspacePath, repos, sharedStore };
|
|
70
|
+
}
|
|
12
71
|
export async function createMcpServer(opts) {
|
|
13
|
-
const
|
|
72
|
+
const workspacePath = opts?.workspacePath ?? process.env['AI_SDLC_WORKSPACE'];
|
|
73
|
+
// Determine primary repo/store
|
|
74
|
+
let db;
|
|
75
|
+
let repoPath;
|
|
76
|
+
if (workspacePath) {
|
|
77
|
+
// Workspace mode — primary store is workspace-level
|
|
78
|
+
repoPath = workspacePath;
|
|
79
|
+
db =
|
|
80
|
+
opts?.db ??
|
|
81
|
+
new Database(opts?.dbPath ?? process.env['AI_SDLC_DB'] ?? join(workspacePath, '.ai-sdlc', 'state.db'));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
repoPath = opts?.repoPath ?? process.cwd();
|
|
85
|
+
db = opts?.db ?? new Database(opts?.dbPath ?? process.env['AI_SDLC_DB'] ?? '.ai-sdlc/state.db');
|
|
86
|
+
}
|
|
14
87
|
const store = StateStore.open(db);
|
|
15
88
|
const costTracker = new CostTracker(store);
|
|
16
89
|
const sessions = new SessionManager();
|
|
17
|
-
const repoPath = opts?.repoPath ?? process.cwd();
|
|
18
90
|
// Load AI-SDLC config (best-effort — tools work in degraded mode without it)
|
|
19
91
|
let config;
|
|
20
92
|
try {
|
|
@@ -23,7 +95,12 @@ export async function createMcpServer(opts) {
|
|
|
23
95
|
catch {
|
|
24
96
|
// Config not available or invalid
|
|
25
97
|
}
|
|
26
|
-
|
|
98
|
+
// Build workspace context if in multi-repo mode
|
|
99
|
+
let workspace;
|
|
100
|
+
if (workspacePath) {
|
|
101
|
+
workspace = buildWorkspaceContext(workspacePath);
|
|
102
|
+
}
|
|
103
|
+
const deps = { store, costTracker, sessions, repoPath, config, workspace };
|
|
27
104
|
const server = new McpServer({
|
|
28
105
|
name: 'ai-sdlc-advisor',
|
|
29
106
|
version: '0.1.0',
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export { handleCheckTask } from './check-task.js';
|
|
|
7
7
|
export { handleTrackUsage } from './track-usage.js';
|
|
8
8
|
export { handleCheckFile } from './check-file.js';
|
|
9
9
|
export { handleSessionEnd } from './session-end.js';
|
|
10
|
+
export { handleListRepos } from './list-repos.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/tools/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { registerCheckTask } from './check-task.js';
|
|
|
4
4
|
import { registerTrackUsage } from './track-usage.js';
|
|
5
5
|
import { registerCheckFile } from './check-file.js';
|
|
6
6
|
import { registerSessionEnd } from './session-end.js';
|
|
7
|
+
import { registerListRepos } from './list-repos.js';
|
|
7
8
|
export function registerAllTools(server, deps) {
|
|
8
9
|
registerSessionStart(server, deps);
|
|
9
10
|
registerGetContext(server, deps);
|
|
@@ -11,6 +12,7 @@ export function registerAllTools(server, deps) {
|
|
|
11
12
|
registerTrackUsage(server, deps);
|
|
12
13
|
registerCheckFile(server, deps);
|
|
13
14
|
registerSessionEnd(server, deps);
|
|
15
|
+
registerListRepos(server, deps);
|
|
14
16
|
}
|
|
15
17
|
export { handleSessionStart } from './session-start.js';
|
|
16
18
|
export { handleGetContext } from './get-context.js';
|
|
@@ -18,4 +20,5 @@ export { handleCheckTask } from './check-task.js';
|
|
|
18
20
|
export { handleTrackUsage } from './track-usage.js';
|
|
19
21
|
export { handleCheckFile } from './check-file.js';
|
|
20
22
|
export { handleSessionEnd } from './session-end.js';
|
|
23
|
+
export { handleListRepos } from './list-repos.js';
|
|
21
24
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ServerDeps } from '../types.js';
|
|
3
|
+
export interface ListReposResult {
|
|
4
|
+
isWorkspace: boolean;
|
|
5
|
+
repos: Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
hasConfig: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export declare function handleListRepos(deps: ServerDeps): ListReposResult;
|
|
12
|
+
export declare function registerListRepos(server: McpServer, deps: ServerDeps): void;
|
|
13
|
+
//# sourceMappingURL=list-repos.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function handleListRepos(deps) {
|
|
2
|
+
if (!deps.workspace) {
|
|
3
|
+
return {
|
|
4
|
+
isWorkspace: false,
|
|
5
|
+
repos: [{ name: 'current', path: deps.repoPath, hasConfig: !!deps.config }],
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
isWorkspace: true,
|
|
10
|
+
repos: deps.workspace.repos.map((r) => ({
|
|
11
|
+
name: r.name,
|
|
12
|
+
path: r.path,
|
|
13
|
+
hasConfig: !!r.config,
|
|
14
|
+
})),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function registerListRepos(server, deps) {
|
|
18
|
+
server.tool('list_repos', 'List repositories in the workspace. In single-repo mode, returns the current repo.', {}, async () => {
|
|
19
|
+
const result = handleListRepos(deps);
|
|
20
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=list-repos.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { resolveIssue } from '../issue-linker.js';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { checkForUpdatesCached } from '../version-check.js';
|
|
4
5
|
export async function handleSessionStart(deps, input) {
|
|
5
6
|
const session = deps.sessions.create({
|
|
6
7
|
developer: input.developer,
|
|
@@ -22,12 +23,33 @@ export async function handleSessionStart(deps, input) {
|
|
|
22
23
|
linkMethod: resolution.method,
|
|
23
24
|
}),
|
|
24
25
|
});
|
|
25
|
-
|
|
26
|
+
// Check for package updates and auto-update project deps (non-blocking, cached)
|
|
27
|
+
let updateNotice;
|
|
28
|
+
try {
|
|
29
|
+
const projectDirs = deps.workspace ? deps.workspace.repos.map((r) => r.path) : [deps.repoPath];
|
|
30
|
+
const versionCheck = await checkForUpdatesCached({ projectDirs });
|
|
31
|
+
const notices = [];
|
|
32
|
+
if (versionCheck.autoUpdated.length > 0) {
|
|
33
|
+
notices.push(`Auto-updated: ${versionCheck.autoUpdated.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
if (versionCheck.serverUpdateAvailable) {
|
|
36
|
+
notices.push(`MCP server update: ${versionCheck.serverVersion} → ${versionCheck.serverLatest}. Restart to apply.`);
|
|
37
|
+
}
|
|
38
|
+
if (notices.length > 0)
|
|
39
|
+
updateNotice = notices.join('. ');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Version check is best-effort
|
|
43
|
+
}
|
|
44
|
+
const result = {
|
|
26
45
|
sessionId: session.sessionId,
|
|
27
46
|
linkedIssue: resolution.issueNumber,
|
|
28
47
|
linkMethod: resolution.method,
|
|
29
48
|
project: session.project,
|
|
30
49
|
};
|
|
50
|
+
if (updateNotice)
|
|
51
|
+
result.updateNotice = updateNotice;
|
|
52
|
+
return result;
|
|
31
53
|
}
|
|
32
54
|
export function registerSessionStart(server, deps) {
|
|
33
55
|
server.tool('session_start', 'Start a governed AI session. Returns session ID, linked issue, and project context.', {
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
import type { StateStore } from '@ai-sdlc/orchestrator/state';
|
|
5
5
|
import type { CostTracker, AiSdlcConfig } from '@ai-sdlc/orchestrator';
|
|
6
6
|
import type { SessionManager } from './session.js';
|
|
7
|
+
export interface RepoContext {
|
|
8
|
+
name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
store: StateStore;
|
|
11
|
+
costTracker: CostTracker;
|
|
12
|
+
config?: AiSdlcConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkspaceContext {
|
|
15
|
+
workspacePath: string;
|
|
16
|
+
repos: RepoContext[];
|
|
17
|
+
/** Shared workspace-level store for cross-repo knowledge. */
|
|
18
|
+
sharedStore: StateStore;
|
|
19
|
+
}
|
|
7
20
|
export interface ServerDeps {
|
|
8
21
|
store: StateStore;
|
|
9
22
|
costTracker: CostTracker;
|
|
@@ -11,5 +24,7 @@ export interface ServerDeps {
|
|
|
11
24
|
repoPath: string;
|
|
12
25
|
/** Loaded AI-SDLC config (if available). */
|
|
13
26
|
config?: AiSdlcConfig;
|
|
27
|
+
/** Workspace context when running in multi-repo mode. */
|
|
28
|
+
workspace?: WorkspaceContext;
|
|
14
29
|
}
|
|
15
30
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version check and auto-update — keep @ai-sdlc packages up to date.
|
|
3
|
+
*
|
|
4
|
+
* Strategy:
|
|
5
|
+
* - The MCP server itself runs via `npx -y` so it re-fetches on cold start.
|
|
6
|
+
* - Project-level @ai-sdlc/* dependencies are detected by scanning package.json
|
|
7
|
+
* files in workspace repos and auto-updated via the user's package manager.
|
|
8
|
+
* - Checks run at most once per 6 hours (cached).
|
|
9
|
+
*/
|
|
10
|
+
export interface VersionInfo {
|
|
11
|
+
package: string;
|
|
12
|
+
current: string;
|
|
13
|
+
latest: string | null;
|
|
14
|
+
updateAvailable: boolean;
|
|
15
|
+
location?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface VersionCheckResult {
|
|
18
|
+
serverVersion: string;
|
|
19
|
+
serverLatest: string | null;
|
|
20
|
+
serverUpdateAvailable: boolean;
|
|
21
|
+
projectUpdates: VersionInfo[];
|
|
22
|
+
hasUpdates: boolean;
|
|
23
|
+
autoUpdated: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface CheckForUpdatesOptions {
|
|
26
|
+
/** Directories to scan for @ai-sdlc/* dependencies. */
|
|
27
|
+
projectDirs?: string[];
|
|
28
|
+
/** Whether to auto-update outdated project dependencies. Default: true. */
|
|
29
|
+
autoUpdate?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function checkForUpdates(opts?: CheckForUpdatesOptions): Promise<VersionCheckResult>;
|
|
32
|
+
/** Cached version check — runs at most once per 6 hours. */
|
|
33
|
+
export declare function checkForUpdatesCached(opts?: CheckForUpdatesOptions): Promise<VersionCheckResult>;
|
|
34
|
+
//# sourceMappingURL=version-check.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version check and auto-update — keep @ai-sdlc packages up to date.
|
|
3
|
+
*
|
|
4
|
+
* Strategy:
|
|
5
|
+
* - The MCP server itself runs via `npx -y` so it re-fetches on cold start.
|
|
6
|
+
* - Project-level @ai-sdlc/* dependencies are detected by scanning package.json
|
|
7
|
+
* files in workspace repos and auto-updated via the user's package manager.
|
|
8
|
+
* - Checks run at most once per 6 hours (cached).
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
function getServerVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const pkg = JSON.parse(readFileSync(join(thisDir, '..', 'package.json'), 'utf-8'));
|
|
18
|
+
return pkg.version ?? '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return '0.0.0';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function fetchLatestVersion(packageName) {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
27
|
+
headers: { Accept: 'application/json' },
|
|
28
|
+
signal: AbortSignal.timeout(5000),
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok)
|
|
31
|
+
return null;
|
|
32
|
+
const data = (await res.json());
|
|
33
|
+
return data.version ?? null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function isNewer(latest, current) {
|
|
40
|
+
const parse = (v) => v
|
|
41
|
+
.replace(/^[~^>=<]*/, '')
|
|
42
|
+
.split('.')
|
|
43
|
+
.map(Number);
|
|
44
|
+
const l = parse(latest);
|
|
45
|
+
const c = parse(current);
|
|
46
|
+
for (let i = 0; i < 3; i++) {
|
|
47
|
+
if ((l[i] ?? 0) > (c[i] ?? 0))
|
|
48
|
+
return true;
|
|
49
|
+
if ((l[i] ?? 0) < (c[i] ?? 0))
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/** Scan a directory's package.json for @ai-sdlc/* dependencies. */
|
|
55
|
+
function scanProjectDeps(projectDir) {
|
|
56
|
+
const pkgPath = join(projectDir, 'package.json');
|
|
57
|
+
if (!existsSync(pkgPath))
|
|
58
|
+
return [];
|
|
59
|
+
try {
|
|
60
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
61
|
+
const deps = [];
|
|
62
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
63
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
64
|
+
if (!name.startsWith('@ai-sdlc/'))
|
|
65
|
+
continue;
|
|
66
|
+
const v = String(version);
|
|
67
|
+
// Skip workspace/file/link references
|
|
68
|
+
if (v.startsWith('workspace:') || v.startsWith('file:') || v.startsWith('link:'))
|
|
69
|
+
continue;
|
|
70
|
+
deps.push({ packageName: name, currentVersion: v, location: projectDir });
|
|
71
|
+
}
|
|
72
|
+
return deps;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function detectPackageManager(projectDir) {
|
|
79
|
+
if (existsSync(join(projectDir, 'pnpm-lock.yaml')))
|
|
80
|
+
return 'pnpm';
|
|
81
|
+
if (existsSync(join(projectDir, 'yarn.lock')))
|
|
82
|
+
return 'yarn';
|
|
83
|
+
return 'npm';
|
|
84
|
+
}
|
|
85
|
+
function runUpdate(projectDir, packages) {
|
|
86
|
+
if (packages.length === 0)
|
|
87
|
+
return false;
|
|
88
|
+
const pm = detectPackageManager(projectDir);
|
|
89
|
+
const pkgList = packages.join(' ');
|
|
90
|
+
try {
|
|
91
|
+
const cmd = pm === 'pnpm'
|
|
92
|
+
? `pnpm update ${pkgList}`
|
|
93
|
+
: pm === 'yarn'
|
|
94
|
+
? `yarn upgrade ${pkgList}`
|
|
95
|
+
: `npm update ${pkgList}`;
|
|
96
|
+
execSync(cmd, { cwd: projectDir, stdio: 'ignore', timeout: 30000 });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function checkForUpdates(opts) {
|
|
104
|
+
const autoUpdate = opts?.autoUpdate ?? true;
|
|
105
|
+
const projectDirs = opts?.projectDirs ?? [];
|
|
106
|
+
// 1. Check if the MCP server itself has a newer version
|
|
107
|
+
const serverVersion = getServerVersion();
|
|
108
|
+
const serverLatest = await fetchLatestVersion('@ai-sdlc/mcp-advisor');
|
|
109
|
+
const serverUpdateAvailable = serverLatest ? isNewer(serverLatest, serverVersion) : false;
|
|
110
|
+
// 2. Scan project dependencies
|
|
111
|
+
const allDeps = [];
|
|
112
|
+
for (const dir of projectDirs) {
|
|
113
|
+
allDeps.push(...scanProjectDeps(dir));
|
|
114
|
+
}
|
|
115
|
+
// Deduplicate by package name (keep first occurrence)
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const uniqueDeps = allDeps.filter((d) => {
|
|
118
|
+
const key = `${d.packageName}@${d.location}`;
|
|
119
|
+
if (seen.has(key))
|
|
120
|
+
return false;
|
|
121
|
+
seen.add(key);
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
// 3. Check each dependency against npm registry
|
|
125
|
+
const projectUpdates = await Promise.all(uniqueDeps.map(async (dep) => {
|
|
126
|
+
const latest = await fetchLatestVersion(dep.packageName);
|
|
127
|
+
return {
|
|
128
|
+
package: dep.packageName,
|
|
129
|
+
current: dep.currentVersion,
|
|
130
|
+
latest,
|
|
131
|
+
updateAvailable: latest ? isNewer(latest, dep.currentVersion) : false,
|
|
132
|
+
location: dep.location,
|
|
133
|
+
};
|
|
134
|
+
}));
|
|
135
|
+
// 4. Auto-update outdated project dependencies
|
|
136
|
+
const autoUpdated = [];
|
|
137
|
+
if (autoUpdate) {
|
|
138
|
+
// Group outdated deps by project directory
|
|
139
|
+
const byDir = new Map();
|
|
140
|
+
for (const u of projectUpdates) {
|
|
141
|
+
if (!u.updateAvailable || !u.location)
|
|
142
|
+
continue;
|
|
143
|
+
const list = byDir.get(u.location) ?? [];
|
|
144
|
+
list.push(`${u.package}@latest`);
|
|
145
|
+
byDir.set(u.location, list);
|
|
146
|
+
}
|
|
147
|
+
for (const [dir, packages] of byDir) {
|
|
148
|
+
if (runUpdate(dir, packages)) {
|
|
149
|
+
autoUpdated.push(...packages.map((p) => p.replace(/@latest$/, '')));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const hasUpdates = serverUpdateAvailable || projectUpdates.some((u) => u.updateAvailable);
|
|
154
|
+
return {
|
|
155
|
+
serverVersion,
|
|
156
|
+
serverLatest,
|
|
157
|
+
serverUpdateAvailable,
|
|
158
|
+
projectUpdates,
|
|
159
|
+
hasUpdates,
|
|
160
|
+
autoUpdated,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
let cachedResult = null;
|
|
164
|
+
let cacheTime = 0;
|
|
165
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
166
|
+
/** Cached version check — runs at most once per 6 hours. */
|
|
167
|
+
export async function checkForUpdatesCached(opts) {
|
|
168
|
+
if (cachedResult && Date.now() - cacheTime < CACHE_TTL_MS) {
|
|
169
|
+
return cachedResult;
|
|
170
|
+
}
|
|
171
|
+
cachedResult = await checkForUpdates(opts);
|
|
172
|
+
cacheTime = Date.now();
|
|
173
|
+
return cachedResult;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=version-check.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdlc/mcp-advisor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server bridging human-directed AI sessions with the AI-SDLC orchestrator",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
34
34
|
"better-sqlite3": "^11.0.0",
|
|
35
35
|
"zod": "^3.23.0",
|
|
36
|
-
"@ai-sdlc/orchestrator": "0.
|
|
36
|
+
"@ai-sdlc/orchestrator": "0.3.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/better-sqlite3": "^7.6.0",
|