archsync 1.0.0 → 1.0.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 (50) hide show
  1. package/README.md +67 -0
  2. package/dist/archsync.cjs +2 -0
  3. package/package.json +11 -7
  4. package/bin/cli.js +0 -91
  5. package/src/__tests__/e2e-workflow.test.js +0 -66
  6. package/src/__tests__/hashEngine.test.js +0 -109
  7. package/src/__tests__/impact.test.js +0 -137
  8. package/src/__tests__/parsers.test.js +0 -496
  9. package/src/__tests__/scan-pipeline.test.js +0 -332
  10. package/src/__tests__/schemaBuilder.test.js +0 -145
  11. package/src/__tests__/workspace.test.js +0 -178
  12. package/src/commands/backup.js +0 -54
  13. package/src/commands/connect.js +0 -129
  14. package/src/commands/diff.js +0 -228
  15. package/src/commands/export.js +0 -125
  16. package/src/commands/impactReport.js +0 -50
  17. package/src/commands/import.js +0 -126
  18. package/src/commands/init.js +0 -80
  19. package/src/commands/login.js +0 -116
  20. package/src/commands/plugin.js +0 -28
  21. package/src/commands/push.js +0 -194
  22. package/src/commands/register.js +0 -127
  23. package/src/commands/scan.js +0 -498
  24. package/src/commands/serve.js +0 -133
  25. package/src/commands/setup.js +0 -233
  26. package/src/commands/status.js +0 -56
  27. package/src/commands/validate.js +0 -245
  28. package/src/commands/watch.js +0 -70
  29. package/src/core/credentialStore.js +0 -76
  30. package/src/core/hashEngine.js +0 -34
  31. package/src/core/impactEngine.js +0 -192
  32. package/src/core/monorepoDetector.js +0 -41
  33. package/src/core/pluginManager.js +0 -40
  34. package/src/core/relationshipEngine.js +0 -917
  35. package/src/core/requestSigning.js +0 -16
  36. package/src/core/schemaBuilder.js +0 -230
  37. package/src/core/schemaDeduplicator.js +0 -54
  38. package/src/core/supabaseClient.js +0 -68
  39. package/src/core/workspaceDetector.js +0 -113
  40. package/src/parsers/astParser.js +0 -274
  41. package/src/parsers/configParser.js +0 -49
  42. package/src/parsers/dependencyGraph.js +0 -31
  43. package/src/parsers/flutterParser.js +0 -98
  44. package/src/parsers/goParser.js +0 -99
  45. package/src/parsers/index.js +0 -211
  46. package/src/parsers/javaParser.js +0 -89
  47. package/src/parsers/nodeParser.js +0 -429
  48. package/src/parsers/pythonParser.js +0 -109
  49. package/src/parsers/reactParser.js +0 -368
  50. package/src/parsers/smartComment.js +0 -144
