feed-the-machine 1.0.0 → 1.2.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.
Files changed (136) hide show
  1. package/bin/generate-manifest.mjs +253 -0
  2. package/bin/install.mjs +134 -4
  3. package/docs/HOOKS.md +243 -0
  4. package/docs/INBOX.md +233 -0
  5. package/ftm/SKILL.md +34 -0
  6. package/ftm-audit/SKILL.md +69 -0
  7. package/ftm-brainstorm/SKILL.md +51 -0
  8. package/ftm-browse/SKILL.md +39 -0
  9. package/ftm-capture/SKILL.md +370 -0
  10. package/ftm-capture.yml +4 -0
  11. package/ftm-codex-gate/SKILL.md +59 -0
  12. package/ftm-config/SKILL.md +35 -0
  13. package/ftm-council/SKILL.md +56 -0
  14. package/ftm-dashboard/SKILL.md +163 -0
  15. package/ftm-debug/SKILL.md +84 -0
  16. package/ftm-diagram/SKILL.md +44 -0
  17. package/ftm-executor/SKILL.md +97 -0
  18. package/ftm-git/SKILL.md +60 -0
  19. package/ftm-inbox/backend/__init__.py +0 -0
  20. package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
  21. package/ftm-inbox/backend/adapters/__init__.py +0 -0
  22. package/ftm-inbox/backend/adapters/_retry.py +64 -0
  23. package/ftm-inbox/backend/adapters/base.py +230 -0
  24. package/ftm-inbox/backend/adapters/freshservice.py +104 -0
  25. package/ftm-inbox/backend/adapters/gmail.py +125 -0
  26. package/ftm-inbox/backend/adapters/jira.py +136 -0
  27. package/ftm-inbox/backend/adapters/registry.py +192 -0
  28. package/ftm-inbox/backend/adapters/slack.py +110 -0
  29. package/ftm-inbox/backend/db/__init__.py +0 -0
  30. package/ftm-inbox/backend/db/connection.py +54 -0
  31. package/ftm-inbox/backend/db/schema.py +78 -0
  32. package/ftm-inbox/backend/executor/__init__.py +7 -0
  33. package/ftm-inbox/backend/executor/engine.py +149 -0
  34. package/ftm-inbox/backend/executor/step_runner.py +98 -0
  35. package/ftm-inbox/backend/main.py +103 -0
  36. package/ftm-inbox/backend/models/__init__.py +1 -0
  37. package/ftm-inbox/backend/models/unified_task.py +36 -0
  38. package/ftm-inbox/backend/planner/__init__.py +6 -0
  39. package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
  40. package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
  41. package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
  42. package/ftm-inbox/backend/planner/generator.py +127 -0
  43. package/ftm-inbox/backend/planner/schema.py +34 -0
  44. package/ftm-inbox/backend/requirements.txt +5 -0
  45. package/ftm-inbox/backend/routes/__init__.py +0 -0
  46. package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
  47. package/ftm-inbox/backend/routes/execute.py +186 -0
  48. package/ftm-inbox/backend/routes/health.py +52 -0
  49. package/ftm-inbox/backend/routes/inbox.py +68 -0
  50. package/ftm-inbox/backend/routes/plan.py +271 -0
  51. package/ftm-inbox/bin/launchagent.mjs +91 -0
  52. package/ftm-inbox/bin/setup.mjs +188 -0
  53. package/ftm-inbox/bin/start.sh +10 -0
  54. package/ftm-inbox/bin/status.sh +17 -0
  55. package/ftm-inbox/bin/stop.sh +8 -0
  56. package/ftm-inbox/config.example.yml +55 -0
  57. package/ftm-inbox/package-lock.json +2898 -0
  58. package/ftm-inbox/package.json +26 -0
  59. package/ftm-inbox/postcss.config.js +6 -0
  60. package/ftm-inbox/src/app.css +199 -0
  61. package/ftm-inbox/src/app.html +18 -0
  62. package/ftm-inbox/src/lib/api.ts +166 -0
  63. package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -0
  64. package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -0
  65. package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -0
  66. package/ftm-inbox/src/lib/components/PlanView.svelte +206 -0
  67. package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -0
  68. package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -0
  69. package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -0
  70. package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -0
  71. package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -0
  72. package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -0
  73. package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -0
  74. package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -0
  75. package/ftm-inbox/src/lib/theme.ts +47 -0
  76. package/ftm-inbox/src/routes/+layout.svelte +76 -0
  77. package/ftm-inbox/src/routes/+page.svelte +401 -0
  78. package/ftm-inbox/static/favicon.png +0 -0
  79. package/ftm-inbox/svelte.config.js +12 -0
  80. package/ftm-inbox/tailwind.config.ts +63 -0
  81. package/ftm-inbox/tsconfig.json +13 -0
  82. package/ftm-inbox/vite.config.ts +6 -0
  83. package/ftm-intent/SKILL.md +44 -0
  84. package/ftm-manifest.json +3794 -0
  85. package/ftm-map/SKILL.md +259 -0
  86. package/ftm-map/scripts/db.py +391 -0
  87. package/ftm-map/scripts/index.py +341 -0
  88. package/ftm-map/scripts/parser.py +455 -0
  89. package/ftm-map/scripts/queries/.gitkeep +0 -0
  90. package/ftm-map/scripts/queries/javascript-tags.scm +23 -0
  91. package/ftm-map/scripts/queries/python-tags.scm +17 -0
  92. package/ftm-map/scripts/queries/typescript-tags.scm +29 -0
  93. package/ftm-map/scripts/query.py +149 -0
  94. package/ftm-map/scripts/requirements.txt +2 -0
  95. package/ftm-map/scripts/setup-hooks.sh +27 -0
  96. package/ftm-map/scripts/setup.sh +45 -0
  97. package/ftm-map/scripts/test_db.py +124 -0
  98. package/ftm-map/scripts/test_parser.py +106 -0
  99. package/ftm-map/scripts/test_query.py +66 -0
  100. package/ftm-map/scripts/tests/fixtures/__init__.py +0 -0
  101. package/ftm-map/scripts/tests/fixtures/sample_project/api.ts +16 -0
  102. package/ftm-map/scripts/tests/fixtures/sample_project/auth.py +15 -0
  103. package/ftm-map/scripts/tests/fixtures/sample_project/utils.js +16 -0
  104. package/ftm-map/scripts/views.py +545 -0
  105. package/ftm-mind/SKILL.md +173 -66
  106. package/ftm-pause/SKILL.md +43 -0
  107. package/ftm-researcher/SKILL.md +275 -0
  108. package/ftm-researcher/evals/agent-diversity.yaml +17 -0
  109. package/ftm-researcher/evals/synthesis-quality.yaml +12 -0
  110. package/ftm-researcher/evals/trigger-accuracy.yaml +39 -0
  111. package/ftm-researcher/references/adaptive-search.md +116 -0
  112. package/ftm-researcher/references/agent-prompts.md +193 -0
  113. package/ftm-researcher/references/council-integration.md +193 -0
  114. package/ftm-researcher/references/output-format.md +203 -0
  115. package/ftm-researcher/references/synthesis-pipeline.md +165 -0
  116. package/ftm-researcher/scripts/score_credibility.py +234 -0
  117. package/ftm-researcher/scripts/validate_research.py +92 -0
  118. package/ftm-resume/SKILL.md +47 -0
  119. package/ftm-retro/SKILL.md +54 -0
  120. package/ftm-routine/SKILL.md +170 -0
  121. package/ftm-state/blackboard/capabilities.json +5 -0
  122. package/ftm-state/blackboard/capabilities.schema.json +27 -0
  123. package/ftm-upgrade/SKILL.md +41 -0
  124. package/ftm-upgrade/scripts/check-version.sh +1 -1
  125. package/ftm-upgrade/scripts/upgrade.sh +1 -1
  126. package/hooks/ftm-blackboard-enforcer.sh +94 -0
  127. package/hooks/ftm-discovery-reminder.sh +90 -0
  128. package/hooks/ftm-drafts-gate.sh +61 -0
  129. package/hooks/ftm-event-logger.mjs +107 -0
  130. package/hooks/ftm-map-autodetect.sh +79 -0
  131. package/hooks/ftm-pending-sync-check.sh +22 -0
  132. package/hooks/ftm-plan-gate.sh +96 -0
  133. package/hooks/ftm-post-commit-trigger.sh +57 -0
  134. package/hooks/settings-template.json +81 -0
  135. package/install.sh +140 -11
  136. package/package.json +12 -2
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ HOOKS_DIR="$HOME/.claude/git-hooks"
5
+
6
+ # Create hooks directory
7
+ mkdir -p "$HOOKS_DIR"
8
+
9
+ # Check if hooksPath is already set
10
+ CURRENT="$(git config --global core.hooksPath 2>/dev/null || echo "")"
11
+ if [ "$CURRENT" = "$HOOKS_DIR" ]; then
12
+ echo "core.hooksPath already configured: $HOOKS_DIR"
13
+ exit 0
14
+ fi
15
+
16
+ if [ -n "$CURRENT" ] && [ "$CURRENT" != "$HOOKS_DIR" ]; then
17
+ echo "WARNING: core.hooksPath is already set to: $CURRENT"
18
+ echo "Changing to: $HOOKS_DIR"
19
+ echo "Previous hooks at $CURRENT will NOT run unless manually chained."
20
+ fi
21
+
22
+ git config --global core.hooksPath "$HOOKS_DIR"
23
+ echo "Set git core.hooksPath to: $HOOKS_DIR"
24
+ echo ""
25
+ echo "NOTE: This is a GLOBAL git config change affecting all repositories."
26
+ echo "The post-commit hook chains to project-local hooks if they exist."
27
+ echo "To revert: git config --global --unset core.hooksPath"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ VENV_DIR="$SCRIPT_DIR/.venv"
6
+
7
+ # Check Python version (3.10+ required)
8
+ PYTHON_BIN=""
9
+ for candidate in python3.12 python3.11 python3.10 python3; do
10
+ if command -v "$candidate" &>/dev/null; then
11
+ version_ok=$("$candidate" -c "import sys; print(1 if sys.version_info >= (3, 10) else 0)" 2>/dev/null || echo 0)
12
+ if [ "$version_ok" = "1" ]; then
13
+ PYTHON_BIN="$candidate"
14
+ break
15
+ fi
16
+ fi
17
+ done
18
+
19
+ if [ -z "$PYTHON_BIN" ]; then
20
+ echo "ERROR: Python 3.10+ is required but not found." >&2
21
+ echo "Install Python 3.10 or higher and try again." >&2
22
+ exit 1
23
+ fi
24
+
25
+ PYTHON_VERSION=$("$PYTHON_BIN" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')")
26
+ echo "Using Python $PYTHON_VERSION at $("$PYTHON_BIN" -c "import sys; print(sys.executable)")"
27
+
28
+ # Create venv if it doesn't exist
29
+ if [ ! -d "$VENV_DIR" ]; then
30
+ echo "Creating virtual environment at $VENV_DIR..."
31
+ "$PYTHON_BIN" -m venv "$VENV_DIR"
32
+ echo "Virtual environment created."
33
+ else
34
+ echo "Virtual environment already exists at $VENV_DIR."
35
+ fi
36
+
37
+ # Install/upgrade dependencies
38
+ echo "Installing dependencies from requirements.txt..."
39
+ "$VENV_DIR/bin/pip" install -q --upgrade pip
40
+ "$VENV_DIR/bin/pip" install -q -r "$SCRIPT_DIR/requirements.txt"
41
+ echo "Dependencies installed."
42
+
43
+ # Verify the installation
44
+ echo "Verifying installation..."
45
+ "$VENV_DIR/bin/python3" -c "import sqlite_vec; from tree_sitter_language_pack import get_parser; print('ftm-map dependencies OK')"
@@ -0,0 +1,124 @@
1
+ """Tests for ftm-map database module."""
2
+ import os
3
+ import sys
4
+ import tempfile
5
+ import unittest
6
+
7
+ sys.path.insert(0, os.path.dirname(__file__))
8
+ from db import (
9
+ get_connection, add_symbol, remove_symbols_by_file, add_edge,
10
+ get_symbol_by_id, get_symbol_by_name, get_transitive_deps,
11
+ get_reverse_deps, fts_search, get_stats
12
+ )
13
+
14
+ class TestDatabase(unittest.TestCase):
15
+ def setUp(self):
16
+ self.tmpdir = tempfile.mkdtemp()
17
+ self.conn = get_connection(self.tmpdir)
18
+
19
+ def tearDown(self):
20
+ self.conn.close()
21
+
22
+ def test_schema_creation(self):
23
+ """Tables and indexes should exist after connection."""
24
+ tables = [r[0] for r in self.conn.execute(
25
+ "SELECT name FROM sqlite_master WHERE type='table'"
26
+ ).fetchall()]
27
+ self.assertIn("symbols", tables)
28
+ self.assertIn("edges", tables)
29
+ self.assertIn("symbols_fts", tables)
30
+
31
+ def test_wal_mode(self):
32
+ """WAL mode should be enabled."""
33
+ mode = self.conn.execute("PRAGMA journal_mode").fetchone()[0]
34
+ self.assertEqual(mode, "wal")
35
+
36
+ def test_add_and_get_symbol(self):
37
+ """Should insert and retrieve a symbol."""
38
+ sid = add_symbol(self.conn, "handleAuth", "function", "auth.py", 1, 5, "def handleAuth(request)", "Auth handler", "abc123")
39
+ self.conn.commit()
40
+ sym = get_symbol_by_id(self.conn, sid)
41
+ self.assertIsNotNone(sym)
42
+ self.assertEqual(sym["name"], "handleAuth")
43
+ self.assertEqual(sym["kind"], "function")
44
+
45
+ def test_remove_symbols_by_file(self):
46
+ """Should remove all symbols for a file."""
47
+ add_symbol(self.conn, "foo", "function", "test.py", 1, 3)
48
+ add_symbol(self.conn, "bar", "function", "test.py", 5, 8)
49
+ add_symbol(self.conn, "baz", "function", "other.py", 1, 3)
50
+ self.conn.commit()
51
+ remove_symbols_by_file(self.conn, "test.py")
52
+ self.conn.commit()
53
+ self.assertEqual(len(get_symbol_by_name(self.conn, "foo")), 0)
54
+ self.assertEqual(len(get_symbol_by_name(self.conn, "baz")), 1)
55
+
56
+ def test_edges_and_cascade(self):
57
+ """Edges should be deleted when source symbol is removed."""
58
+ s1 = add_symbol(self.conn, "caller", "function", "a.py", 1, 5)
59
+ s2 = add_symbol(self.conn, "callee", "function", "b.py", 1, 5)
60
+ add_edge(self.conn, s1, s2, "calls")
61
+ self.conn.commit()
62
+ remove_symbols_by_file(self.conn, "a.py")
63
+ self.conn.commit()
64
+ edges = self.conn.execute("SELECT * FROM edges").fetchall()
65
+ self.assertEqual(len(edges), 0)
66
+
67
+ def test_transitive_deps(self):
68
+ """Should return transitive dependency chain."""
69
+ # A calls B, B calls C
70
+ a = add_symbol(self.conn, "A", "function", "a.py", 1, 5)
71
+ b = add_symbol(self.conn, "B", "function", "b.py", 1, 5)
72
+ c = add_symbol(self.conn, "C", "function", "c.py", 1, 5)
73
+ add_edge(self.conn, a, b, "calls")
74
+ add_edge(self.conn, b, c, "calls")
75
+ self.conn.commit()
76
+ deps = get_transitive_deps(self.conn, a)
77
+ dep_names = {d["name"] for d in deps}
78
+ self.assertIn("B", dep_names)
79
+ self.assertIn("C", dep_names)
80
+
81
+ def test_reverse_deps_blast_radius(self):
82
+ """Blast radius of C should return B and A."""
83
+ a = add_symbol(self.conn, "A", "function", "a.py", 1, 5)
84
+ b = add_symbol(self.conn, "B", "function", "b.py", 1, 5)
85
+ c = add_symbol(self.conn, "C", "function", "c.py", 1, 5)
86
+ add_edge(self.conn, a, b, "calls")
87
+ add_edge(self.conn, b, c, "calls")
88
+ self.conn.commit()
89
+ blast = get_reverse_deps(self.conn, c)
90
+ blast_names = {d["name"] for d in blast}
91
+ self.assertIn("B", blast_names)
92
+ self.assertIn("A", blast_names)
93
+
94
+ def test_fts_search(self):
95
+ """FTS5 search should rank handleAuth above getUser for 'handle' query."""
96
+ add_symbol(self.conn, "handleAuth", "function", "auth.py", 1, 5, "def handleAuth(request)", "Handle authentication")
97
+ add_symbol(self.conn, "getUser", "function", "auth.py", 7, 10, "def getUser(user_id)", "Get user by ID")
98
+ self.conn.commit()
99
+ results = fts_search(self.conn, "handle")
100
+ self.assertGreater(len(results), 0)
101
+ self.assertEqual(results[0]["name"], "handleAuth")
102
+
103
+ def test_cycle_prevention(self):
104
+ """Recursive CTE should not loop on cycles."""
105
+ a = add_symbol(self.conn, "A", "function", "a.py", 1, 5)
106
+ b = add_symbol(self.conn, "B", "function", "b.py", 1, 5)
107
+ add_edge(self.conn, a, b, "calls")
108
+ add_edge(self.conn, b, a, "calls") # cycle!
109
+ self.conn.commit()
110
+ deps = get_transitive_deps(self.conn, a)
111
+ # Should not hang, and should contain B
112
+ self.assertTrue(any(d["name"] == "B" for d in deps))
113
+
114
+ def test_stats(self):
115
+ """Stats should return correct counts."""
116
+ add_symbol(self.conn, "x", "function", "x.py", 1, 3)
117
+ add_symbol(self.conn, "y", "function", "y.py", 1, 3)
118
+ self.conn.commit()
119
+ stats = get_stats(self.conn)
120
+ self.assertEqual(stats["symbols"], 2)
121
+ self.assertEqual(stats["files"], 2)
122
+
123
+ if __name__ == "__main__":
124
+ unittest.main()
@@ -0,0 +1,106 @@
1
+ """Tests for ftm-map parser module."""
2
+ import os
3
+ import sys
4
+ import unittest
5
+
6
+ sys.path.insert(0, os.path.dirname(__file__))
7
+
8
+ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "tests", "fixtures", "sample_project")
9
+
10
+ class TestParser(unittest.TestCase):
11
+
12
+ @classmethod
13
+ def setUpClass(cls):
14
+ """Check if tree-sitter-language-pack is available."""
15
+ try:
16
+ from parser import parse_file, extract_relationships, detect_language
17
+ cls.parse_file = staticmethod(parse_file)
18
+ cls.extract_relationships = staticmethod(extract_relationships)
19
+ cls.detect_language = staticmethod(detect_language)
20
+ cls.skip_reason = None
21
+ except ImportError as e:
22
+ cls.skip_reason = f"tree-sitter-language-pack not installed: {e}"
23
+
24
+ def setUp(self):
25
+ if self.skip_reason:
26
+ self.skipTest(self.skip_reason)
27
+
28
+ def test_detect_language(self):
29
+ self.assertEqual(self.detect_language("foo.py"), "python")
30
+ self.assertEqual(self.detect_language("bar.ts"), "typescript")
31
+ self.assertEqual(self.detect_language("baz.js"), "javascript")
32
+ self.assertIsNone(self.detect_language("README.md"))
33
+
34
+ def test_parse_python_file(self):
35
+ """Should extract Python functions."""
36
+ path = os.path.join(FIXTURES_DIR, "auth.py")
37
+ if not os.path.exists(path):
38
+ self.skipTest("Fixture not found")
39
+ symbols = self.parse_file(path)
40
+ names = {s.name for s in symbols}
41
+ self.assertIn("handleAuth", names)
42
+ self.assertIn("validateToken", names)
43
+ self.assertIn("getUser", names)
44
+
45
+ def test_parse_typescript_file(self):
46
+ """Should extract TypeScript functions and classes."""
47
+ path = os.path.join(FIXTURES_DIR, "api.ts")
48
+ if not os.path.exists(path):
49
+ self.skipTest("Fixture not found")
50
+ symbols = self.parse_file(path)
51
+ names = {s.name for s in symbols}
52
+ self.assertIn("processRequest", names)
53
+ self.assertIn("ApiController", names)
54
+
55
+ def test_parse_javascript_file(self):
56
+ """Should extract JavaScript functions."""
57
+ path = os.path.join(FIXTURES_DIR, "utils.js")
58
+ if not os.path.exists(path):
59
+ self.skipTest("Fixture not found")
60
+ symbols = self.parse_file(path)
61
+ names = {s.name for s in symbols}
62
+ self.assertIn("formatDate", names)
63
+ self.assertIn("parseConfig", names)
64
+
65
+ def test_symbol_has_content_hash(self):
66
+ """Every symbol should have a content hash."""
67
+ path = os.path.join(FIXTURES_DIR, "auth.py")
68
+ if not os.path.exists(path):
69
+ self.skipTest("Fixture not found")
70
+ symbols = self.parse_file(path)
71
+ for sym in symbols:
72
+ self.assertTrue(len(sym.content_hash) > 0, f"{sym.name} missing content_hash")
73
+
74
+ def test_symbol_has_line_numbers(self):
75
+ """Every symbol should have start and end line."""
76
+ path = os.path.join(FIXTURES_DIR, "auth.py")
77
+ if not os.path.exists(path):
78
+ self.skipTest("Fixture not found")
79
+ symbols = self.parse_file(path)
80
+ for sym in symbols:
81
+ self.assertGreater(sym.start_line, 0)
82
+ self.assertGreaterEqual(sym.end_line, sym.start_line)
83
+
84
+ def test_extract_relationships(self):
85
+ """Should extract call relationships."""
86
+ path = os.path.join(FIXTURES_DIR, "auth.py")
87
+ if not os.path.exists(path):
88
+ self.skipTest("Fixture not found")
89
+ rels = self.extract_relationships(path)
90
+ # handleAuth calls validateToken and getUser
91
+ call_targets = {r.target_name for r in rels if r.kind == "calls"}
92
+ self.assertIn("validateToken", call_targets)
93
+ self.assertIn("getUser", call_targets)
94
+
95
+ def test_unsupported_file_returns_empty(self):
96
+ """Unsupported file types should return empty list."""
97
+ symbols = self.parse_file("/tmp/fake.xyz")
98
+ self.assertEqual(symbols, [])
99
+
100
+ def test_nonexistent_file_returns_empty(self):
101
+ """Non-existent files should return empty list, not error."""
102
+ symbols = self.parse_file("/tmp/nonexistent_file_12345.py")
103
+ self.assertEqual(symbols, [])
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main()
@@ -0,0 +1,66 @@
1
+ """Tests for ftm-map query module."""
2
+ import os
3
+ import sys
4
+ import tempfile
5
+ import unittest
6
+
7
+ sys.path.insert(0, os.path.dirname(__file__))
8
+ from db import get_connection, add_symbol, add_edge
9
+ from query import blast_radius, dependency_chain, search, symbol_info
10
+
11
+ class TestQuery(unittest.TestCase):
12
+ def setUp(self):
13
+ self.tmpdir = tempfile.mkdtemp()
14
+ self.conn = get_connection(self.tmpdir)
15
+ # Build test graph: A -> B -> C, D imports A
16
+ self.a = add_symbol(self.conn, "handleAuth", "function", "auth.py", 1, 5, "def handleAuth(req)", "Auth handler")
17
+ self.b = add_symbol(self.conn, "validateToken", "function", "auth.py", 7, 10, "def validateToken(token)")
18
+ self.c = add_symbol(self.conn, "getUser", "function", "users.py", 1, 5, "def getUser(uid)")
19
+ self.d = add_symbol(self.conn, "processRequest", "function", "api.ts", 1, 8, "function processRequest(req)")
20
+ add_edge(self.conn, self.a, self.b, "calls")
21
+ add_edge(self.conn, self.a, self.c, "calls")
22
+ add_edge(self.conn, self.d, self.a, "calls")
23
+ self.conn.commit()
24
+
25
+ def tearDown(self):
26
+ self.conn.close()
27
+
28
+ def test_blast_radius(self):
29
+ """Blast radius of getUser should include handleAuth and processRequest."""
30
+ result = blast_radius(self.conn, "getUser")
31
+ names = {r["name"] for r in result["results"]}
32
+ self.assertIn("handleAuth", names)
33
+ self.assertIn("processRequest", names)
34
+ self.assertEqual(result["affected_count"], 2)
35
+
36
+ def test_dependency_chain(self):
37
+ """handleAuth depends on validateToken and getUser."""
38
+ result = dependency_chain(self.conn, "handleAuth")
39
+ names = {r["name"] for r in result["results"]}
40
+ self.assertIn("validateToken", names)
41
+ self.assertIn("getUser", names)
42
+
43
+ def test_search(self):
44
+ """Search for 'auth' should return handleAuth."""
45
+ result = search(self.conn, "auth")
46
+ self.assertGreater(result["result_count"], 0)
47
+ names = {r["name"] for r in result["results"]}
48
+ self.assertIn("handleAuth", names)
49
+
50
+ def test_symbol_info(self):
51
+ """Should return full details with callers and callees."""
52
+ result = symbol_info(self.conn, "handleAuth")
53
+ self.assertEqual(result["name"], "handleAuth")
54
+ callee_names = {c["name"] for c in result["callees"]}
55
+ self.assertIn("validateToken", callee_names)
56
+ self.assertIn("getUser", callee_names)
57
+ caller_names = {c["name"] for c in result["callers"]}
58
+ self.assertIn("processRequest", caller_names)
59
+
60
+ def test_missing_symbol(self):
61
+ """Query for non-existent symbol should return error."""
62
+ result = blast_radius(self.conn, "nonexistent")
63
+ self.assertIn("error", result)
64
+
65
+ if __name__ == "__main__":
66
+ unittest.main()
File without changes
@@ -0,0 +1,16 @@
1
+ import { handleAuth } from './auth';
2
+
3
+ export function processRequest(req: Request): Response {
4
+ const user = handleAuth(req);
5
+ return formatResponse(user);
6
+ }
7
+
8
+ function formatResponse(data: any): Response {
9
+ return new Response(JSON.stringify(data));
10
+ }
11
+
12
+ export class ApiController {
13
+ handle(req: Request) {
14
+ return processRequest(req);
15
+ }
16
+ }
@@ -0,0 +1,15 @@
1
+ """Authentication module for the sample project."""
2
+
3
+ def handleAuth(request):
4
+ """Handle authentication requests."""
5
+ token = validateToken(request.token)
6
+ user = getUser(token.user_id)
7
+ return user
8
+
9
+ def validateToken(token):
10
+ """Validate a JWT token."""
11
+ return {"user_id": 42, "valid": True}
12
+
13
+ def getUser(user_id):
14
+ """Get user by ID from database."""
15
+ return {"id": user_id, "name": "Alice"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Utility functions for the sample project.
3
+ */
4
+
5
+ function formatDate(date) {
6
+ return date.toISOString();
7
+ }
8
+
9
+ function parseConfig(configStr) {
10
+ return JSON.parse(configStr);
11
+ }
12
+
13
+ const processData = (data) => {
14
+ const config = parseConfig(data.config);
15
+ return { ...data, config, timestamp: formatDate(new Date()) };
16
+ };