archbyte 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ const BUILTIN_WORKFLOWS = [
8
8
  {
9
9
  id: "full-analysis",
10
10
  name: "Full Analysis Pipeline",
11
- description: "Complete architecture pipeline: analyze, generate, validate, and report",
11
+ description: "Complete architecture pipeline: analyze, generate, audit, and report",
12
12
  steps: [
13
13
  {
14
14
  id: "generate",
@@ -17,13 +17,6 @@ const BUILTIN_WORKFLOWS = [
17
17
  needs: [],
18
18
  description: "Generate architecture diagram from analysis JSON",
19
19
  },
20
- {
21
- id: "validate",
22
- name: "Validate Architecture",
23
- command: "archbyte validate",
24
- needs: ["generate"],
25
- description: "Run fitness function rules against the architecture",
26
- },
27
20
  {
28
21
  id: "stats",
29
22
  name: "Architecture Stats",
@@ -34,23 +27,23 @@ const BUILTIN_WORKFLOWS = [
34
27
  {
35
28
  id: "export",
36
29
  name: "Export Mermaid",
37
- command: "archbyte export --format mermaid",
30
+ command: "archbyte export --format mermaid --output .archbyte/architecture.mmd",
38
31
  needs: ["generate"],
39
- description: "Export architecture as Mermaid diagram",
32
+ description: "Export architecture as Mermaid diagram to .archbyte/architecture.mmd",
40
33
  },
41
34
  ],
42
35
  },
43
36
  {
44
37
  id: "ci-check",
45
38
  name: "CI Architecture Check",
46
- description: "Lightweight CI pipeline: validate and diff against baseline",
39
+ description: "Lightweight CI pipeline: audit architecture health",
47
40
  steps: [
48
41
  {
49
- id: "validate",
50
- name: "Validate",
51
- command: "archbyte validate --ci",
42
+ id: "stats",
43
+ name: "Stats",
44
+ command: "archbyte stats --ci",
52
45
  needs: [],
53
- description: "Run validation in CI mode (JSON output, exit code on failure)",
46
+ description: "Run architecture stats in CI mode",
54
47
  },
55
48
  ],
56
49
  },
@@ -15,13 +15,16 @@ let config;
15
15
  let sseClients = new Set();
16
16
  let diagramWatcher = null;
17
17
  let isAnalyzing = false;
18
+ // Source file watcher — detects changes to notify UI for manual re-analysis
19
+ let sourceWatcher = null;
20
+ const pendingSourceChanges = new Map();
21
+ let changeDebounceTimer = null;
22
+ const CHANGE_DEBOUNCE_MS = 2500;
18
23
  const sessionChanges = [];
19
24
  const MAX_SESSION_CHANGES = 50;
20
25
  let currentArchitecture = null;
21
26
  // Process tracking for run-from-UI
22
27
  const runningWorkflows = new Map();
23
- let patrolProcess = null;
24
- let patrolRunning = false;
25
28
  let chatProcess = null;
26
29
  // Resolve archbyte CLI binary path
27
30
  function getArchbyteBin() {
@@ -331,7 +334,7 @@ function createHttpServer() {
331
334
  return;
332
335
  }
333
336
  // API: Audit — unified validation + health endpoint
334
- if ((url === "/api/audit" || url === "/api/validate") && req.method === "GET") {
337
+ if (url === "/api/audit" && req.method === "GET") {
335
338
  const arch = currentArchitecture || (await loadArchitecture());
336
339
  if (!arch) {
337
340
  res.writeHead(404, { "Content-Type": "application/json" });
@@ -937,51 +940,13 @@ function createHttpServer() {
937
940
  res.end(content);
938
941
  return;
939
942
  }
940
- // API: Patrol history
941
- if (url === "/api/patrol/latest" && req.method === "GET") {
942
- const latestPath = path.join(config.workspaceRoot, ".archbyte/patrols/latest.json");
943
- if (!existsSync(latestPath)) {
944
- res.writeHead(200, { "Content-Type": "application/json" });
945
- res.end(JSON.stringify(null));
946
- return;
947
- }
948
- try {
949
- const content = readFileSync(latestPath, "utf-8");
950
- res.writeHead(200, { "Content-Type": "application/json" });
951
- res.end(content);
952
- }
953
- catch {
954
- res.writeHead(200, { "Content-Type": "application/json" });
955
- res.end(JSON.stringify(null));
956
- }
957
- return;
958
- }
959
- if (url === "/api/patrol/history" && req.method === "GET") {
960
- const historyPath = path.join(config.workspaceRoot, ".archbyte/patrols/history.jsonl");
961
- if (!existsSync(historyPath)) {
962
- res.writeHead(200, { "Content-Type": "application/json" });
963
- res.end(JSON.stringify([]));
964
- return;
965
- }
966
- try {
967
- const content = readFileSync(historyPath, "utf-8");
968
- const records = content.trim().split("\n").filter(Boolean).slice(-50).map((line) => JSON.parse(line));
969
- res.writeHead(200, { "Content-Type": "application/json" });
970
- res.end(JSON.stringify(records));
971
- }
972
- catch {
973
- res.writeHead(200, { "Content-Type": "application/json" });
974
- res.end(JSON.stringify([]));
975
- }
976
- return;
977
- }
978
943
  // API: Workflow list and status
979
944
  if (url === "/api/workflow/list" && req.method === "GET") {
980
945
  const workflows = [];
981
946
  // Built-in workflows
982
947
  const builtins = [
983
- { id: "full-analysis", name: "Full Analysis Pipeline", description: "Complete architecture pipeline", steps: ["generate", "validate", "stats", "export"] },
984
- { id: "ci-check", name: "CI Architecture Check", description: "Lightweight CI pipeline", steps: ["validate"] },
948
+ { id: "full-analysis", name: "Full Analysis Pipeline", description: "Complete architecture pipeline", steps: ["generate", "stats", "export"] },
949
+ { id: "ci-check", name: "CI Architecture Check", description: "Lightweight CI pipeline", steps: ["stats"] },
985
950
  { id: "drift-check", name: "Architecture Drift Check", description: "Check for architecture drift", steps: ["snapshot", "generate", "diff"] },
986
951
  ];
987
952
  for (const b of builtins) {
@@ -1197,6 +1162,14 @@ function createHttpServer() {
1197
1162
  res.end(JSON.stringify(sessionChanges));
1198
1163
  return;
1199
1164
  }
1165
+ // API: Pending source file changes — hydrates UI on reconnect
1166
+ if (url === "/api/changes/pending" && req.method === "GET") {
1167
+ const files = Array.from(pendingSourceChanges.values()).map((c) => ({ event: c.event, path: c.path }));
1168
+ const count = files.length;
1169
+ res.writeHead(200, { "Content-Type": "application/json" });
1170
+ res.end(JSON.stringify({ count, files: files.slice(0, 20), truncated: count > 20 }));
1171
+ return;
1172
+ }
1200
1173
  // API: Run workflow
1201
1174
  if (url.startsWith("/api/workflow/run/") && req.method === "POST") {
1202
1175
  const id = url.split("/").pop();
@@ -1269,56 +1242,6 @@ function createHttpServer() {
1269
1242
  });
1270
1243
  return;
1271
1244
  }
1272
- // API: Start patrol
1273
- if (url === "/api/patrol/start" && req.method === "POST") {
1274
- if (patrolRunning) {
1275
- res.writeHead(409, { "Content-Type": "application/json" });
1276
- res.end(JSON.stringify({ error: "Patrol already running" }));
1277
- return;
1278
- }
1279
- let body = "";
1280
- req.on("data", (chunk) => { body += chunk.toString(); });
1281
- req.on("end", () => {
1282
- const { interval } = JSON.parse(body || "{}");
1283
- const bin = getArchbyteBin();
1284
- const args = [bin, "patrol"];
1285
- if (interval)
1286
- args.push("--interval", String(interval));
1287
- patrolProcess = spawn(process.execPath, args, {
1288
- cwd: config.workspaceRoot,
1289
- stdio: ["ignore", "inherit", "inherit"],
1290
- env: { ...process.env, FORCE_COLOR: "1" },
1291
- });
1292
- patrolRunning = true;
1293
- broadcastOpsEvent({ type: "patrol:started" });
1294
- patrolProcess.on("close", () => {
1295
- patrolProcess = null;
1296
- patrolRunning = false;
1297
- broadcastOpsEvent({ type: "patrol:stopped" });
1298
- });
1299
- res.writeHead(200, { "Content-Type": "application/json" });
1300
- res.end(JSON.stringify({ ok: true }));
1301
- });
1302
- return;
1303
- }
1304
- // API: Stop patrol
1305
- if (url === "/api/patrol/stop" && req.method === "POST") {
1306
- if (patrolProcess) {
1307
- patrolProcess.kill("SIGTERM");
1308
- patrolProcess = null;
1309
- patrolRunning = false;
1310
- broadcastOpsEvent({ type: "patrol:stopped" });
1311
- }
1312
- res.writeHead(200, { "Content-Type": "application/json" });
1313
- res.end(JSON.stringify({ ok: true }));
1314
- return;
1315
- }
1316
- // API: Patrol running status
1317
- if (url === "/api/patrol/running" && req.method === "GET") {
1318
- res.writeHead(200, { "Content-Type": "application/json" });
1319
- res.end(JSON.stringify({ running: patrolRunning }));
1320
- return;
1321
- }
1322
1245
  // API: Config — read project config (including provider settings)
1323
1246
  if (url === "/api/config" && req.method === "GET") {
1324
1247
  const configPath = path.join(config.workspaceRoot, ".archbyte/config.json");
@@ -1732,12 +1655,22 @@ function diffArchitectures(prev, curr) {
1732
1655
  .map((e) => ({ source: e.source, target: e.target, label: e.label }));
1733
1656
  return { addedNodes, removedNodes, modifiedNodes, addedEdges, removedEdges };
1734
1657
  }
1735
- // Run analyze → generate pipeline (used by /api/analyze and patrol)
1658
+ // Run analyze → generate pipeline (used by /api/analyze)
1736
1659
  function runAnalyzePipeline(mode = "static", fileChanges) {
1737
1660
  if (isAnalyzing)
1738
1661
  return;
1739
1662
  isAnalyzing = true;
1740
1663
  const pipelineStart = Date.now();
1664
+ // Capture and clear pending source changes
1665
+ if (!fileChanges) {
1666
+ fileChanges = Array.from(pendingSourceChanges.values()).map((c) => ({ event: c.event, path: c.path }));
1667
+ }
1668
+ pendingSourceChanges.clear();
1669
+ if (changeDebounceTimer) {
1670
+ clearTimeout(changeDebounceTimer);
1671
+ changeDebounceTimer = null;
1672
+ }
1673
+ broadcastOpsEvent({ type: "changes:cleared" });
1741
1674
  // Snapshot current architecture for diffing after pipeline completes
1742
1675
  const prevArchitecture = currentArchitecture
1743
1676
  ? { ...currentArchitecture, nodes: [...currentArchitecture.nodes], edges: [...currentArchitecture.edges] }
@@ -1828,6 +1761,56 @@ function setupWatcher() {
1828
1761
  broadcastUpdate();
1829
1762
  });
1830
1763
  }
1764
+ // Broadcast pending source changes to SSE clients
1765
+ function broadcastPendingChanges() {
1766
+ const files = Array.from(pendingSourceChanges.values()).map((c) => c.path);
1767
+ const count = files.length;
1768
+ const truncated = count > 20;
1769
+ broadcastOpsEvent({
1770
+ type: "changes:detected",
1771
+ count,
1772
+ files: files.slice(0, 20),
1773
+ truncated,
1774
+ });
1775
+ }
1776
+ // Setup source file watcher — watches workspace for code changes
1777
+ function setupSourceWatcher() {
1778
+ sourceWatcher = watch(config.workspaceRoot, {
1779
+ ignoreInitial: true,
1780
+ ignored: [
1781
+ /(^|[/\\])\../, // dotfiles/dirs
1782
+ "**/node_modules/**",
1783
+ "**/dist/**",
1784
+ "**/build/**",
1785
+ "**/out/**",
1786
+ "**/.archbyte/**",
1787
+ "**/coverage/**",
1788
+ "**/*.lock",
1789
+ "**/package-lock.json",
1790
+ "**/__pycache__/**",
1791
+ "**/target/**",
1792
+ "**/vendor/**",
1793
+ ],
1794
+ depth: 10,
1795
+ });
1796
+ sourceWatcher.on("all", (event, filePath) => {
1797
+ if (event !== "change" && event !== "add" && event !== "unlink")
1798
+ return;
1799
+ const relativePath = path.relative(config.workspaceRoot, filePath);
1800
+ pendingSourceChanges.set(relativePath, {
1801
+ event,
1802
+ path: relativePath,
1803
+ timestamp: Date.now(),
1804
+ });
1805
+ // Debounce: reset timer on each change, broadcast after settling
1806
+ if (changeDebounceTimer)
1807
+ clearTimeout(changeDebounceTimer);
1808
+ changeDebounceTimer = setTimeout(() => {
1809
+ changeDebounceTimer = null;
1810
+ broadcastPendingChanges();
1811
+ }, CHANGE_DEBOUNCE_MS);
1812
+ });
1813
+ }
1831
1814
  // Graceful shutdown
1832
1815
  function setupShutdown() {
1833
1816
  const shutdown = async () => {
@@ -1840,14 +1823,6 @@ function setupShutdown() {
1840
1823
  catch { }
1841
1824
  }
1842
1825
  runningWorkflows.clear();
1843
- if (patrolProcess) {
1844
- try {
1845
- patrolProcess.kill("SIGTERM");
1846
- }
1847
- catch { }
1848
- patrolProcess = null;
1849
- patrolRunning = false;
1850
- }
1851
1826
  if (chatProcess) {
1852
1827
  try {
1853
1828
  chatProcess.kill("SIGTERM");
@@ -1862,6 +1837,7 @@ function setupShutdown() {
1862
1837
  catch { }
1863
1838
  }
1864
1839
  sseClients.clear();
1840
+ await sourceWatcher?.close();
1865
1841
  await diagramWatcher?.close();
1866
1842
  httpServer?.close();
1867
1843
  process.exit(0);
@@ -1878,8 +1854,7 @@ function loadLicenseInfo() {
1878
1854
  tier: "free",
1879
1855
  features: {
1880
1856
  analyze: true,
1881
- validate: false,
1882
- patrol: false,
1857
+ audit: false,
1883
1858
  workflows: false,
1884
1859
  chat: false,
1885
1860
  premiumAgents: false,
@@ -1931,8 +1906,7 @@ function loadLicenseInfo() {
1931
1906
  tier: isPremium ? "premium" : "free",
1932
1907
  features: {
1933
1908
  analyze: true,
1934
- validate: isPremium,
1935
- patrol: isPremium,
1909
+ audit: isPremium,
1936
1910
  workflows: isPremium,
1937
1911
  chat: isPremium,
1938
1912
  premiumAgents: isPremium,
@@ -1956,6 +1930,7 @@ export async function startServer(cfg) {
1956
1930
  process.exit(1);
1957
1931
  }
1958
1932
  setupWatcher();
1933
+ setupSourceWatcher();
1959
1934
  console.error(`[archbyte] Serving ${config.name}`);
1960
1935
  console.error(`[archbyte] Diagram: ${config.diagramPath}`);
1961
1936
  // Listen for 'q' keypress to quit gracefully
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archbyte",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "ArchByte - See what agents build. As they build it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@anthropic-ai/sdk": "^0.74.0",
39
- "@google/genai": "^1.41.0",
39
+ "@google/genai": "^1.42.0",
40
40
  "@modelcontextprotocol/sdk": "^1.26.0",
41
41
  "chalk": "^5.3.0",
42
42
  "chokidar": "^3.5.3",
@@ -102,10 +102,31 @@ rules:
102
102
  # to: { type: external }
103
103
  # level: warn
104
104
 
105
- # ── Patrol ──
106
- # Configure the continuous monitoring daemon (archbyte patrol).
107
- # patrol:
108
- # ignore:
109
- # - "docs/"
110
- # - "*.md"
111
- # - "scripts/"
105
+ # ── Workflows ──
106
+ # Composable multi-step architecture pipelines.
107
+ # Run built-in workflows:
108
+ # archbyte workflow --list # see available workflows
109
+ # archbyte workflow --run full-analysis
110
+ # archbyte workflow --run ci-check
111
+ # archbyte workflow --run drift-check
112
+ #
113
+ # Create custom workflows in .archbyte/workflows/:
114
+ # archbyte workflow --create "My Pipeline"
115
+ #
116
+ # Custom workflow format (.archbyte/workflows/my-pipeline.yaml):
117
+ # id: my-pipeline
118
+ # name: "My Pipeline"
119
+ # description: "Custom architecture pipeline"
120
+ # steps:
121
+ # - id: generate
122
+ # name: "Generate Diagram"
123
+ # command: "archbyte generate"
124
+ # needs: []
125
+ # - id: validate
126
+ # name: "Validate"
127
+ # command: "archbyte validate"
128
+ # needs: [generate]
129
+ # - id: export
130
+ # name: "Export"
131
+ # command: "archbyte export --format mermaid"
132
+ # needs: [validate]