projscan 3.5.0 → 3.7.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/README.md +27 -21
- package/dist/cli/commands/claim.d.ts +5 -0
- package/dist/cli/commands/claim.js +139 -0
- package/dist/cli/commands/claim.js.map +1 -0
- package/dist/cli/commands/collision.d.ts +5 -0
- package/dist/cli/commands/collision.js +62 -0
- package/dist/cli/commands/collision.js.map +1 -0
- package/dist/cli/commands/coordinate.d.ts +7 -0
- package/dist/cli/commands/coordinate.js +98 -0
- package/dist/cli/commands/coordinate.js.map +1 -0
- package/dist/cli/commands/mergeRisk.d.ts +5 -0
- package/dist/cli/commands/mergeRisk.js +58 -0
- package/dist/cli/commands/mergeRisk.js.map +1 -0
- package/dist/cli/commands/route.d.ts +5 -0
- package/dist/cli/commands/route.js +53 -0
- package/dist/cli/commands/route.js.map +1 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agentBrief.js +32 -2
- package/dist/core/agentBrief.js.map +1 -1
- package/dist/core/claims.d.ts +60 -0
- package/dist/core/claims.js +139 -0
- package/dist/core/claims.js.map +1 -0
- package/dist/core/collisionDetector.d.ts +65 -0
- package/dist/core/collisionDetector.js +222 -0
- package/dist/core/collisionDetector.js.map +1 -0
- package/dist/core/coordination.d.ts +62 -0
- package/dist/core/coordination.js +121 -0
- package/dist/core/coordination.js.map +1 -0
- package/dist/core/embeddings.js +30 -17
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/intentRouter.d.ts +40 -0
- package/dist/core/intentRouter.js +213 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/mergeRisk.d.ts +42 -0
- package/dist/core/mergeRisk.js +71 -0
- package/dist/core/mergeRisk.js.map +1 -0
- package/dist/core/preflight.js +50 -0
- package/dist/core/preflight.js.map +1 -1
- package/dist/core/roadmapCatalog.js +50 -50
- package/dist/core/roadmapCatalog.js.map +1 -1
- package/dist/mcp/tools/claim.d.ts +7 -0
- package/dist/mcp/tools/claim.js +69 -0
- package/dist/mcp/tools/claim.js.map +1 -0
- package/dist/mcp/tools/collision.d.ts +7 -0
- package/dist/mcp/tools/collision.js +38 -0
- package/dist/mcp/tools/collision.js.map +1 -0
- package/dist/mcp/tools/coordinate.d.ts +7 -0
- package/dist/mcp/tools/coordinate.js +24 -0
- package/dist/mcp/tools/coordinate.js.map +1 -0
- package/dist/mcp/tools/coordinateWatch.d.ts +4 -0
- package/dist/mcp/tools/coordinateWatch.js +138 -0
- package/dist/mcp/tools/coordinateWatch.js.map +1 -0
- package/dist/mcp/tools/mergeRisk.d.ts +7 -0
- package/dist/mcp/tools/mergeRisk.js +24 -0
- package/dist/mcp/tools/mergeRisk.js.map +1 -0
- package/dist/mcp/tools/route.d.ts +7 -0
- package/dist/mcp/tools/route.js +24 -0
- package/dist/mcp/tools/route.js.map +1 -0
- package/dist/mcp/tools.js +12 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/projscan-sbom.cdx.json +6 -6
- package/dist/tool-manifest.json +132 -3
- package/dist/types.d.ts +12 -2
- package/dist/utils/formatSupport.d.ts +9 -0
- package/dist/utils/formatSupport.js +9 -0
- package/dist/utils/formatSupport.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { scanRepository } from './repositoryScanner.js';
|
|
4
|
+
import { buildCodeGraph, importersOf } from './codeGraph.js';
|
|
5
|
+
import { computeImpact } from './impact.js';
|
|
6
|
+
import { getChangedFiles } from '../utils/changedFiles.js';
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/** Parse `git worktree list --porcelain` into structured refs. Local-first. */
|
|
9
|
+
export async function listWorktrees(rootPath) {
|
|
10
|
+
let stdout;
|
|
11
|
+
try {
|
|
12
|
+
({ stdout } = await execFileAsync('git', ['worktree', 'list', '--porcelain'], {
|
|
13
|
+
cwd: rootPath,
|
|
14
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const refs = [];
|
|
21
|
+
let current = null;
|
|
22
|
+
const flush = () => {
|
|
23
|
+
if (current)
|
|
24
|
+
refs.push(current);
|
|
25
|
+
current = null;
|
|
26
|
+
};
|
|
27
|
+
for (const line of stdout.split('\n')) {
|
|
28
|
+
if (line.startsWith('worktree ')) {
|
|
29
|
+
flush();
|
|
30
|
+
current = { path: line.slice('worktree '.length).trim(), branch: null, head: null };
|
|
31
|
+
}
|
|
32
|
+
else if (current && line.startsWith('HEAD ')) {
|
|
33
|
+
current.head = line.slice('HEAD '.length).trim();
|
|
34
|
+
}
|
|
35
|
+
else if (current && line.startsWith('branch ')) {
|
|
36
|
+
current.branch = line.slice('branch '.length).trim().replace(/^refs\/heads\//, '');
|
|
37
|
+
}
|
|
38
|
+
else if (current && line.trim() === 'detached') {
|
|
39
|
+
current.branch = null;
|
|
40
|
+
}
|
|
41
|
+
else if (line.trim() === '') {
|
|
42
|
+
flush();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
flush();
|
|
46
|
+
return refs;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Detect collisions across the repo's in-flight worktrees. Needs at least two
|
|
50
|
+
* worktrees (otherwise there's nothing to coordinate). The dependency edges
|
|
51
|
+
* come from the current repo's import graph (HEAD); a file added only inside a
|
|
52
|
+
* worktree simply has no edges yet and is still caught by same-file overlap.
|
|
53
|
+
*/
|
|
54
|
+
export async function detectCollisions(rootPath, options = {}) {
|
|
55
|
+
const worktrees = await listWorktrees(rootPath);
|
|
56
|
+
if (worktrees.length < 2) {
|
|
57
|
+
return {
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
available: false,
|
|
60
|
+
reason: worktrees.length === 0
|
|
61
|
+
? 'not a git repository, or git worktrees are unavailable'
|
|
62
|
+
: 'only one worktree — collision detection needs at least two in-flight worktrees',
|
|
63
|
+
worktrees: worktrees.map((w) => ({ path: w.path, branch: w.branch, changedFileCount: 0, baseRef: null })),
|
|
64
|
+
collisions: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Changed files per worktree (repo-relative, POSIX).
|
|
68
|
+
const changes = await Promise.all(worktrees.map(async (w) => {
|
|
69
|
+
const result = await getChangedFiles(w.path, options.baseRef);
|
|
70
|
+
return {
|
|
71
|
+
ref: w,
|
|
72
|
+
files: result.available ? result.files : [],
|
|
73
|
+
baseRef: result.baseRef,
|
|
74
|
+
};
|
|
75
|
+
}));
|
|
76
|
+
// Import graph of the current repo for dependency (blast-radius) edges.
|
|
77
|
+
const scan = await scanRepository(rootPath);
|
|
78
|
+
const graph = await buildCodeGraph(rootPath, scan.files);
|
|
79
|
+
const collisions = [];
|
|
80
|
+
for (let i = 0; i < changes.length; i++) {
|
|
81
|
+
for (let j = i + 1; j < changes.length; j++) {
|
|
82
|
+
const a = changes[i];
|
|
83
|
+
const b = changes[j];
|
|
84
|
+
const aFiles = a.files;
|
|
85
|
+
const bSet = new Set(b.files);
|
|
86
|
+
const aSet = new Set(a.files);
|
|
87
|
+
// Same-file overlap — both worktrees changed the same file.
|
|
88
|
+
for (const file of aFiles) {
|
|
89
|
+
if (bSet.has(file)) {
|
|
90
|
+
collisions.push({
|
|
91
|
+
kind: 'same-file',
|
|
92
|
+
severity: 'high',
|
|
93
|
+
worktreeA: a.ref.path,
|
|
94
|
+
fileA: file,
|
|
95
|
+
worktreeB: b.ref.path,
|
|
96
|
+
fileB: file,
|
|
97
|
+
reason: `Both worktrees changed ${file}.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Dependency overlap — one worktree changed a file the other's change
|
|
102
|
+
// imports (1-hop import edge). Skip files already flagged same-file.
|
|
103
|
+
for (const file of aFiles) {
|
|
104
|
+
if (bSet.has(file))
|
|
105
|
+
continue;
|
|
106
|
+
const importers = new Set(importersOf(graph, file));
|
|
107
|
+
for (const other of b.files) {
|
|
108
|
+
if (other === file || aSet.has(other))
|
|
109
|
+
continue;
|
|
110
|
+
if (importers.has(other)) {
|
|
111
|
+
collisions.push({
|
|
112
|
+
kind: 'dependency',
|
|
113
|
+
severity: 'medium',
|
|
114
|
+
worktreeA: a.ref.path,
|
|
115
|
+
fileA: file,
|
|
116
|
+
worktreeB: b.ref.path,
|
|
117
|
+
fileB: other,
|
|
118
|
+
distance: 1,
|
|
119
|
+
reason: `${other} (changed in the other worktree) imports ${file} (changed here).`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const file of b.files) {
|
|
125
|
+
if (aSet.has(file))
|
|
126
|
+
continue;
|
|
127
|
+
const importers = new Set(importersOf(graph, file));
|
|
128
|
+
for (const other of aFiles) {
|
|
129
|
+
if (other === file || bSet.has(other))
|
|
130
|
+
continue;
|
|
131
|
+
if (importers.has(other)) {
|
|
132
|
+
collisions.push({
|
|
133
|
+
kind: 'dependency',
|
|
134
|
+
severity: 'medium',
|
|
135
|
+
worktreeA: a.ref.path,
|
|
136
|
+
fileA: other,
|
|
137
|
+
worktreeB: b.ref.path,
|
|
138
|
+
fileB: file,
|
|
139
|
+
distance: 1,
|
|
140
|
+
reason: `${other} (changed here) imports ${file} (changed in the other worktree).`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Transitive overlap (opt-in) — multi-hop dependency edges via the impact
|
|
146
|
+
// graph. Only distance >= 2 here; distance 1 is the precise pass above.
|
|
147
|
+
if (options.transitive) {
|
|
148
|
+
const maxDistance = options.maxDistance ?? 5;
|
|
149
|
+
for (const file of aFiles) {
|
|
150
|
+
if (bSet.has(file))
|
|
151
|
+
continue;
|
|
152
|
+
for (const node of computeImpact(graph, { kind: 'file', value: file }, { maxDistance }).reachable) {
|
|
153
|
+
if (node.distance >= 2 && bSet.has(node.file) && !aSet.has(node.file)) {
|
|
154
|
+
collisions.push({
|
|
155
|
+
kind: 'dependency',
|
|
156
|
+
severity: 'medium',
|
|
157
|
+
worktreeA: a.ref.path,
|
|
158
|
+
fileA: file,
|
|
159
|
+
worktreeB: b.ref.path,
|
|
160
|
+
fileB: node.file,
|
|
161
|
+
distance: node.distance,
|
|
162
|
+
reason: `${node.file} (changed in the other worktree) transitively imports ${file} (changed here), ${node.distance} hops away.`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const file of b.files) {
|
|
168
|
+
if (aSet.has(file))
|
|
169
|
+
continue;
|
|
170
|
+
for (const node of computeImpact(graph, { kind: 'file', value: file }, { maxDistance }).reachable) {
|
|
171
|
+
if (node.distance >= 2 && aSet.has(node.file) && !bSet.has(node.file)) {
|
|
172
|
+
collisions.push({
|
|
173
|
+
kind: 'dependency',
|
|
174
|
+
severity: 'medium',
|
|
175
|
+
worktreeA: a.ref.path,
|
|
176
|
+
fileA: node.file,
|
|
177
|
+
worktreeB: b.ref.path,
|
|
178
|
+
fileB: file,
|
|
179
|
+
distance: node.distance,
|
|
180
|
+
reason: `${node.file} (changed here) transitively imports ${file} (changed in the other worktree), ${node.distance} hops away.`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
schemaVersion: 1,
|
|
190
|
+
available: true,
|
|
191
|
+
worktrees: changes.map((c) => ({
|
|
192
|
+
path: c.ref.path,
|
|
193
|
+
branch: c.ref.branch,
|
|
194
|
+
changedFileCount: c.files.length,
|
|
195
|
+
baseRef: c.baseRef,
|
|
196
|
+
})),
|
|
197
|
+
collisions: dedupeCollisions(collisions),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Same-file collisions pass through; dependency collisions are deduped per
|
|
202
|
+
* oriented (worktreeA, fileA, worktreeB, fileB) pair, keeping the shortest
|
|
203
|
+
* distance — so a transitive path can't re-report a pair the direct (distance
|
|
204
|
+
* 1) pass already found.
|
|
205
|
+
*/
|
|
206
|
+
function dedupeCollisions(list) {
|
|
207
|
+
const sameFile = [];
|
|
208
|
+
const bestDependency = new Map();
|
|
209
|
+
for (const c of list) {
|
|
210
|
+
if (c.kind === 'same-file') {
|
|
211
|
+
sameFile.push(c);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const key = `${c.worktreeA}|${c.fileA}|${c.worktreeB}|${c.fileB}`;
|
|
215
|
+
const existing = bestDependency.get(key);
|
|
216
|
+
if (!existing || (c.distance ?? Infinity) < (existing.distance ?? Infinity)) {
|
|
217
|
+
bestDependency.set(key, c);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return [...sameFile, ...bestDependency.values()];
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=collisionDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collisionDetector.js","sourceRoot":"","sources":["../../src/core/collisionDetector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type CollisionReport } from './collisionDetector.js';
|
|
2
|
+
import { type Claim } from './claims.js';
|
|
3
|
+
import { type MergeRiskReport } from './mergeRisk.js';
|
|
4
|
+
/**
|
|
5
|
+
* Coordination summary (4.x arc, epic 5 — the capstone).
|
|
6
|
+
*
|
|
7
|
+
* Composes the three swarm primitives — collisions, claims, and merge-risk —
|
|
8
|
+
* into one budget-shaped read with a `readiness` verdict, so an agent gets a
|
|
9
|
+
* single coordination signal instead of three calls. It is also the arc's
|
|
10
|
+
* measurable outcome surface: the counts (collisions caught, contended claims,
|
|
11
|
+
* merge hotspots) quantify what the coordination layer surfaced. Local-first.
|
|
12
|
+
*/
|
|
13
|
+
export type CoordinationReadiness = 'clear' | 'caution' | 'conflicted';
|
|
14
|
+
export interface CoordinationSummary {
|
|
15
|
+
schemaVersion: 1;
|
|
16
|
+
available: boolean;
|
|
17
|
+
reason?: string;
|
|
18
|
+
worktreeCount: number;
|
|
19
|
+
collisions: {
|
|
20
|
+
total: number;
|
|
21
|
+
high: number;
|
|
22
|
+
medium: number;
|
|
23
|
+
};
|
|
24
|
+
claims: {
|
|
25
|
+
total: number;
|
|
26
|
+
contendedTargets: number;
|
|
27
|
+
};
|
|
28
|
+
mergeRisk: {
|
|
29
|
+
hotspotCount: number;
|
|
30
|
+
integrationOrder: Array<{
|
|
31
|
+
worktree: string;
|
|
32
|
+
branch: string | null;
|
|
33
|
+
riskScore: number;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
readiness: CoordinationReadiness;
|
|
37
|
+
summary: string[];
|
|
38
|
+
}
|
|
39
|
+
export interface CoordinationInputs {
|
|
40
|
+
collisionReport: CollisionReport;
|
|
41
|
+
claims: Claim[];
|
|
42
|
+
mergeRisk: MergeRiskReport;
|
|
43
|
+
}
|
|
44
|
+
/** Pure: fold the three swarm signals into a summary + readiness verdict. */
|
|
45
|
+
export declare function summarizeCoordination(inputs: CoordinationInputs): CoordinationSummary;
|
|
46
|
+
/**
|
|
47
|
+
* Compact, agent-facing hints derived from a coordination summary — for
|
|
48
|
+
* surfacing inside other reports (e.g. agent briefs). Empty when coordination
|
|
49
|
+
* is unavailable or the swarm is clear, so it adds nothing in the common
|
|
50
|
+
* single-worktree case.
|
|
51
|
+
*/
|
|
52
|
+
export declare function coordinationHints(summary: CoordinationSummary): string[];
|
|
53
|
+
/**
|
|
54
|
+
* A stable fingerprint of the coordination state — readiness, worktree count,
|
|
55
|
+
* collision counts, and contention. `--watch` mode re-emits only when this
|
|
56
|
+
* changes between ticks, so stable state stays quiet.
|
|
57
|
+
*/
|
|
58
|
+
export declare function coordinationSignature(summary: CoordinationSummary): string;
|
|
59
|
+
/** Run the full coordination read for the repo's in-flight worktrees. */
|
|
60
|
+
export declare function computeCoordination(rootPath: string, options?: {
|
|
61
|
+
baseRef?: string;
|
|
62
|
+
}): Promise<CoordinationSummary>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { detectCollisions } from './collisionDetector.js';
|
|
2
|
+
import { listClaims, findContendedClaims } from './claims.js';
|
|
3
|
+
import { deriveMergeRisk } from './mergeRisk.js';
|
|
4
|
+
/** Pure: fold the three swarm signals into a summary + readiness verdict. */
|
|
5
|
+
export function summarizeCoordination(inputs) {
|
|
6
|
+
const { collisionReport, claims, mergeRisk } = inputs;
|
|
7
|
+
if (!collisionReport.available) {
|
|
8
|
+
return {
|
|
9
|
+
schemaVersion: 1,
|
|
10
|
+
available: false,
|
|
11
|
+
...(collisionReport.reason ? { reason: collisionReport.reason } : {}),
|
|
12
|
+
worktreeCount: collisionReport.worktrees.length,
|
|
13
|
+
collisions: { total: 0, high: 0, medium: 0 },
|
|
14
|
+
claims: { total: claims.length, contendedTargets: 0 },
|
|
15
|
+
mergeRisk: { hotspotCount: 0, integrationOrder: [] },
|
|
16
|
+
readiness: 'clear',
|
|
17
|
+
summary: [collisionReport.reason ?? 'Coordination unavailable.'],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const high = collisionReport.collisions.filter((c) => c.severity === 'high').length;
|
|
21
|
+
const medium = collisionReport.collisions.filter((c) => c.severity === 'medium').length;
|
|
22
|
+
const contendedTargets = new Set(findContendedClaims(claims).map((c) => c.target)).size;
|
|
23
|
+
const readiness = high > 0 || contendedTargets > 0
|
|
24
|
+
? 'conflicted'
|
|
25
|
+
: medium > 0
|
|
26
|
+
? 'caution'
|
|
27
|
+
: 'clear';
|
|
28
|
+
const summary = [];
|
|
29
|
+
summary.push(`${collisionReport.worktrees.length} in-flight worktree(s).`);
|
|
30
|
+
if (high + medium > 0) {
|
|
31
|
+
summary.push(`${high + medium} collision(s): ${high} high, ${medium} medium.`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
summary.push('No collisions across worktrees.');
|
|
35
|
+
}
|
|
36
|
+
if (contendedTargets > 0)
|
|
37
|
+
summary.push(`${contendedTargets} claim target(s) contended by multiple agents.`);
|
|
38
|
+
if (mergeRisk.hotFiles.length > 0)
|
|
39
|
+
summary.push(`${mergeRisk.hotFiles.length} merge hotspot file(s).`);
|
|
40
|
+
if (mergeRisk.integrationOrder.length > 0) {
|
|
41
|
+
const first = mergeRisk.integrationOrder[0];
|
|
42
|
+
summary.push(`Merge ${first.branch ?? first.worktree} first (lowest risk).`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
available: true,
|
|
47
|
+
worktreeCount: collisionReport.worktrees.length,
|
|
48
|
+
collisions: { total: collisionReport.collisions.length, high, medium },
|
|
49
|
+
claims: { total: claims.length, contendedTargets },
|
|
50
|
+
mergeRisk: {
|
|
51
|
+
hotspotCount: mergeRisk.hotFiles.length,
|
|
52
|
+
integrationOrder: mergeRisk.integrationOrder.map((o) => ({
|
|
53
|
+
worktree: o.worktree,
|
|
54
|
+
branch: o.branch,
|
|
55
|
+
riskScore: o.riskScore,
|
|
56
|
+
})),
|
|
57
|
+
},
|
|
58
|
+
readiness,
|
|
59
|
+
summary,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compact, agent-facing hints derived from a coordination summary — for
|
|
64
|
+
* surfacing inside other reports (e.g. agent briefs). Empty when coordination
|
|
65
|
+
* is unavailable or the swarm is clear, so it adds nothing in the common
|
|
66
|
+
* single-worktree case.
|
|
67
|
+
*/
|
|
68
|
+
export function coordinationHints(summary) {
|
|
69
|
+
if (!summary.available || summary.readiness === 'clear')
|
|
70
|
+
return [];
|
|
71
|
+
const hints = [`Swarm readiness: ${summary.readiness} — run \`projscan coordinate\` for details.`];
|
|
72
|
+
if (summary.collisions.high > 0) {
|
|
73
|
+
hints.push(`${summary.collisions.high} high-severity collision(s) (same file edited by two worktrees).`);
|
|
74
|
+
}
|
|
75
|
+
if (summary.collisions.medium > 0) {
|
|
76
|
+
hints.push(`${summary.collisions.medium} dependency collision(s) across worktrees.`);
|
|
77
|
+
}
|
|
78
|
+
if (summary.claims.contendedTargets > 0) {
|
|
79
|
+
hints.push(`${summary.claims.contendedTargets} claim target(s) contended by multiple agents.`);
|
|
80
|
+
}
|
|
81
|
+
const first = summary.mergeRisk.integrationOrder[0];
|
|
82
|
+
if (first)
|
|
83
|
+
hints.push(`Merge ${first.branch ?? first.worktree} first (lowest risk).`);
|
|
84
|
+
return hints;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A stable fingerprint of the coordination state — readiness, worktree count,
|
|
88
|
+
* collision counts, and contention. `--watch` mode re-emits only when this
|
|
89
|
+
* changes between ticks, so stable state stays quiet.
|
|
90
|
+
*/
|
|
91
|
+
export function coordinationSignature(summary) {
|
|
92
|
+
return [
|
|
93
|
+
summary.available ? 'a' : 'u',
|
|
94
|
+
summary.readiness,
|
|
95
|
+
summary.worktreeCount,
|
|
96
|
+
summary.collisions.high,
|
|
97
|
+
summary.collisions.medium,
|
|
98
|
+
summary.claims.contendedTargets,
|
|
99
|
+
summary.mergeRisk.hotspotCount,
|
|
100
|
+
].join(':');
|
|
101
|
+
}
|
|
102
|
+
/** Run the full coordination read for the repo's in-flight worktrees. */
|
|
103
|
+
export async function computeCoordination(rootPath, options = {}) {
|
|
104
|
+
// One collision pass feeds both the summary and the merge-risk derivation —
|
|
105
|
+
// detectCollisions builds the code graph, so we must not run it twice.
|
|
106
|
+
const [collisionReport, claims] = await Promise.all([
|
|
107
|
+
detectCollisions(rootPath, options),
|
|
108
|
+
listClaims(rootPath),
|
|
109
|
+
]);
|
|
110
|
+
const { integrationOrder, hotFiles } = deriveMergeRisk(collisionReport);
|
|
111
|
+
const mergeRisk = {
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
available: collisionReport.available,
|
|
114
|
+
...(collisionReport.reason ? { reason: collisionReport.reason } : {}),
|
|
115
|
+
integrationOrder,
|
|
116
|
+
hotFiles,
|
|
117
|
+
collisions: collisionReport.collisions,
|
|
118
|
+
};
|
|
119
|
+
return summarizeCoordination({ collisionReport, claims, mergeRisk });
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=coordination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordination.js","sourceRoot":"","sources":["../../src/core/coordination.ts"],"names":[],"mappings":"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"}
|
package/dist/core/embeddings.js
CHANGED
|
@@ -67,28 +67,41 @@ async function getPipeline(model, onFirstLoad) {
|
|
|
67
67
|
const mod = await tryLoadTransformers();
|
|
68
68
|
if (!mod)
|
|
69
69
|
return null;
|
|
70
|
-
let
|
|
71
|
-
if (
|
|
70
|
+
let loading = pipelines.get(model);
|
|
71
|
+
if (loading) {
|
|
72
72
|
// Bump on access so the LRU cache treats the recently-used model as
|
|
73
73
|
// hot. Map insertion order = recency.
|
|
74
74
|
pipelines.delete(model);
|
|
75
|
-
pipelines.set(model,
|
|
76
|
-
return existing;
|
|
75
|
+
pipelines.set(model, loading);
|
|
77
76
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
else {
|
|
78
|
+
onFirstLoad?.(`Loading embedding model ${model} (first run downloads ~25MB)...`);
|
|
79
|
+
loading = mod.pipeline('feature-extraction', model, {
|
|
80
|
+
quantized: true,
|
|
81
|
+
});
|
|
82
|
+
pipelines.set(model, loading);
|
|
83
|
+
// Evict the least-recently-used pipeline when over the bound. Map
|
|
84
|
+
// iteration is in insertion order, so the first key is the oldest.
|
|
85
|
+
while (pipelines.size > MAX_CACHED_PIPELINES) {
|
|
86
|
+
const oldest = pipelines.keys().next().value;
|
|
87
|
+
if (oldest === undefined)
|
|
88
|
+
break;
|
|
89
|
+
pipelines.delete(oldest);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return await loading;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
// Model download / instantiation failed — offline, an HTTP 429 from the
|
|
97
|
+
// model host (huggingface.co rate-limits unauthenticated CI traffic), or a
|
|
98
|
+
// corrupt local cache. Drop the rejected promise so it can't poison the
|
|
99
|
+
// cache for the rest of the process, and degrade gracefully to null so
|
|
100
|
+
// callers fall back to BM25 instead of throwing.
|
|
101
|
+
pipelines.delete(model);
|
|
102
|
+
process.stderr.write(`[projscan] embedding model "${model}" failed to load: ${err instanceof Error ? err.message : String(err)}. Semantic features disabled for this run.\n`);
|
|
103
|
+
return null;
|
|
90
104
|
}
|
|
91
|
-
return existing;
|
|
92
105
|
}
|
|
93
106
|
/**
|
|
94
107
|
* Test-only: drop all cached pipelines. Lets unit tests verify LRU
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../src/core/embeddings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,MAAM,aAAa,GAAG,yBAAyB,CAAC;AACvD,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAuBjC,IAAI,YAAmD,CAAC;AAExD,kEAAkE;AAClE,qEAAqE;AACrE,sEAAsE;AACtE,gEAAgE;AAChE,uEAAuE;AACvE,mEAAmE;AACnE,6CAA6C;AAC7C,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,EAAE;AACF,oEAAoE;AACpE,qEAAqE;AACrE,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAqC,CAAC;AAE/D,KAAK,UAAU,mBAAmB;IAChC,IAAI,aAAa,EAAE;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,YAAY,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAuB,CAAC;QACzE,YAAY,GAAG,GAAG,CAAC;QACnB,yEAAyE;QACzE,mDAAmD;QACnD,IAAI,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC1C,GAAG,CAAC,GAA+B,CAAC,gBAAgB,GAAG,KAAK,CAAC;QAChE,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,GAAwB,CAAC;QACnC,IAAI,CAAC,CAAC,IAAI,KAAK,sBAAsB,IAAI,CAAC,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;YACvE,YAAY,GAAG,IAAI,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,6EAA6E;QAC7E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjH,YAAY,GAAG,IAAI,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,GAAG,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACxC,OAAO,GAAG,KAAK,IAAI,CAAC;AACtB,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,KAAa,EAAE,WAAiC;IACzE,MAAM,GAAG,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACxC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,IAAI,
|
|
1
|
+
{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../src/core/embeddings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent router (4.x agent-ergonomics, epic 4).
|
|
3
|
+
*
|
|
4
|
+
* projscan exposes 40+ MCP tools. An agent shouldn't have to reason over all of
|
|
5
|
+
* them every turn — it should be able to state a goal and get pointed at the one
|
|
6
|
+
* right tool. `routeIntent` does exactly that: a deterministic, curated map from
|
|
7
|
+
* common agent intents to the tool + exact call. No LLM (projscan never embeds
|
|
8
|
+
* inference); ranking is keyword overlap against a hand-curated catalog.
|
|
9
|
+
*
|
|
10
|
+
* This is the additive, non-breaking half of the epic — a discovery entry point.
|
|
11
|
+
* Actually shrinking the advertised tool surface (hiding the long tail behind
|
|
12
|
+
* this router) is a breaking change reserved for 4.0.
|
|
13
|
+
*/
|
|
14
|
+
export interface RouteEntry {
|
|
15
|
+
/** Short intent label. */
|
|
16
|
+
intent: string;
|
|
17
|
+
category: string;
|
|
18
|
+
tool: string;
|
|
19
|
+
cli: string;
|
|
20
|
+
/** What the tool does, one line. */
|
|
21
|
+
what: string;
|
|
22
|
+
/** When to reach for it. */
|
|
23
|
+
why: string;
|
|
24
|
+
/** A runnable example. */
|
|
25
|
+
example: string;
|
|
26
|
+
/** Terms that signal this intent. */
|
|
27
|
+
keywords: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface RouteResult {
|
|
30
|
+
intent: string | null;
|
|
31
|
+
matched: boolean;
|
|
32
|
+
matches: RouteEntry[];
|
|
33
|
+
}
|
|
34
|
+
export declare const ROUTE_CATALOG: RouteEntry[];
|
|
35
|
+
/**
|
|
36
|
+
* Map a stated intent to the best-matching projscan tool(s). With no intent,
|
|
37
|
+
* returns the full catalog grouped by category. Ranking is keyword overlap;
|
|
38
|
+
* ties keep catalog order (deterministic).
|
|
39
|
+
*/
|
|
40
|
+
export declare function routeIntent(intent: string | undefined): RouteResult;
|