devworkspace 0.3.0 → 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.
Files changed (119) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +8 -5
  3. package/dist/agents/claude/adapter.d.ts +3 -14
  4. package/dist/agents/claude/adapter.js +46 -49
  5. package/dist/agents/claude/adapter.js.map +1 -1
  6. package/dist/agents/codex/adapter.d.ts +2 -0
  7. package/dist/agents/codex/adapter.js +61 -0
  8. package/dist/agents/codex/adapter.js.map +1 -0
  9. package/dist/agents/copilot/adapter.d.ts +2 -0
  10. package/dist/agents/copilot/adapter.js +62 -0
  11. package/dist/agents/copilot/adapter.js.map +1 -0
  12. package/dist/agents/cursor/adapter.d.ts +2 -0
  13. package/dist/agents/cursor/adapter.js +127 -0
  14. package/dist/agents/cursor/adapter.js.map +1 -0
  15. package/dist/agents/index.d.ts +15 -0
  16. package/dist/agents/index.js +54 -0
  17. package/dist/agents/index.js.map +1 -0
  18. package/dist/agents/shared.d.ts +20 -0
  19. package/dist/agents/shared.js +76 -0
  20. package/dist/agents/shared.js.map +1 -0
  21. package/dist/agents/types.d.ts +16 -1
  22. package/dist/agents/types.js.map +1 -1
  23. package/dist/cli/args.d.ts +42 -0
  24. package/dist/cli/args.js +12 -0
  25. package/dist/cli/args.js.map +1 -1
  26. package/dist/cli/help.js +64 -10
  27. package/dist/cli/help.js.map +1 -1
  28. package/dist/cli/render.d.ts +12 -1
  29. package/dist/cli/render.js +188 -0
  30. package/dist/cli/render.js.map +1 -1
  31. package/dist/cli/run.js +100 -9
  32. package/dist/cli/run.js.map +1 -1
  33. package/dist/config/schemas.d.ts +6 -0
  34. package/dist/config/schemas.js +6 -0
  35. package/dist/config/schemas.js.map +1 -1
  36. package/dist/core/version.d.ts +7 -0
  37. package/dist/core/version.js +13 -0
  38. package/dist/core/version.js.map +1 -1
  39. package/dist/core/workspace.d.ts +1 -0
  40. package/dist/core/workspace.js +1 -0
  41. package/dist/core/workspace.js.map +1 -1
  42. package/dist/discovery/commands.d.ts +1 -0
  43. package/dist/discovery/commands.js +3 -1
  44. package/dist/discovery/commands.js.map +1 -1
  45. package/dist/features/schema.d.ts +3 -0
  46. package/dist/features/schema.js +79 -0
  47. package/dist/features/schema.js.map +1 -0
  48. package/dist/features/types.d.ts +77 -0
  49. package/dist/features/types.js +2 -0
  50. package/dist/features/types.js.map +1 -0
  51. package/dist/features/validate.d.ts +14 -0
  52. package/dist/features/validate.js +124 -0
  53. package/dist/features/validate.js.map +1 -0
  54. package/dist/index.d.ts +8 -2
  55. package/dist/index.js +4 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/integrations/adapters/claudePlugin.d.ts +12 -0
  58. package/dist/integrations/adapters/claudePlugin.js +37 -8
  59. package/dist/integrations/adapters/claudePlugin.js.map +1 -1
  60. package/dist/integrations/adapters/pluginSkill.js +5 -0
  61. package/dist/integrations/adapters/pluginSkill.js.map +1 -1
  62. package/dist/security/files.js +10 -2
  63. package/dist/security/files.js.map +1 -1
  64. package/dist/security/redact.js +1 -1
  65. package/dist/security/redact.js.map +1 -1
  66. package/dist/services/agent.d.ts +12 -2
  67. package/dist/services/agent.js +96 -14
  68. package/dist/services/agent.js.map +1 -1
  69. package/dist/services/analysis.js +3 -3
  70. package/dist/services/analysis.js.map +1 -1
  71. package/dist/services/doctor.js +65 -14
  72. package/dist/services/doctor.js.map +1 -1
  73. package/dist/services/drift.d.ts +21 -0
  74. package/dist/services/drift.js +89 -0
  75. package/dist/services/drift.js.map +1 -0
  76. package/dist/services/features.d.ts +22 -0
  77. package/dist/services/features.js +105 -0
  78. package/dist/services/features.js.map +1 -0
  79. package/dist/services/graph.d.ts +97 -0
  80. package/dist/services/graph.js +342 -0
  81. package/dist/services/graph.js.map +1 -0
  82. package/dist/services/refresh.js +15 -0
  83. package/dist/services/refresh.js.map +1 -1
  84. package/dist/services/reset.js +45 -5
  85. package/dist/services/reset.js.map +1 -1
  86. package/dist/services/setup.d.ts +56 -0
  87. package/dist/services/setup.js +116 -0
  88. package/dist/services/setup.js.map +1 -0
  89. package/dist/services/status.d.ts +8 -0
  90. package/dist/services/status.js +78 -10
  91. package/dist/services/status.js.map +1 -1
  92. package/dist/services/tasks.js +6 -1
  93. package/dist/services/tasks.js.map +1 -1
  94. package/dist/services/verify.d.ts +11 -0
  95. package/dist/services/verify.js +64 -2
  96. package/dist/services/verify.js.map +1 -1
  97. package/dist/tasks/routing.js +14 -10
  98. package/dist/tasks/routing.js.map +1 -1
  99. package/dist/tasks/task.js +6 -2
  100. package/dist/tasks/task.js.map +1 -1
  101. package/dist/verification/scope.js +1 -1
  102. package/dist/verification/scope.js.map +1 -1
  103. package/docs/agents.md +60 -0
  104. package/docs/architecture-report/01-executive-summary.md +168 -0
  105. package/docs/architecture-report/02-system-architecture.md +356 -0
  106. package/docs/architecture-report/03-tools-and-technologies.md +112 -0
  107. package/docs/architecture-report/04-execution-flows.md +257 -0
  108. package/docs/architecture-report/05-codebase-map.md +150 -0
  109. package/docs/architecture-report/06-command-reference.md +446 -0
  110. package/docs/architecture-report/07-data-flow-and-integrations.md +268 -0
  111. package/docs/architecture-report/08-build-test-deployment.md +185 -0
  112. package/docs/architecture-report/09-security-and-reliability.md +165 -0
  113. package/docs/architecture-report/10-feature-status.md +110 -0
  114. package/docs/architecture-report/11-glossary.md +97 -0
  115. package/docs/architecture-report/README.md +103 -0
  116. package/docs/architecture-report/diagrams.md +446 -0
  117. package/docs/architecture.md +2 -2
  118. package/docs/getting-started.md +38 -1
  119. package/package.json +1 -1