@@ -1,34 +0,0 @@
1
- import crypto from 'crypto';
2
-
3
- // Create a deterministic hash of a node's meaningful content
4
- export function hashNode(node) {
5
- const content = JSON.stringify({
6
- entityType: node.entityType,
7
- system: node.system,
8
- text: node.text,
9
- data: node.data || {},
10
- metadata: node.metadata || {},
11
- });
12
- return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
13
- }
14
-
15
- // Create hash for full schema
16
- export function hashSchema(schema) {
17
- const nodeHashes = (schema.nodes || []).map(n => hashNode(n)).sort();
18
- const edgeHashes = (schema.edges || []).map(e => {
19
- return crypto.createHash('sha256')
20
- .update(`${e.source}-${e.relation}-${e.target}`)
21
- .digest('hex').slice(0, 16);
22
- }).sort();
23
-
24
- return crypto.createHash('sha256')
25
- .update(JSON.stringify({ nodeHashes, edgeHashes }))
26
- .digest('hex');
27
- }
28
-
29
- // Check if two schemas are identical
30
- export function schemasEqual(a, b) {
31
- if (!a && !b) return true;
32
- if (!a || !b) return false;
33
- return hashSchema(a) === hashSchema(b);
34
- }
@@ -1,192 +0,0 @@
1
- /**
2
- * Impact Engine — breaking-change detection and dependency impact analysis.
3
- *
4
- * Given two schema snapshots (before / after), it:
5
- * 1. Diffs nodes and classifies changes as breaking or non-breaking.
6
- * A change is breaking when other entities depend on the changed node:
7
- * removing a route someone calls, changing its method/path, removing a
8
- * model a service queries, etc.
9
- * 2. Walks the dependency graph BACKWARDS (who depends on me?) to find
10
- * every transitively affected node — across systems, so deleting a
11
- * backend route flags the Flutter screen that calls it.
12
- *
13
- * Nothing here is entity-type- or system-name-specific: dependents are
14
- * derived purely from the edges in the schema, so any project shape works.
15
- *
16
- * The output is embedded into the schema (`schema.warnings`) on push so the
17
- * canvas can show the same warnings in the affected areas.
18
- */
19
-
20
- // Fields inside node.data whose change breaks the node's public contract.
21
- // Everything else in data (positions, parse metadata, …) is cosmetic.
22
- const CONTRACT_FIELDS = ['method', 'path', 'fullPath', 'prefix', 'parentClass', 'orm', 'kind'];
23
-
24
- function contractOf(node) {
25
- const data = node.data || {};
26
- const picked = {};
27
- for (const f of CONTRACT_FIELDS) {
28
- if (data[f] !== undefined) picked[f] = data[f];
29
- }
30
- return JSON.stringify({ text: node.text, entityType: node.entityType, ...picked });
31
- }
32
-
33
- /** Build reverse adjacency: nodeId → [{ sourceId, relation }] of nodes that depend on it. */
34
- function buildReverseIndex(schema) {
35
- const incoming = new Map();
36
- for (const edge of schema.edges || []) {
37
- if (!incoming.has(edge.target)) incoming.set(edge.target, []);
38
- incoming.get(edge.target).push({ sourceId: edge.source, relation: edge.relation });
39
- }
40
- return incoming;
41
- }
42
-
43
- /**
44
- * All transitive dependents of `nodeId`, breadth-first, with the dependency
45
- * depth (1 = direct caller) and the relation that links each hop.
46
- */
47
- export function findDependents(schema, nodeId, { maxDepth = 10 } = {}) {
48
- const incoming = buildReverseIndex(schema);
49
- const byId = new Map((schema.nodes || []).map(n => [n.id, n]));
50
- const visited = new Set([nodeId]);
51
- const result = [];
52
-
53
- let frontier = [nodeId];
54
- for (let depth = 1; depth <= maxDepth && frontier.length; depth++) {
55
- const next = [];
56
- for (const id of frontier) {
57
- for (const { sourceId, relation } of incoming.get(id) || []) {
58
- if (visited.has(sourceId)) continue;
59
- visited.add(sourceId);
60
- const node = byId.get(sourceId);
61
- if (!node) continue;
62
- result.push({
63
- id: node.id,
64
- text: node.text,
65
- entityType: node.entityType,
66
- system: node.system,
67
- relation,
68
- depth,
69
- });
70
- next.push(sourceId);
71
- }
72
- }
73
- frontier = next;
74
- }
75
- return result;
76
- }
77
-
78
- /**
79
- * Compare two schemas and produce structured warnings for changes that
80
- * affect dependents.
81
- *
82
- * @param {object|null} oldSchema — previous snapshot (local cache or remote)
83
- * @param {object} newSchema — freshly scanned schema
84
- * @return {Array} warnings:
85
- * { severity: 'breaking'|'warning',
86
- * changeKind: 'removed'|'modified',
87
- * nodeId, nodeText, entityType, system,
88
- * message,
89
- * affected: [{ id, text, entityType, system, relation, depth }],
90
- * affectedSystems: string[],
91
- * affectedByType: { [entityType]: count } }
92
- */
93
- export function analyzeImpact(oldSchema, newSchema) {
94
- if (!oldSchema || !Array.isArray(oldSchema.nodes)) return [];
95
-
96
- const oldById = new Map(oldSchema.nodes.map(n => [n.id, n]));
97
- const newById = new Map((newSchema.nodes || []).map(n => [n.id, n]));
98
- const warnings = [];
99
-
100
- const pushWarning = (changeKind, oldNode, detail) => {
101
- // Dependents are computed on the OLD graph — that's where the
102
- // relationships to the removed/changed node still exist.
103
- const affected = findDependents(oldSchema, oldNode.id);
104
- if (affected.length === 0 && changeKind === 'modified') return; // nobody cares
105
- const affectedSystems = [...new Set(affected.map(a => a.system).filter(Boolean))];
106
- const affectedByType = {};
107
- for (const a of affected) {
108
- affectedByType[a.entityType] = (affectedByType[a.entityType] || 0) + 1;
109
- }
110
- warnings.push({
111
- severity: affected.length > 0 ? 'breaking' : 'warning',
112
- changeKind,
113
- nodeId: oldNode.id,
114
- nodeText: oldNode.text,
115
- entityType: oldNode.entityType,
116
- system: oldNode.system,
117
- message: detail,
118
- affected,
119
- affectedSystems,
120
- affectedByType,
121
- });
122
- };
123
-
124
- for (const [id, oldNode] of oldById) {
125
- const newNode = newById.get(id);
126
- if (!newNode) {
127
- pushWarning('removed', oldNode, `${oldNode.entityType} "${oldNode.text}" was removed`);
128
- continue;
129
- }
130
- if (contractOf(oldNode) !== contractOf(newNode)) {
131
- const oldC = JSON.parse(contractOf(oldNode));
132
- const newC = JSON.parse(contractOf(newNode));
133
- const changedFields = Object.keys({ ...oldC, ...newC })
134
- .filter(k => JSON.stringify(oldC[k]) !== JSON.stringify(newC[k]));
135
- pushWarning(
136
- 'modified',
137
- oldNode,
138
- `${oldNode.entityType} "${oldNode.text}" changed: ${changedFields.join(', ')}`
139
- );
140
- }
141
- }
142
-
143
- // Most impactful first
144
- warnings.sort((a, b) => b.affected.length - a.affected.length);
145
- return warnings;
146
- }
147
-
148
- /**
149
- * Attach warnings to a schema (mutates and returns it). Node-level flags let
150
- * renderers badge the exact entities involved without re-deriving anything:
151
- * - the changed node's survivors get `warning`
152
- * - every dependent gets `impactedBy` (list of warning node ids)
153
- */
154
- export function embedWarnings(schema, warnings) {
155
- schema.warnings = warnings;
156
- if (!warnings.length) return schema;
157
-
158
- const impactedBy = new Map(); // nodeId → [changed nodeIds]
159
- const changed = new Map(); // nodeId → warning
160
-
161
- for (const w of warnings) {
162
- changed.set(w.nodeId, w);
163
- for (const a of w.affected) {
164
- if (!impactedBy.has(a.id)) impactedBy.set(a.id, []);
165
- impactedBy.get(a.id).push(w.nodeId);
166
- }
167
- }
168
-
169
- for (const node of schema.nodes || []) {
170
- const w = changed.get(node.id);
171
- if (w) {
172
- node.warning = { severity: w.severity, message: w.message };
173
- }
174
- const sources = impactedBy.get(node.id);
175
- if (sources) {
176
- node.impactedBy = sources;
177
- }
178
- }
179
- return schema;
180
- }
181
-
182
- /** Compact, colour-free summary lines for CI/JSON consumers. */
183
- export function summarizeWarnings(warnings) {
184
- return warnings.map(w => ({
185
- severity: w.severity,
186
- change: w.message,
187
- system: w.system,
188
- affectedCount: w.affected.length,
189
- affectedSystems: w.affectedSystems,
190
- affectedByType: w.affectedByType,
191
- }));
192
- }
@@ -1,41 +0,0 @@
1
- // Monorepo Support - Task 120
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- function detectMonorepo(rootDir) {
6
- const indicators = [
7
- { file: 'turbo.json', type: 'turborepo' },
8
- { file: 'nx.json', type: 'nx' },
9
- { file: 'lerna.json', type: 'lerna' },
10
- { file: 'pnpm-workspace.yaml', type: 'pnpm' },
11
- ];
12
- for (const { file, type } of indicators) {
13
- if (fs.existsSync(path.join(rootDir, file))) return type;
14
- }
15
- const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8') || '{}');
16
- if (pkg.workspaces) return 'npm-workspaces';
17
- return null;
18
- }
19
-
20
- function getWorkspacePackages(rootDir) {
21
- const pkgPath = path.join(rootDir, 'package.json');
22
- if (!fs.existsSync(pkgPath)) return [];
23
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
24
- const workspaces = pkg.workspaces || [];
25
- const patterns = Array.isArray(workspaces) ? workspaces : (workspaces.packages || []);
26
- const packages = [];
27
- for (const pattern of patterns) {
28
- const globbed = pattern.replace(/\*$/, '');
29
- const base = path.join(rootDir, globbed.replace(/\*/g, ''));
30
- if (fs.existsSync(base)) {
31
- const dirs = fs.readdirSync(path.dirname(base)).filter(d => {
32
- const full = path.join(path.dirname(base), d);
33
- return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'package.json'));
34
- });
35
- dirs.forEach(d => packages.push({ name: d, path: path.join(path.dirname(base), d) }));
36
- }
37
- }
38
- return packages;
39
- }
40
-
41
- module.exports = { detectMonorepo, getWorkspacePackages };
@@ -1,40 +0,0 @@
1
- // CLI Plugin System - Task 24
2
- const path = require('path');
3
- const fs = require('fs');
4
- const { execSync } = require('child_process');
5
-
6
- const PLUGINS_DIR = path.join(require('os').homedir(), '.archsync', 'plugins');
7
-
8
- function ensurePluginsDir() {
9
- if (!fs.existsSync(PLUGINS_DIR)) fs.mkdirSync(PLUGINS_DIR, { recursive: true });
10
- }
11
-
12
- function loadPlugin(name) {
13
- const pluginPath = path.join(PLUGINS_DIR, 'node_modules', name);
14
- if (!fs.existsSync(pluginPath)) throw new Error(`Plugin ${name} not installed. Run: archsync plugin:add ${name}`);
15
- const plugin = require(pluginPath);
16
- if (typeof plugin.parse !== 'function') throw new Error(`Plugin ${name} must export a parse(filePath, options) function`);
17
- return plugin;
18
- }
19
-
20
- function listPlugins() {
21
- ensurePluginsDir();
22
- const pkgPath = path.join(PLUGINS_DIR, 'package.json');
23
- if (!fs.existsSync(pkgPath)) return [];
24
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
25
- return Object.keys(pkg.dependencies || {});
26
- }
27
-
28
- function installPlugin(name) {
29
- ensurePluginsDir();
30
- const pkgPath = path.join(PLUGINS_DIR, 'package.json');
31
- if (!fs.existsSync(pkgPath)) fs.writeFileSync(pkgPath, JSON.stringify({ name: 'archsync-plugins', private: true, dependencies: {} }));
32
- execSync(`npm install ${name}`, { cwd: PLUGINS_DIR, stdio: 'inherit' });
33
- }
34
-
35
- function uninstallPlugin(name) {
36
- ensurePluginsDir();
37
- execSync(`npm uninstall ${name}`, { cwd: PLUGINS_DIR, stdio: 'inherit' });
38
- }
39
-
40
- module.exports = { loadPlugin, listPlugins, installPlugin, uninstallPlugin };