devworkspace 0.3.1 → 0.3.2
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/CHANGELOG.md +18 -1
- package/README.md +3 -1
- package/dist/cli/args.d.ts +42 -0
- package/dist/cli/args.js +12 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.js +52 -5
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/render.d.ts +10 -0
- package/dist/cli/render.js +167 -0
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/run.js +84 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/config/schemas.d.ts +6 -0
- package/dist/config/schemas.js +6 -0
- package/dist/config/schemas.js.map +1 -1
- package/dist/core/version.d.ts +7 -0
- package/dist/core/version.js +13 -0
- package/dist/core/version.js.map +1 -1
- package/dist/core/workspace.d.ts +1 -0
- package/dist/core/workspace.js +1 -0
- package/dist/core/workspace.js.map +1 -1
- package/dist/discovery/commands.d.ts +1 -0
- package/dist/discovery/commands.js +3 -1
- package/dist/discovery/commands.js.map +1 -1
- package/dist/features/schema.d.ts +3 -0
- package/dist/features/schema.js +79 -0
- package/dist/features/schema.js.map +1 -0
- package/dist/features/types.d.ts +77 -0
- package/dist/features/types.js +2 -0
- package/dist/features/types.js.map +1 -0
- package/dist/features/validate.d.ts +14 -0
- package/dist/features/validate.js +124 -0
- package/dist/features/validate.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/services/doctor.js +34 -0
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/drift.d.ts +21 -0
- package/dist/services/drift.js +89 -0
- package/dist/services/drift.js.map +1 -0
- package/dist/services/features.d.ts +22 -0
- package/dist/services/features.js +105 -0
- package/dist/services/features.js.map +1 -0
- package/dist/services/graph.d.ts +97 -0
- package/dist/services/graph.js +342 -0
- package/dist/services/graph.js.map +1 -0
- package/dist/services/refresh.js +15 -0
- package/dist/services/refresh.js.map +1 -1
- package/dist/services/setup.d.ts +56 -0
- package/dist/services/setup.js +116 -0
- package/dist/services/setup.js.map +1 -0
- package/dist/services/status.d.ts +6 -0
- package/dist/services/status.js +16 -0
- package/dist/services/status.js.map +1 -1
- package/dist/services/verify.d.ts +11 -0
- package/dist/services/verify.js +64 -2
- package/dist/services/verify.js.map +1 -1
- package/docs/architecture-report/01-executive-summary.md +168 -0
- package/docs/architecture-report/02-system-architecture.md +356 -0
- package/docs/architecture-report/03-tools-and-technologies.md +112 -0
- package/docs/architecture-report/04-execution-flows.md +257 -0
- package/docs/architecture-report/05-codebase-map.md +150 -0
- package/docs/architecture-report/06-command-reference.md +446 -0
- package/docs/architecture-report/07-data-flow-and-integrations.md +268 -0
- package/docs/architecture-report/08-build-test-deployment.md +185 -0
- package/docs/architecture-report/09-security-and-reliability.md +165 -0
- package/docs/architecture-report/10-feature-status.md +110 -0
- package/docs/architecture-report/11-glossary.md +97 -0
- package/docs/architecture-report/README.md +103 -0
- package/docs/architecture-report/diagrams.md +446 -0
- package/docs/getting-started.md +38 -1
- package/package.json +1 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathExists, readTextBounded } from '../utils/fs.js';
|
|
4
|
+
import { findSymbolInText } from '../features/validate.js';
|
|
5
|
+
import { requireWorkspace } from '../core/workspace.js';
|
|
6
|
+
import { DevWorkspaceError } from '../errors/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the location of Graphify's output graph in the repository.
|
|
9
|
+
*/
|
|
10
|
+
export function getGraphPath(root) {
|
|
11
|
+
return path.join(root, 'graphify-out', 'graph.json');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loads and validates Graphify graph JSON data safely.
|
|
15
|
+
*/
|
|
16
|
+
export async function loadGraphData(root) {
|
|
17
|
+
const gPath = getGraphPath(root);
|
|
18
|
+
if (!(await pathExists(gPath))) {
|
|
19
|
+
return { exists: false, path: gPath };
|
|
20
|
+
}
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
raw = await fs.readFile(gPath, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
throw new DevWorkspaceError('IO', `Failed to read Graphify output at "${gPath}": ${e.message}`, {
|
|
27
|
+
hint: 'Ensure graphify-out/graph.json is readable or regenerate with "graphify update .".',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new DevWorkspaceError('SCHEMA', `Graphify output at "${gPath}" is not valid JSON.`, {
|
|
36
|
+
hint: 'Regenerate the graph using "graphify update ." or "/graphify ." in Claude Code.',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
40
|
+
throw new DevWorkspaceError('SCHEMA', `Graphify output at "${gPath}" must be a JSON object with nodes and edges.`, {
|
|
41
|
+
hint: 'Regenerate the graph using "graphify update .".',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const obj = parsed;
|
|
45
|
+
const nodes = Array.isArray(obj.nodes) ? obj.nodes : [];
|
|
46
|
+
const edges = Array.isArray(obj.edges) ? obj.edges : Array.isArray(obj.links) ? obj.links : [];
|
|
47
|
+
return {
|
|
48
|
+
exists: true,
|
|
49
|
+
path: gPath,
|
|
50
|
+
data: { nodes, edges },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reports the presence, size, and basic schema health of the Graphify graph.
|
|
55
|
+
*/
|
|
56
|
+
export async function getGraphStatusService(o) {
|
|
57
|
+
const ws = await requireWorkspace(o.cwd);
|
|
58
|
+
const loaded = await loadGraphData(ws.root);
|
|
59
|
+
if (!loaded.exists || !loaded.data) {
|
|
60
|
+
return {
|
|
61
|
+
available: false,
|
|
62
|
+
graphPath: loaded.path,
|
|
63
|
+
nodesCount: 0,
|
|
64
|
+
edgesCount: 0,
|
|
65
|
+
nodeTypes: {},
|
|
66
|
+
message: 'No Graphify knowledge graph found at graphify-out/graph.json.',
|
|
67
|
+
hint: 'In Claude Code run "/graphify ." to build the graph, or run "graphify update ." if already initialized.',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const { nodes, edges } = loaded.data;
|
|
71
|
+
const nodeTypes = {};
|
|
72
|
+
for (const n of nodes) {
|
|
73
|
+
const t = n.type || 'unknown';
|
|
74
|
+
nodeTypes[t] = (nodeTypes[t] || 0) + 1;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
available: true,
|
|
78
|
+
graphPath: loaded.path,
|
|
79
|
+
nodesCount: nodes.length,
|
|
80
|
+
edgesCount: edges.length,
|
|
81
|
+
nodeTypes,
|
|
82
|
+
message: `Graphify graph loaded: ${nodes.length} nodes, ${edges.length} edges.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extracts a candidate file path and symbol from a node definition.
|
|
87
|
+
*/
|
|
88
|
+
function extractPathAndSymbol(node) {
|
|
89
|
+
// If explicitly declared:
|
|
90
|
+
const rawPath = typeof node.path === 'string' ? node.path : typeof node.file === 'string' ? node.file : null;
|
|
91
|
+
let relPath = rawPath ? rawPath.replace(/\\/g, '/').replace(/^\/+/, '') : null;
|
|
92
|
+
let symbol = null;
|
|
93
|
+
if (node.id.includes('::')) {
|
|
94
|
+
const parts = node.id.split('::');
|
|
95
|
+
if (!relPath && parts[0] && (parts[0].includes('/') || parts[0].includes('.'))) {
|
|
96
|
+
relPath = parts[0].replace(/\\/g, '/').replace(/^\/+/, '');
|
|
97
|
+
}
|
|
98
|
+
symbol = parts[1] || null;
|
|
99
|
+
}
|
|
100
|
+
else if (!relPath && (node.id.includes('/') || node.id.includes('.'))) {
|
|
101
|
+
relPath = node.id.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
102
|
+
}
|
|
103
|
+
if (!symbol && typeof node.name === 'string' && node.name !== relPath) {
|
|
104
|
+
symbol = node.name;
|
|
105
|
+
}
|
|
106
|
+
return { relPath, symbol };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Verifies whether a node's physical path and symbol exist on disk.
|
|
110
|
+
*/
|
|
111
|
+
async function verifyNode(root, node, depth) {
|
|
112
|
+
const { relPath, symbol } = extractPathAndSymbol(node);
|
|
113
|
+
const name = typeof node.name === 'string' ? node.name : typeof node.label === 'string' ? node.label : node.id;
|
|
114
|
+
const type = typeof node.type === 'string' ? node.type : 'symbol';
|
|
115
|
+
if (!relPath) {
|
|
116
|
+
return {
|
|
117
|
+
id: node.id,
|
|
118
|
+
name,
|
|
119
|
+
type,
|
|
120
|
+
path: null,
|
|
121
|
+
symbol,
|
|
122
|
+
depth,
|
|
123
|
+
status: 'hypothesis',
|
|
124
|
+
fileExists: false,
|
|
125
|
+
symbolFound: null,
|
|
126
|
+
reason: 'No source file associated with node',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const fullPath = path.join(root, ...relPath.split('/'));
|
|
130
|
+
const exists = await pathExists(fullPath);
|
|
131
|
+
if (!exists) {
|
|
132
|
+
return {
|
|
133
|
+
id: node.id,
|
|
134
|
+
name,
|
|
135
|
+
type,
|
|
136
|
+
path: relPath,
|
|
137
|
+
symbol,
|
|
138
|
+
depth,
|
|
139
|
+
status: 'hypothesis',
|
|
140
|
+
fileExists: false,
|
|
141
|
+
symbolFound: null,
|
|
142
|
+
reason: `File "${relPath}" not found on disk`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
let symbolFound = null;
|
|
146
|
+
let status = 'verified';
|
|
147
|
+
let reason;
|
|
148
|
+
if (symbol) {
|
|
149
|
+
try {
|
|
150
|
+
const text = await readTextBounded(fullPath, 500_000);
|
|
151
|
+
if (text) {
|
|
152
|
+
symbolFound = findSymbolInText(text, symbol);
|
|
153
|
+
if (!symbolFound) {
|
|
154
|
+
status = 'hypothesis';
|
|
155
|
+
reason = `Symbol "${symbol}" not found in "${relPath}"`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
status = 'hypothesis';
|
|
161
|
+
reason = `Could not inspect symbols in "${relPath}"`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
id: node.id,
|
|
166
|
+
name,
|
|
167
|
+
type,
|
|
168
|
+
path: relPath,
|
|
169
|
+
symbol,
|
|
170
|
+
depth,
|
|
171
|
+
status,
|
|
172
|
+
fileExists: true,
|
|
173
|
+
symbolFound,
|
|
174
|
+
...(reason ? { reason } : {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Finds the starting node in a graph that matches the user's query.
|
|
179
|
+
*/
|
|
180
|
+
export function findMatchingStartNode(nodes, query) {
|
|
181
|
+
const q = query.trim().toLowerCase();
|
|
182
|
+
const qNorm = q.replace(/\\/g, '/');
|
|
183
|
+
// 1. Exact match on ID
|
|
184
|
+
const exactId = nodes.find((n) => n.id.toLowerCase() === q || n.id.toLowerCase().replace(/\\/g, '/') === qNorm);
|
|
185
|
+
if (exactId)
|
|
186
|
+
return exactId;
|
|
187
|
+
// 2. Exact match on path or file
|
|
188
|
+
const exactPath = nodes.find((n) => {
|
|
189
|
+
const p = (n.path || n.file || '').toLowerCase().replace(/\\/g, '/');
|
|
190
|
+
return p === qNorm;
|
|
191
|
+
});
|
|
192
|
+
if (exactPath)
|
|
193
|
+
return exactPath;
|
|
194
|
+
// 3. Exact match on name or symbol
|
|
195
|
+
const exactName = nodes.find((n) => {
|
|
196
|
+
const name = (n.name || n.label || '').toLowerCase();
|
|
197
|
+
return name === q;
|
|
198
|
+
});
|
|
199
|
+
if (exactName)
|
|
200
|
+
return exactName;
|
|
201
|
+
// 4. Substring match on ID or path
|
|
202
|
+
const subMatch = nodes.find((n) => {
|
|
203
|
+
const id = n.id.toLowerCase().replace(/\\/g, '/');
|
|
204
|
+
const p = (n.path || n.file || '').toLowerCase().replace(/\\/g, '/');
|
|
205
|
+
return id.includes(qNorm) || p.includes(qNorm);
|
|
206
|
+
});
|
|
207
|
+
if (subMatch)
|
|
208
|
+
return subMatch;
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Performs a bounded graph trace starting from a specified file or symbol node up to maxDepth.
|
|
213
|
+
* Verifies every traversed node against the repository filesystem, marking unverified nodes
|
|
214
|
+
* and edges as hypotheses.
|
|
215
|
+
*/
|
|
216
|
+
export async function traceGraphService(o, query, options = {}) {
|
|
217
|
+
const ws = await requireWorkspace(o.cwd);
|
|
218
|
+
const loaded = await loadGraphData(ws.root);
|
|
219
|
+
const depthLimit = Math.min(Math.max(options.depth ?? 2, 1), 10);
|
|
220
|
+
const maxNodes = Math.min(options.maxNodes ?? 150, 500);
|
|
221
|
+
if (!loaded.exists || !loaded.data) {
|
|
222
|
+
return {
|
|
223
|
+
available: false,
|
|
224
|
+
query,
|
|
225
|
+
depth: depthLimit,
|
|
226
|
+
matchedNode: null,
|
|
227
|
+
totalNodes: 0,
|
|
228
|
+
verifiedNodes: 0,
|
|
229
|
+
hypothesisNodes: 0,
|
|
230
|
+
nodes: [],
|
|
231
|
+
edges: [],
|
|
232
|
+
warnings: ['No Graphify graph found at graphify-out/graph.json. Run "/graphify ." in Claude Code.'],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const { nodes, edges } = loaded.data;
|
|
236
|
+
const startNode = findMatchingStartNode(nodes, query);
|
|
237
|
+
if (!startNode) {
|
|
238
|
+
return {
|
|
239
|
+
available: true,
|
|
240
|
+
query,
|
|
241
|
+
depth: depthLimit,
|
|
242
|
+
matchedNode: null,
|
|
243
|
+
totalNodes: 0,
|
|
244
|
+
verifiedNodes: 0,
|
|
245
|
+
hypothesisNodes: 0,
|
|
246
|
+
nodes: [],
|
|
247
|
+
edges: [],
|
|
248
|
+
warnings: [`No graph node found matching "${query}". Check node IDs with "devworkspace graph status".`],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// Build adjacency index
|
|
252
|
+
const outgoing = new Map();
|
|
253
|
+
const incoming = new Map();
|
|
254
|
+
for (const e of edges) {
|
|
255
|
+
if (!outgoing.has(e.source))
|
|
256
|
+
outgoing.set(e.source, []);
|
|
257
|
+
outgoing.get(e.source).push(e);
|
|
258
|
+
if (!incoming.has(e.target))
|
|
259
|
+
incoming.set(e.target, []);
|
|
260
|
+
incoming.get(e.target).push(e);
|
|
261
|
+
}
|
|
262
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
263
|
+
// Breadth-first search bounded by depthLimit
|
|
264
|
+
const visitedDepths = new Map();
|
|
265
|
+
const queue = [{ id: startNode.id, depth: 0 }];
|
|
266
|
+
visitedDepths.set(startNode.id, 0);
|
|
267
|
+
const collectedEdges = [];
|
|
268
|
+
while (queue.length > 0 && visitedDepths.size < maxNodes) {
|
|
269
|
+
const current = queue.shift();
|
|
270
|
+
if (current.depth >= depthLimit)
|
|
271
|
+
continue;
|
|
272
|
+
// Explore outgoing edges
|
|
273
|
+
const outEdges = outgoing.get(current.id) || [];
|
|
274
|
+
for (const edge of outEdges) {
|
|
275
|
+
collectedEdges.push(edge);
|
|
276
|
+
if (!visitedDepths.has(edge.target)) {
|
|
277
|
+
visitedDepths.set(edge.target, current.depth + 1);
|
|
278
|
+
queue.push({ id: edge.target, depth: current.depth + 1 });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Explore incoming edges
|
|
282
|
+
const inEdges = incoming.get(current.id) || [];
|
|
283
|
+
for (const edge of inEdges) {
|
|
284
|
+
collectedEdges.push(edge);
|
|
285
|
+
if (!visitedDepths.has(edge.source)) {
|
|
286
|
+
visitedDepths.set(edge.source, current.depth + 1);
|
|
287
|
+
queue.push({ id: edge.source, depth: current.depth + 1 });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Verify all traversed nodes against disk
|
|
292
|
+
const tracedNodes = [];
|
|
293
|
+
const nodeStatusMap = new Map();
|
|
294
|
+
for (const [nodeId, depth] of visitedDepths.entries()) {
|
|
295
|
+
const rawNode = nodeMap.get(nodeId) || { id: nodeId };
|
|
296
|
+
const verified = await verifyNode(ws.root, rawNode, depth);
|
|
297
|
+
tracedNodes.push(verified);
|
|
298
|
+
nodeStatusMap.set(nodeId, verified.status);
|
|
299
|
+
}
|
|
300
|
+
// Deduplicate and verify collected edges
|
|
301
|
+
const edgeKeys = new Set();
|
|
302
|
+
const tracedEdges = [];
|
|
303
|
+
for (const e of collectedEdges) {
|
|
304
|
+
if (!visitedDepths.has(e.source) || !visitedDepths.has(e.target))
|
|
305
|
+
continue;
|
|
306
|
+
const key = `${e.source}->${e.target}::${e.relation || e.type || ''}`;
|
|
307
|
+
if (edgeKeys.has(key))
|
|
308
|
+
continue;
|
|
309
|
+
edgeKeys.add(key);
|
|
310
|
+
const srcStatus = nodeStatusMap.get(e.source);
|
|
311
|
+
const tgtStatus = nodeStatusMap.get(e.target);
|
|
312
|
+
const edgeStatus = srcStatus === 'verified' && tgtStatus === 'verified' ? 'verified' : 'hypothesis';
|
|
313
|
+
tracedEdges.push({
|
|
314
|
+
source: e.source,
|
|
315
|
+
target: e.target,
|
|
316
|
+
relation: typeof e.relation === 'string' ? e.relation : typeof e.type === 'string' ? e.type : 'relates_to',
|
|
317
|
+
status: edgeStatus,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// Sort nodes by depth, then by path/name
|
|
321
|
+
tracedNodes.sort((a, b) => a.depth - b.depth || (a.path || a.id).localeCompare(b.path || b.id));
|
|
322
|
+
const matched = tracedNodes.find((n) => n.id === startNode.id) || null;
|
|
323
|
+
const verifiedCount = tracedNodes.filter((n) => n.status === 'verified').length;
|
|
324
|
+
const hypothesisCount = tracedNodes.filter((n) => n.status === 'hypothesis').length;
|
|
325
|
+
const warnings = [];
|
|
326
|
+
if (hypothesisCount > 0) {
|
|
327
|
+
warnings.push(`${hypothesisCount} node(s) or connected dependencies are unverified hypotheses (files or symbols missing on disk).`);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
available: true,
|
|
331
|
+
query,
|
|
332
|
+
depth: depthLimit,
|
|
333
|
+
matchedNode: matched,
|
|
334
|
+
totalNodes: tracedNodes.length,
|
|
335
|
+
verifiedNodes: verifiedCount,
|
|
336
|
+
hypothesisNodes: hypothesisCount,
|
|
337
|
+
nodes: tracedNodes,
|
|
338
|
+
edges: tracedEdges,
|
|
339
|
+
warnings,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/services/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { pathExists, readTextBounded } from '../utils/fs.js';\nimport { findSymbolInText } from '../features/validate.js';\nimport { requireWorkspace } from '../core/workspace.js';\nimport type { ServiceOptions } from './common.js';\nimport { DevWorkspaceError } from '../errors/index.js';\n\nexport type NodeVerificationStatus = 'verified' | 'hypothesis';\n\nexport interface RawGraphNode {\n id: string;\n name?: string;\n label?: string;\n type?: string;\n path?: string;\n file?: string;\n [key: string]: unknown;\n}\n\nexport interface RawGraphEdge {\n source: string;\n target: string;\n relation?: string;\n type?: string;\n label?: string;\n weight?: number;\n [key: string]: unknown;\n}\n\nexport interface RawGraphData {\n nodes?: RawGraphNode[];\n edges?: RawGraphEdge[];\n links?: RawGraphEdge[];\n [key: string]: unknown;\n}\n\nexport interface TracedNode {\n id: string;\n name: string;\n type: string;\n path: string | null;\n symbol: string | null;\n depth: number;\n status: NodeVerificationStatus;\n fileExists: boolean;\n symbolFound: boolean | null;\n reason?: string;\n}\n\nexport interface TracedEdge {\n source: string;\n target: string;\n relation: string;\n status: NodeVerificationStatus;\n}\n\nexport interface GraphStatusResult {\n available: boolean;\n graphPath: string;\n nodesCount: number;\n edgesCount: number;\n nodeTypes: Record<string, number>;\n message: string;\n hint?: string;\n}\n\nexport interface GraphTraceResult {\n available: boolean;\n query: string;\n depth: number;\n matchedNode: TracedNode | null;\n totalNodes: number;\n verifiedNodes: number;\n hypothesisNodes: number;\n nodes: TracedNode[];\n edges: TracedEdge[];\n warnings: string[];\n}\n\n/**\n * Resolves the location of Graphify's output graph in the repository.\n */\nexport function getGraphPath(root: string): string {\n return path.join(root, 'graphify-out', 'graph.json');\n}\n\n/**\n * Loads and validates Graphify graph JSON data safely.\n */\nexport async function loadGraphData(root: string): Promise<{ exists: boolean; path: string; data?: { nodes: RawGraphNode[]; edges: RawGraphEdge[] } }> {\n const gPath = getGraphPath(root);\n if (!(await pathExists(gPath))) {\n return { exists: false, path: gPath };\n }\n\n let raw: string;\n try {\n raw = await fs.readFile(gPath, 'utf8');\n } catch (e) {\n throw new DevWorkspaceError('IO', `Failed to read Graphify output at \"${gPath}\": ${(e as Error).message}`, {\n hint: 'Ensure graphify-out/graph.json is readable or regenerate with \"graphify update .\".',\n });\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new DevWorkspaceError('SCHEMA', `Graphify output at \"${gPath}\" is not valid JSON.`, {\n hint: 'Regenerate the graph using \"graphify update .\" or \"/graphify .\" in Claude Code.',\n });\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new DevWorkspaceError('SCHEMA', `Graphify output at \"${gPath}\" must be a JSON object with nodes and edges.`, {\n hint: 'Regenerate the graph using \"graphify update .\".',\n });\n }\n\n const obj = parsed as RawGraphData;\n const nodes = Array.isArray(obj.nodes) ? obj.nodes : [];\n const edges = Array.isArray(obj.edges) ? obj.edges : Array.isArray(obj.links) ? obj.links : [];\n\n return {\n exists: true,\n path: gPath,\n data: { nodes, edges },\n };\n}\n\n/**\n * Reports the presence, size, and basic schema health of the Graphify graph.\n */\nexport async function getGraphStatusService(o: ServiceOptions): Promise<GraphStatusResult> {\n const ws = await requireWorkspace(o.cwd);\n const loaded = await loadGraphData(ws.root);\n\n if (!loaded.exists || !loaded.data) {\n return {\n available: false,\n graphPath: loaded.path,\n nodesCount: 0,\n edgesCount: 0,\n nodeTypes: {},\n message: 'No Graphify knowledge graph found at graphify-out/graph.json.',\n hint: 'In Claude Code run \"/graphify .\" to build the graph, or run \"graphify update .\" if already initialized.',\n };\n }\n\n const { nodes, edges } = loaded.data;\n const nodeTypes: Record<string, number> = {};\n for (const n of nodes) {\n const t = n.type || 'unknown';\n nodeTypes[t] = (nodeTypes[t] || 0) + 1;\n }\n\n return {\n available: true,\n graphPath: loaded.path,\n nodesCount: nodes.length,\n edgesCount: edges.length,\n nodeTypes,\n message: `Graphify graph loaded: ${nodes.length} nodes, ${edges.length} edges.`,\n };\n}\n\n/**\n * Extracts a candidate file path and symbol from a node definition.\n */\nfunction extractPathAndSymbol(node: RawGraphNode): { relPath: string | null; symbol: string | null } {\n // If explicitly declared:\n const rawPath = typeof node.path === 'string' ? node.path : typeof node.file === 'string' ? node.file : null;\n let relPath = rawPath ? rawPath.replace(/\\\\/g, '/').replace(/^\\/+/, '') : null;\n let symbol: string | null = null;\n\n if (node.id.includes('::')) {\n const parts = node.id.split('::');\n if (!relPath && parts[0] && (parts[0].includes('/') || parts[0].includes('.'))) {\n relPath = parts[0].replace(/\\\\/g, '/').replace(/^\\/+/, '');\n }\n symbol = parts[1] || null;\n } else if (!relPath && (node.id.includes('/') || node.id.includes('.'))) {\n relPath = node.id.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n }\n\n if (!symbol && typeof node.name === 'string' && node.name !== relPath) {\n symbol = node.name;\n }\n\n return { relPath, symbol };\n}\n\n/**\n * Verifies whether a node's physical path and symbol exist on disk.\n */\nasync function verifyNode(root: string, node: RawGraphNode, depth: number): Promise<TracedNode> {\n const { relPath, symbol } = extractPathAndSymbol(node);\n const name = typeof node.name === 'string' ? node.name : typeof node.label === 'string' ? node.label : node.id;\n const type = typeof node.type === 'string' ? node.type : 'symbol';\n\n if (!relPath) {\n return {\n id: node.id,\n name,\n type,\n path: null,\n symbol,\n depth,\n status: 'hypothesis',\n fileExists: false,\n symbolFound: null,\n reason: 'No source file associated with node',\n };\n }\n\n const fullPath = path.join(root, ...relPath.split('/'));\n const exists = await pathExists(fullPath);\n\n if (!exists) {\n return {\n id: node.id,\n name,\n type,\n path: relPath,\n symbol,\n depth,\n status: 'hypothesis',\n fileExists: false,\n symbolFound: null,\n reason: `File \"${relPath}\" not found on disk`,\n };\n }\n\n let symbolFound: boolean | null = null;\n let status: NodeVerificationStatus = 'verified';\n let reason: string | undefined;\n\n if (symbol) {\n try {\n const text = await readTextBounded(fullPath, 500_000);\n if (text) {\n symbolFound = findSymbolInText(text, symbol);\n if (!symbolFound) {\n status = 'hypothesis';\n reason = `Symbol \"${symbol}\" not found in \"${relPath}\"`;\n }\n }\n } catch {\n status = 'hypothesis';\n reason = `Could not inspect symbols in \"${relPath}\"`;\n }\n }\n\n return {\n id: node.id,\n name,\n type,\n path: relPath,\n symbol,\n depth,\n status,\n fileExists: true,\n symbolFound,\n ...(reason ? { reason } : {}),\n };\n}\n\n/**\n * Finds the starting node in a graph that matches the user's query.\n */\nexport function findMatchingStartNode(nodes: RawGraphNode[], query: string): RawGraphNode | null {\n const q = query.trim().toLowerCase();\n const qNorm = q.replace(/\\\\/g, '/');\n\n // 1. Exact match on ID\n const exactId = nodes.find((n) => n.id.toLowerCase() === q || n.id.toLowerCase().replace(/\\\\/g, '/') === qNorm);\n if (exactId) return exactId;\n\n // 2. Exact match on path or file\n const exactPath = nodes.find((n) => {\n const p = (n.path || n.file || '').toLowerCase().replace(/\\\\/g, '/');\n return p === qNorm;\n });\n if (exactPath) return exactPath;\n\n // 3. Exact match on name or symbol\n const exactName = nodes.find((n) => {\n const name = (n.name || n.label || '').toLowerCase();\n return name === q;\n });\n if (exactName) return exactName;\n\n // 4. Substring match on ID or path\n const subMatch = nodes.find((n) => {\n const id = n.id.toLowerCase().replace(/\\\\/g, '/');\n const p = (n.path || n.file || '').toLowerCase().replace(/\\\\/g, '/');\n return id.includes(qNorm) || p.includes(qNorm);\n });\n if (subMatch) return subMatch;\n\n return null;\n}\n\n/**\n * Performs a bounded graph trace starting from a specified file or symbol node up to maxDepth.\n * Verifies every traversed node against the repository filesystem, marking unverified nodes\n * and edges as hypotheses.\n */\nexport async function traceGraphService(\n o: ServiceOptions,\n query: string,\n options: { depth?: number; maxNodes?: number } = {},\n): Promise<GraphTraceResult> {\n const ws = await requireWorkspace(o.cwd);\n const loaded = await loadGraphData(ws.root);\n\n const depthLimit = Math.min(Math.max(options.depth ?? 2, 1), 10);\n const maxNodes = Math.min(options.maxNodes ?? 150, 500);\n\n if (!loaded.exists || !loaded.data) {\n return {\n available: false,\n query,\n depth: depthLimit,\n matchedNode: null,\n totalNodes: 0,\n verifiedNodes: 0,\n hypothesisNodes: 0,\n nodes: [],\n edges: [],\n warnings: ['No Graphify graph found at graphify-out/graph.json. Run \"/graphify .\" in Claude Code.'],\n };\n }\n\n const { nodes, edges } = loaded.data;\n const startNode = findMatchingStartNode(nodes, query);\n\n if (!startNode) {\n return {\n available: true,\n query,\n depth: depthLimit,\n matchedNode: null,\n totalNodes: 0,\n verifiedNodes: 0,\n hypothesisNodes: 0,\n nodes: [],\n edges: [],\n warnings: [`No graph node found matching \"${query}\". Check node IDs with \"devworkspace graph status\".`],\n };\n }\n\n // Build adjacency index\n const outgoing = new Map<string, RawGraphEdge[]>();\n const incoming = new Map<string, RawGraphEdge[]>();\n\n for (const e of edges) {\n if (!outgoing.has(e.source)) outgoing.set(e.source, []);\n outgoing.get(e.source)!.push(e);\n\n if (!incoming.has(e.target)) incoming.set(e.target, []);\n incoming.get(e.target)!.push(e);\n }\n\n const nodeMap = new Map<string, RawGraphNode>(nodes.map((n) => [n.id, n]));\n\n // Breadth-first search bounded by depthLimit\n const visitedDepths = new Map<string, number>();\n const queue: { id: string; depth: number }[] = [{ id: startNode.id, depth: 0 }];\n visitedDepths.set(startNode.id, 0);\n\n const collectedEdges: RawGraphEdge[] = [];\n\n while (queue.length > 0 && visitedDepths.size < maxNodes) {\n const current = queue.shift()!;\n if (current.depth >= depthLimit) continue;\n\n // Explore outgoing edges\n const outEdges = outgoing.get(current.id) || [];\n for (const edge of outEdges) {\n collectedEdges.push(edge);\n if (!visitedDepths.has(edge.target)) {\n visitedDepths.set(edge.target, current.depth + 1);\n queue.push({ id: edge.target, depth: current.depth + 1 });\n }\n }\n\n // Explore incoming edges\n const inEdges = incoming.get(current.id) || [];\n for (const edge of inEdges) {\n collectedEdges.push(edge);\n if (!visitedDepths.has(edge.source)) {\n visitedDepths.set(edge.source, current.depth + 1);\n queue.push({ id: edge.source, depth: current.depth + 1 });\n }\n }\n }\n\n // Verify all traversed nodes against disk\n const tracedNodes: TracedNode[] = [];\n const nodeStatusMap = new Map<string, NodeVerificationStatus>();\n\n for (const [nodeId, depth] of visitedDepths.entries()) {\n const rawNode = nodeMap.get(nodeId) || { id: nodeId };\n const verified = await verifyNode(ws.root, rawNode, depth);\n tracedNodes.push(verified);\n nodeStatusMap.set(nodeId, verified.status);\n }\n\n // Deduplicate and verify collected edges\n const edgeKeys = new Set<string>();\n const tracedEdges: TracedEdge[] = [];\n\n for (const e of collectedEdges) {\n if (!visitedDepths.has(e.source) || !visitedDepths.has(e.target)) continue;\n const key = `${e.source}->${e.target}::${e.relation || e.type || ''}`;\n if (edgeKeys.has(key)) continue;\n edgeKeys.add(key);\n\n const srcStatus = nodeStatusMap.get(e.source);\n const tgtStatus = nodeStatusMap.get(e.target);\n const edgeStatus: NodeVerificationStatus = srcStatus === 'verified' && tgtStatus === 'verified' ? 'verified' : 'hypothesis';\n\n tracedEdges.push({\n source: e.source,\n target: e.target,\n relation: typeof e.relation === 'string' ? e.relation : typeof e.type === 'string' ? e.type : 'relates_to',\n status: edgeStatus,\n });\n }\n\n // Sort nodes by depth, then by path/name\n tracedNodes.sort((a, b) => a.depth - b.depth || (a.path || a.id).localeCompare(b.path || b.id));\n\n const matched = tracedNodes.find((n) => n.id === startNode.id) || null;\n const verifiedCount = tracedNodes.filter((n) => n.status === 'verified').length;\n const hypothesisCount = tracedNodes.filter((n) => n.status === 'hypothesis').length;\n\n const warnings: string[] = [];\n if (hypothesisCount > 0) {\n warnings.push(`${hypothesisCount} node(s) or connected dependencies are unverified hypotheses (files or symbols missing on disk).`);\n }\n\n return {\n available: true,\n query,\n depth: depthLimit,\n matchedNode: matched,\n totalNodes: tracedNodes.length,\n verifiedNodes: verifiedCount,\n hypothesisNodes: hypothesisCount,\n nodes: tracedNodes,\n edges: tracedEdges,\n warnings,\n };\n}\n\n"]}
|
package/dist/services/refresh.js
CHANGED
|
@@ -4,6 +4,8 @@ import { loadProject } from '../core/workspace.js';
|
|
|
4
4
|
import { readVersioned, writeVersioned } from '../core/versioned.js';
|
|
5
5
|
import { packageVersion } from '../core/version.js';
|
|
6
6
|
import { applyStaleness, evaluateStaleness, loadFacts, saveFacts } from '../knowledge/facts.js';
|
|
7
|
+
import { loadFeatures, saveFeatures } from './features.js';
|
|
8
|
+
import { validateFeature } from '../features/validate.js';
|
|
7
9
|
import { buildRefreshPrompt } from '../prompts/refresh.js';
|
|
8
10
|
import { pathExists, sha256Text, writeJsonIfChanged, writeTextIfChanged } from '../utils/fs.js';
|
|
9
11
|
import { applyDetectorFacts, runDiscovery, writeCommands } from './analysis.js';
|
|
@@ -42,6 +44,19 @@ export async function refreshWorkspace(o) {
|
|
|
42
44
|
const facts = await loadFacts(ws);
|
|
43
45
|
const rep = await applyStaleness(ws.root, facts, now);
|
|
44
46
|
await saveFacts(ws, facts);
|
|
47
|
+
if (await pathExists(ws.features)) {
|
|
48
|
+
try {
|
|
49
|
+
const featuresFile = await loadFeatures(ws);
|
|
50
|
+
for (const feat of featuresFile.features) {
|
|
51
|
+
const checked = await validateFeature(ws.root, feat);
|
|
52
|
+
feat.status = checked.status;
|
|
53
|
+
}
|
|
54
|
+
await saveFeatures(ws, featuresFile);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// preserve existing features on error
|
|
58
|
+
}
|
|
59
|
+
}
|
|
45
60
|
const stale = facts.facts.filter((f) => f.status === 'STALE' && f.sourceType !== 'detector').map((f) => ({ key: f.key, value: f.value, reason: f.staleReason ?? 'evidence changed' }));
|
|
46
61
|
let prompt = null;
|
|
47
62
|
if (stale.length > 0 || rep.needsReview.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/services/refresh.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { SCHEMA_VERSION, validateAnalysisState, type AnalysisState } from '../config/schemas.js';\nimport { loadProject } from '../core/workspace.js';\nimport { readVersioned, writeVersioned } from '../core/versioned.js';\nimport { packageVersion } from '../core/version.js';\nimport { applyStaleness, evaluateStaleness, loadFacts, saveFacts } from '../knowledge/facts.js';\nimport { buildRefreshPrompt } from '../prompts/refresh.js';\nimport { pathExists, sha256Text, writeJsonIfChanged, writeTextIfChanged } from '../utils/fs.js';\nimport { applyDetectorFacts, runDiscovery, writeCommands } from './analysis.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport interface RefreshResult {\n dryRun: boolean;\n headChanged: boolean;\n autoRefreshed: string[];\n retired: string[];\n stale: { key: string; value: string; reason: string }[];\n needsReview: { key: string; reason: string }[];\n prompt: string | null;\n}\n\n/**\n * Incremental refresh. Deterministic facts are re-derived on the spot; everything else is re-hashed against its cited\n * sources and only the facts whose evidence changed become STALE and enter the (optional) agent refresh prompt.\n */\nexport async function refreshWorkspace(o: ServiceOptions): Promise<RefreshResult> {\n return mutate(o, async (ws) => {\n const project = await loadProject(ws);\n const now = isoNow(o);\n const run = await runDiscovery(ws, project.name, project.settings.maxFiles, { writeCache: !o.dryRun });\n let previous: AnalysisState | null = null;\n if (await pathExists(ws.analysisState)) {\n previous = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data;\n }\n const headChanged = !!previous && previous.gitHead !== run.gitHead;\n\n if (o.dryRun) {\n const facts = await loadFacts(ws);\n const view = await evaluateStaleness(ws.root, facts, now);\n const valueOf = (key: string): string => facts.facts.find((f) => f.key === key)?.value ?? '';\n return {\n dryRun: true,\n headChanged,\n autoRefreshed: [],\n retired: [],\n stale: [...view.recorded, ...view.detected].map((s) => ({ ...s, value: valueOf(s.key) })),\n needsReview: view.needsReview,\n prompt: null,\n };\n }\n\n await writeJsonIfChanged(ws.discovery, run.discovery);\n await writeCommands(ws, run.discovery.commands, { truncated: run.discovery.scan.truncated });\n const det = await applyDetectorFacts(ws, run.discovery, now);\n\n const facts = await loadFacts(ws);\n const rep = await applyStaleness(ws.root, facts, now);\n await saveFacts(ws, facts);\n\n const stale = facts.facts.filter((f) => f.status === 'STALE' && f.sourceType !== 'detector').map((f) => ({ key: f.key, value: f.value, reason: f.staleReason ?? 'evidence changed' }));\n let prompt: string | null = null;\n if (stale.length > 0 || rep.needsReview.length > 0) {\n await writeTextIfChanged(\n ws.refreshPrompt,\n buildRefreshPrompt({ projectName: project.name, stale, needsReview: rep.needsReview, autoRefreshed: det.written, cliVersion: packageVersion() }),\n );\n prompt = path.relative(ws.root, ws.refreshPrompt).replace(/\\\\/g, '/');\n }\n\n const state: AnalysisState = {\n schemaVersion: SCHEMA_VERSION,\n analyzedAt: previous?.analyzedAt ?? now,\n refreshedAt: now,\n gitHead: run.gitHead,\n fileCount: run.discovery.scan.fileCount,\n truncated: run.discovery.scan.truncated,\n discoveryHash: sha256Text(JSON.stringify(run.discovery)),\n };\n await writeVersioned(ws, ws.analysisState, state);\n return { dryRun: false, headChanged, autoRefreshed: det.written, retired: det.retired, stale, needsReview: rep.needsReview, prompt };\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/services/refresh.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { SCHEMA_VERSION, validateAnalysisState, type AnalysisState } from '../config/schemas.js';\nimport { loadProject } from '../core/workspace.js';\nimport { readVersioned, writeVersioned } from '../core/versioned.js';\nimport { packageVersion } from '../core/version.js';\nimport { applyStaleness, evaluateStaleness, loadFacts, saveFacts } from '../knowledge/facts.js';\nimport { loadFeatures, saveFeatures } from './features.js';\nimport { validateFeature } from '../features/validate.js';\nimport { buildRefreshPrompt } from '../prompts/refresh.js';\nimport { pathExists, sha256Text, writeJsonIfChanged, writeTextIfChanged } from '../utils/fs.js';\nimport { applyDetectorFacts, runDiscovery, writeCommands } from './analysis.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport interface RefreshResult {\n dryRun: boolean;\n headChanged: boolean;\n autoRefreshed: string[];\n retired: string[];\n stale: { key: string; value: string; reason: string }[];\n needsReview: { key: string; reason: string }[];\n prompt: string | null;\n}\n\n/**\n * Incremental refresh. Deterministic facts are re-derived on the spot; everything else is re-hashed against its cited\n * sources and only the facts whose evidence changed become STALE and enter the (optional) agent refresh prompt.\n */\nexport async function refreshWorkspace(o: ServiceOptions): Promise<RefreshResult> {\n return mutate(o, async (ws) => {\n const project = await loadProject(ws);\n const now = isoNow(o);\n const run = await runDiscovery(ws, project.name, project.settings.maxFiles, { writeCache: !o.dryRun });\n let previous: AnalysisState | null = null;\n if (await pathExists(ws.analysisState)) {\n previous = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data;\n }\n const headChanged = !!previous && previous.gitHead !== run.gitHead;\n\n if (o.dryRun) {\n const facts = await loadFacts(ws);\n const view = await evaluateStaleness(ws.root, facts, now);\n const valueOf = (key: string): string => facts.facts.find((f) => f.key === key)?.value ?? '';\n return {\n dryRun: true,\n headChanged,\n autoRefreshed: [],\n retired: [],\n stale: [...view.recorded, ...view.detected].map((s) => ({ ...s, value: valueOf(s.key) })),\n needsReview: view.needsReview,\n prompt: null,\n };\n }\n\n await writeJsonIfChanged(ws.discovery, run.discovery);\n await writeCommands(ws, run.discovery.commands, { truncated: run.discovery.scan.truncated });\n const det = await applyDetectorFacts(ws, run.discovery, now);\n\n const facts = await loadFacts(ws);\n const rep = await applyStaleness(ws.root, facts, now);\n await saveFacts(ws, facts);\n\n if (await pathExists(ws.features)) {\n try {\n const featuresFile = await loadFeatures(ws);\n for (const feat of featuresFile.features) {\n const checked = await validateFeature(ws.root, feat);\n feat.status = checked.status;\n }\n await saveFeatures(ws, featuresFile);\n } catch {\n // preserve existing features on error\n }\n }\n\n const stale = facts.facts.filter((f) => f.status === 'STALE' && f.sourceType !== 'detector').map((f) => ({ key: f.key, value: f.value, reason: f.staleReason ?? 'evidence changed' }));\n let prompt: string | null = null;\n if (stale.length > 0 || rep.needsReview.length > 0) {\n await writeTextIfChanged(\n ws.refreshPrompt,\n buildRefreshPrompt({ projectName: project.name, stale, needsReview: rep.needsReview, autoRefreshed: det.written, cliVersion: packageVersion() }),\n );\n prompt = path.relative(ws.root, ws.refreshPrompt).replace(/\\\\/g, '/');\n }\n\n const state: AnalysisState = {\n schemaVersion: SCHEMA_VERSION,\n analyzedAt: previous?.analyzedAt ?? now,\n refreshedAt: now,\n gitHead: run.gitHead,\n fileCount: run.discovery.scan.fileCount,\n truncated: run.discovery.scan.truncated,\n discoveryHash: sha256Text(JSON.stringify(run.discovery)),\n };\n await writeVersioned(ws, ws.analysisState, state);\n return { dryRun: false, headChanged, autoRefreshed: det.written, retired: det.retired, stale, needsReview: rep.needsReview, prompt };\n });\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ServiceOptions } from './common.js';
|
|
2
|
+
import type { ContractDriftItem } from '../discovery/commands.js';
|
|
3
|
+
export type CliExecutionMode = 'source-checkout' | 'installed-package' | 'npx-cache';
|
|
4
|
+
export interface CliProvenance {
|
|
5
|
+
version: string;
|
|
6
|
+
path: string;
|
|
7
|
+
mode: CliExecutionMode;
|
|
8
|
+
}
|
|
9
|
+
export declare function detectCliProvenance(): CliProvenance;
|
|
10
|
+
export interface ProjectSetupResult {
|
|
11
|
+
root: string;
|
|
12
|
+
os: string;
|
|
13
|
+
nodeVersion: string;
|
|
14
|
+
cli: CliProvenance;
|
|
15
|
+
initialized: boolean;
|
|
16
|
+
isFirstRun: boolean;
|
|
17
|
+
projectName: string;
|
|
18
|
+
packages: {
|
|
19
|
+
path: string;
|
|
20
|
+
ecosystem: string;
|
|
21
|
+
packageManager?: string;
|
|
22
|
+
}[];
|
|
23
|
+
frameworks: string[];
|
|
24
|
+
verification: {
|
|
25
|
+
total: number;
|
|
26
|
+
available: number;
|
|
27
|
+
checks: string[];
|
|
28
|
+
drift: ContractDriftItem[];
|
|
29
|
+
};
|
|
30
|
+
features: {
|
|
31
|
+
present: boolean;
|
|
32
|
+
total: number;
|
|
33
|
+
verified: number;
|
|
34
|
+
unknown: number;
|
|
35
|
+
} | null;
|
|
36
|
+
integrations: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
status: string;
|
|
40
|
+
required: boolean;
|
|
41
|
+
}[];
|
|
42
|
+
doctor: {
|
|
43
|
+
healthy: boolean;
|
|
44
|
+
passed: number;
|
|
45
|
+
warnings: number;
|
|
46
|
+
failures: number;
|
|
47
|
+
};
|
|
48
|
+
nextSteps: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Single-command setup workflow.
|
|
52
|
+
* Idempotently inspects repository, initializes or safely updates .devworkspace/,
|
|
53
|
+
* discovers packages & commands, checks for drift, validates feature map if present,
|
|
54
|
+
* observes optional integrations, and runs safe diagnostics.
|
|
55
|
+
*/
|
|
56
|
+
export declare function setupProject(o: ServiceOptions): Promise<ProjectSetupResult>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { packageVersion } from '../core/version.js';
|
|
4
|
+
import { findWorkspaceRoot, loadProject, wsPaths } from '../core/workspace.js';
|
|
5
|
+
import { detectContractDrift } from '../discovery/commands.js';
|
|
6
|
+
import { inspectAll } from '../integrations/index.js';
|
|
7
|
+
import { pathExists } from '../utils/fs.js';
|
|
8
|
+
import { analyzeWorkspace } from './analysis.js';
|
|
9
|
+
import { managerEnv } from './common.js';
|
|
10
|
+
import { runDoctor } from './doctor.js';
|
|
11
|
+
import { loadFeatures } from './features.js';
|
|
12
|
+
import { initWorkspace } from './init.js';
|
|
13
|
+
import { validateFeatureMap } from '../features/validate.js';
|
|
14
|
+
export function detectCliProvenance() {
|
|
15
|
+
const cliPath = process.argv[1] ? path.resolve(process.argv[1]) : 'unknown';
|
|
16
|
+
const norm = cliPath.replace(/\\/g, '/');
|
|
17
|
+
let mode = 'installed-package';
|
|
18
|
+
if (norm.includes('/_npx/') || norm.includes('\\_npx\\')) {
|
|
19
|
+
mode = 'npx-cache';
|
|
20
|
+
}
|
|
21
|
+
else if (norm.includes('/devworkspace/dist/') || norm.endsWith('/dist/cli/main.js') || norm.endsWith('/src/cli/main.ts')) {
|
|
22
|
+
mode = 'source-checkout';
|
|
23
|
+
}
|
|
24
|
+
return { version: packageVersion(), path: cliPath, mode };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Single-command setup workflow.
|
|
28
|
+
* Idempotently inspects repository, initializes or safely updates .devworkspace/,
|
|
29
|
+
* discovers packages & commands, checks for drift, validates feature map if present,
|
|
30
|
+
* observes optional integrations, and runs safe diagnostics.
|
|
31
|
+
*/
|
|
32
|
+
export async function setupProject(o) {
|
|
33
|
+
const startDir = o.cwd ? path.resolve(o.cwd) : process.cwd();
|
|
34
|
+
const existingRoot = await findWorkspaceRoot(startDir);
|
|
35
|
+
const root = existingRoot ?? startDir;
|
|
36
|
+
const isFirstRun = !existingRoot;
|
|
37
|
+
const cli = detectCliProvenance();
|
|
38
|
+
// 1. Initialize workspace non-destructively
|
|
39
|
+
await initWorkspace({ ...o, cwd: root, root });
|
|
40
|
+
// 2. Discover packages, commands, frameworks, entrypoints, and derive detector facts
|
|
41
|
+
const ws = wsPaths(root);
|
|
42
|
+
const analysis = await analyzeWorkspace(ws, { ...o, cwd: root });
|
|
43
|
+
// 3. Inspect configured verification commands and detect contract drift
|
|
44
|
+
const driftReport = await detectContractDrift(ws.root, analysis.discovery.commands);
|
|
45
|
+
const availableChecks = analysis.discovery.commands.filter((c) => c.available);
|
|
46
|
+
// 4. Validate feature map if present
|
|
47
|
+
let featuresSummary = null;
|
|
48
|
+
if (await pathExists(ws.features)) {
|
|
49
|
+
try {
|
|
50
|
+
const f = await loadFeatures(ws);
|
|
51
|
+
const rep = await validateFeatureMap(ws.root, f.features);
|
|
52
|
+
featuresSummary = {
|
|
53
|
+
present: true,
|
|
54
|
+
total: rep.total,
|
|
55
|
+
verified: rep.verified,
|
|
56
|
+
unknown: rep.unknown,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
featuresSummary = { present: true, total: 0, verified: 0, unknown: 0 };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// 5. Shallowly observe optional integrations (Graphify, Agent Skills, Ponytail, OmniRoute)
|
|
64
|
+
const integrationsReports = await inspectAll(ws, managerEnv(o, root), { deep: false });
|
|
65
|
+
const integrations = integrationsReports.map((r) => ({
|
|
66
|
+
id: r.id,
|
|
67
|
+
name: r.name,
|
|
68
|
+
status: r.status,
|
|
69
|
+
required: r.required,
|
|
70
|
+
}));
|
|
71
|
+
// 6. Run doctor diagnostics
|
|
72
|
+
const doctorResult = await runDoctor({ ...o, cwd: root });
|
|
73
|
+
// 7. Compile next steps
|
|
74
|
+
const nextSteps = [];
|
|
75
|
+
if (driftReport.hasDrift) {
|
|
76
|
+
nextSteps.push('Review verification contract drift: run "devworkspace refresh" or edit .devworkspace/context/commands.json.');
|
|
77
|
+
}
|
|
78
|
+
const missingRequired = integrationsReports.filter((r) => r.required && r.status === 'NOT_INSTALLED');
|
|
79
|
+
if (missingRequired.length > 0) {
|
|
80
|
+
nextSteps.push(`Optional integrations: run "devworkspace setup --integrations --yes" to install ${missingRequired.map((m) => m.name).join(', ')}.`);
|
|
81
|
+
}
|
|
82
|
+
nextSteps.push('Check overall workspace health: run "devworkspace status".');
|
|
83
|
+
nextSteps.push('Start your first engineering task: run "devworkspace task new <request>".');
|
|
84
|
+
const project = await loadProject(ws);
|
|
85
|
+
return {
|
|
86
|
+
root,
|
|
87
|
+
os: `${os.type()} ${os.release()} (${process.platform})`,
|
|
88
|
+
nodeVersion: process.versions.node,
|
|
89
|
+
cli,
|
|
90
|
+
initialized: true,
|
|
91
|
+
isFirstRun,
|
|
92
|
+
projectName: project.name,
|
|
93
|
+
packages: analysis.discovery.monorepo.packages.map((p) => ({
|
|
94
|
+
path: p.path,
|
|
95
|
+
ecosystem: p.ecosystem,
|
|
96
|
+
packageManager: p.packageManager,
|
|
97
|
+
})),
|
|
98
|
+
frameworks: analysis.discovery.frameworks.map((f) => f.name),
|
|
99
|
+
verification: {
|
|
100
|
+
total: analysis.discovery.commands.length,
|
|
101
|
+
available: availableChecks.length,
|
|
102
|
+
checks: [...new Set(availableChecks.map((c) => c.id))],
|
|
103
|
+
drift: driftReport.issues,
|
|
104
|
+
},
|
|
105
|
+
features: featuresSummary,
|
|
106
|
+
integrations,
|
|
107
|
+
doctor: {
|
|
108
|
+
healthy: doctorResult.healthy,
|
|
109
|
+
passed: doctorResult.counts.pass,
|
|
110
|
+
warnings: doctorResult.counts.warn,
|
|
111
|
+
failures: doctorResult.counts.fail,
|
|
112
|
+
},
|
|
113
|
+
nextSteps,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=setup.js.map
|