@@ -0,0 +1,105 @@
1
+ import { FEATURE_SCHEMA_VERSION, validateFeatures } from '../features/schema.js';
2
+ import { readVersioned, writeVersioned } from '../core/versioned.js';
3
+ import { requireWorkspace } from '../core/workspace.js';
4
+ import { DevWorkspaceError } from '../errors/index.js';
5
+ import { pathExists } from '../utils/fs.js';
6
+ import { validateFeature, validateFeatureMap } from '../features/validate.js';
7
+ import { isoNow, mutate } from './common.js';
8
+ export async function loadFeatures(ws) {
9
+ if (!(await pathExists(ws.features))) {
10
+ return { schemaVersion: FEATURE_SCHEMA_VERSION, features: [] };
11
+ }
12
+ return (await readVersioned(ws.features, 'features', FEATURE_SCHEMA_VERSION, validateFeatures)).data;
13
+ }
14
+ export async function saveFeatures(ws, file) {
15
+ validateFeatures(file);
16
+ await writeVersioned(ws, ws.features, file);
17
+ }
18
+ function countComponents(f) {
19
+ let total = 0;
20
+ let tests = 0;
21
+ const countGroup = (g, isTest = false) => {
22
+ if (!g)
23
+ return;
24
+ for (const arr of Object.values(g)) {
25
+ if (Array.isArray(arr)) {
26
+ total += arr.length;
27
+ if (isTest)
28
+ tests += arr.length;
29
+ }
30
+ }
31
+ };
32
+ countGroup(f.frontend);
33
+ countGroup(f.backend);
34
+ countGroup(f.database);
35
+ countGroup(f.tests, true);
36
+ return { total, tests };
37
+ }
38
+ export async function listFeaturesService(o) {
39
+ const ws = await requireWorkspace(o.cwd);
40
+ const file = await loadFeatures(ws);
41
+ return file.features.map((f) => {
42
+ const counts = countComponents(f);
43
+ return {
44
+ id: f.id,
45
+ name: f.name,
46
+ ...(f.description ? { description: f.description } : {}),
47
+ status: f.status ?? 'inferred',
48
+ componentsCount: counts.total,
49
+ testsCount: counts.tests,
50
+ ...(f.tags ? { tags: f.tags } : {}),
51
+ updatedAt: f.updatedAt ?? '',
52
+ };
53
+ });
54
+ }
55
+ export async function getFeatureService(o, id) {
56
+ const ws = await requireWorkspace(o.cwd);
57
+ const file = await loadFeatures(ws);
58
+ const target = file.features.find((f) => f.id.toLowerCase() === id.toLowerCase());
59
+ if (!target) {
60
+ throw new DevWorkspaceError('NOT_FOUND', `Feature "${id}" not found in .devworkspace/context/features.json.`, {
61
+ hint: 'Run "devworkspace feature list" to see existing features, or "devworkspace feature add" to create one.',
62
+ });
63
+ }
64
+ return target;
65
+ }
66
+ export async function validateFeatureMapService(o) {
67
+ const ws = await requireWorkspace(o.cwd);
68
+ const file = await loadFeatures(ws);
69
+ return validateFeatureMap(ws.root, file.features);
70
+ }
71
+ export async function saveFeatureService(o, input) {
72
+ const ws = await requireWorkspace(o.cwd);
73
+ const now = isoNow(o);
74
+ const item = {
75
+ id: input.id.trim(),
76
+ name: input.name.trim(),
77
+ ...(input.description ? { description: input.description.trim() } : {}),
78
+ status: input.status ?? 'inferred',
79
+ origin: input.origin ?? 'user',
80
+ ...(input.frontend ? { frontend: input.frontend } : {}),
81
+ ...(input.backend ? { backend: input.backend } : {}),
82
+ ...(input.database ? { database: input.database } : {}),
83
+ ...(input.tests ? { tests: input.tests } : {}),
84
+ ...(input.tags ? { tags: input.tags } : {}),
85
+ updatedAt: now,
86
+ };
87
+ // Perform self-validation through the schema
88
+ validateFeatures({ schemaVersion: FEATURE_SCHEMA_VERSION, features: [item] });
89
+ // Validate components against the disk to assign accurate initial status
90
+ const checked = await validateFeature(ws.root, item);
91
+ item.status = checked.status;
92
+ await mutate(o, async (w) => {
93
+ const current = await loadFeatures(w);
94
+ const index = current.features.findIndex((f) => f.id.toLowerCase() === item.id.toLowerCase());
95
+ if (index >= 0) {
96
+ current.features[index] = item;
97
+ }
98
+ else {
99
+ current.features.push(item);
100
+ }
101
+ await saveFeatures(w, current);
102
+ });
103
+ return item;
104
+ }
105
+ //# sourceMappingURL=features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.js","sourceRoot":"","sources":["../../src/services/features.ts"],"names":[],"mappings":"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","sourcesContent":["import { FEATURE_SCHEMA_VERSION, validateFeatures } from '../features/schema.js';\nimport type { FeatureItem, FeaturesFile, FeatureValidationReport } from '../features/types.js';\nimport { readVersioned, writeVersioned } from '../core/versioned.js';\nimport { requireWorkspace, type Ws } from '../core/workspace.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { pathExists } from '../utils/fs.js';\nimport { validateFeature, validateFeatureMap } from '../features/validate.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport interface FeatureSummary {\n id: string;\n name: string;\n description?: string;\n status: string;\n componentsCount: number;\n testsCount: number;\n tags?: string[];\n updatedAt: string;\n}\n\nexport async function loadFeatures(ws: Ws): Promise<FeaturesFile> {\n if (!(await pathExists(ws.features))) {\n return { schemaVersion: FEATURE_SCHEMA_VERSION, features: [] };\n }\n return (await readVersioned(ws.features, 'features', FEATURE_SCHEMA_VERSION, validateFeatures)).data;\n}\n\nexport async function saveFeatures(ws: Ws, file: FeaturesFile): Promise<void> {\n validateFeatures(file);\n await writeVersioned(ws, ws.features, file);\n}\n\nfunction countComponents(f: FeatureItem): { total: number; tests: number } {\n let total = 0;\n let tests = 0;\n\n const countGroup = (g?: Record<string, unknown[] | undefined>, isTest = false): void => {\n if (!g) return;\n for (const arr of Object.values(g)) {\n if (Array.isArray(arr)) {\n total += arr.length;\n if (isTest) tests += arr.length;\n }\n }\n };\n\n countGroup(f.frontend);\n countGroup(f.backend);\n countGroup(f.database);\n countGroup(f.tests, true);\n\n return { total, tests };\n}\n\nexport async function listFeaturesService(o: ServiceOptions): Promise<FeatureSummary[]> {\n const ws = await requireWorkspace(o.cwd);\n const file = await loadFeatures(ws);\n return file.features.map((f) => {\n const counts = countComponents(f);\n return {\n id: f.id,\n name: f.name,\n ...(f.description ? { description: f.description } : {}),\n status: f.status ?? 'inferred',\n componentsCount: counts.total,\n testsCount: counts.tests,\n ...(f.tags ? { tags: f.tags } : {}),\n updatedAt: f.updatedAt ?? '',\n };\n });\n}\n\nexport async function getFeatureService(o: ServiceOptions, id: string): Promise<FeatureItem> {\n const ws = await requireWorkspace(o.cwd);\n const file = await loadFeatures(ws);\n const target = file.features.find((f) => f.id.toLowerCase() === id.toLowerCase());\n if (!target) {\n throw new DevWorkspaceError('NOT_FOUND', `Feature \"${id}\" not found in .devworkspace/context/features.json.`, {\n hint: 'Run \"devworkspace feature list\" to see existing features, or \"devworkspace feature add\" to create one.',\n });\n }\n return target;\n}\n\nexport async function validateFeatureMapService(o: ServiceOptions): Promise<FeatureValidationReport> {\n const ws = await requireWorkspace(o.cwd);\n const file = await loadFeatures(ws);\n return validateFeatureMap(ws.root, file.features);\n}\n\nexport async function saveFeatureService(o: ServiceOptions, input: Partial<FeatureItem> & { id: string; name: string }): Promise<FeatureItem> {\n const ws = await requireWorkspace(o.cwd);\n const now = isoNow(o);\n\n const item: FeatureItem = {\n id: input.id.trim(),\n name: input.name.trim(),\n ...(input.description ? { description: input.description.trim() } : {}),\n status: input.status ?? 'inferred',\n origin: input.origin ?? 'user',\n ...(input.frontend ? { frontend: input.frontend } : {}),\n ...(input.backend ? { backend: input.backend } : {}),\n ...(input.database ? { database: input.database } : {}),\n ...(input.tests ? { tests: input.tests } : {}),\n ...(input.tags ? { tags: input.tags } : {}),\n updatedAt: now,\n };\n\n // Perform self-validation through the schema\n validateFeatures({ schemaVersion: FEATURE_SCHEMA_VERSION, features: [item] });\n\n // Validate components against the disk to assign accurate initial status\n const checked = await validateFeature(ws.root, item);\n item.status = checked.status;\n\n await mutate(o, async (w) => {\n const current = await loadFeatures(w);\n const index = current.features.findIndex((f) => f.id.toLowerCase() === item.id.toLowerCase());\n if (index >= 0) {\n current.features[index] = item;\n } else {\n current.features.push(item);\n }\n await saveFeatures(w, current);\n });\n\n return item;\n}\n\n"]}
@@ -0,0 +1,97 @@
1
+ import type { ServiceOptions } from './common.js';
2
+ export type NodeVerificationStatus = 'verified' | 'hypothesis';
3
+ export interface RawGraphNode {
4
+ id: string;
5
+ name?: string;
6
+ label?: string;
7
+ type?: string;
8
+ path?: string;
9
+ file?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ export interface RawGraphEdge {
13
+ source: string;
14
+ target: string;
15
+ relation?: string;
16
+ type?: string;
17
+ label?: string;
18
+ weight?: number;
19
+ [key: string]: unknown;
20
+ }
21
+ export interface RawGraphData {
22
+ nodes?: RawGraphNode[];
23
+ edges?: RawGraphEdge[];
24
+ links?: RawGraphEdge[];
25
+ [key: string]: unknown;
26
+ }
27
+ export interface TracedNode {
28
+ id: string;
29
+ name: string;
30
+ type: string;
31
+ path: string | null;
32
+ symbol: string | null;
33
+ depth: number;
34
+ status: NodeVerificationStatus;
35
+ fileExists: boolean;
36
+ symbolFound: boolean | null;
37
+ reason?: string;
38
+ }
39
+ export interface TracedEdge {
40
+ source: string;
41
+ target: string;
42
+ relation: string;
43
+ status: NodeVerificationStatus;
44
+ }
45
+ export interface GraphStatusResult {
46
+ available: boolean;
47
+ graphPath: string;
48
+ nodesCount: number;
49
+ edgesCount: number;
50
+ nodeTypes: Record<string, number>;
51
+ message: string;
52
+ hint?: string;
53
+ }
54
+ export interface GraphTraceResult {
55
+ available: boolean;
56
+ query: string;
57
+ depth: number;
58
+ matchedNode: TracedNode | null;
59
+ totalNodes: number;
60
+ verifiedNodes: number;
61
+ hypothesisNodes: number;
62
+ nodes: TracedNode[];
63
+ edges: TracedEdge[];
64
+ warnings: string[];
65
+ }
66
+ /**
67
+ * Resolves the location of Graphify's output graph in the repository.
68
+ */
69
+ export declare function getGraphPath(root: string): string;
70
+ /**
71
+ * Loads and validates Graphify graph JSON data safely.
72
+ */
73
+ export declare function loadGraphData(root: string): Promise<{
74
+ exists: boolean;
75
+ path: string;
76
+ data?: {
77
+ nodes: RawGraphNode[];
78
+ edges: RawGraphEdge[];
79
+ };
80
+ }>;
81
+ /**
82
+ * Reports the presence, size, and basic schema health of the Graphify graph.
83
+ */
84
+ export declare function getGraphStatusService(o: ServiceOptions): Promise<GraphStatusResult>;
85
+ /**
86
+ * Finds the starting node in a graph that matches the user's query.
87
+ */
88
+ export declare function findMatchingStartNode(nodes: RawGraphNode[], query: string): RawGraphNode | null;
89
+ /**
90
+ * Performs a bounded graph trace starting from a specified file or symbol node up to maxDepth.
91
+ * Verifies every traversed node against the repository filesystem, marking unverified nodes
92
+ * and edges as hypotheses.
93
+ */
94
+ export declare function traceGraphService(o: ServiceOptions, query: string, options?: {
95
+ depth?: number;
96
+ maxNodes?: number;
97
+ }): Promise<GraphTraceResult>;
@@ -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"]}
@@ -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) {