mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
fix: add Webhook to web constants + reformat for CI
- Add Webhook entry to NODE_COLORS and NODE_SIZES in gitnexus-web constants - Run prettier across all packages to fix format CI check - Resolves typecheck-web and format CI failures on PR #512 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bad1a12e4f
commit
ce889889a7
124 changed files with 1232 additions and 890 deletions
|
|
@ -38,6 +38,7 @@ export const NODE_COLORS: Record<NodeLabel, string> = {
|
|||
Template: '#a78bfa', // Violet light - like Type
|
||||
Route: '#f43f5e', // Rose - like Process
|
||||
Tool: '#a855f7', // Purple - like Project
|
||||
Webhook: '#06b6d4', // Cyan - event/integration handler
|
||||
};
|
||||
|
||||
// Node sizes by type - clear visual hierarchy with dramatic size differences
|
||||
|
|
@ -79,6 +80,7 @@ export const NODE_SIZES: Record<NodeLabel, number> = {
|
|||
Template: 3, // Like Type
|
||||
Route: 5, // Like Enum
|
||||
Tool: 5, // Like Enum
|
||||
Webhook: 5, // Like Enum - event handler
|
||||
};
|
||||
|
||||
// Community color palette for cluster-based coloring
|
||||
|
|
|
|||
|
|
@ -80,17 +80,20 @@ All notable changes to GitNexus will be documented in this file.
|
|||
## [1.5.2] - 2026-04-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`gitnexus-shared` module not found** — `gitnexus-shared` was a `file:` workspace dependency never published to npm, causing `ERR_MODULE_NOT_FOUND` when installing `gitnexus` globally. The build now bundles shared code into `dist/_shared/` and rewrites imports to relative paths (#613)
|
||||
- **v1.5.1 publish regression** — npm's `prepare` lifecycle ran `tsc` after `prepack`, overwriting the rewritten imports before packing; both scripts now run the full build so the final tarball is always correct
|
||||
|
||||
## [1.5.1] - 2026-04-01 [YANKED]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Incomplete fix for `gitnexus-shared` bundling — `prepare` script overwrote rewritten imports during publish
|
||||
|
||||
## [1.5.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
|
||||
- **Repo landing screen** — when the backend detects indexed repositories, the web UI now shows a landing page with selectable repo cards (name, stats, indexed date) instead of auto-loading the first repo; users can also analyze new repos directly from the landing screen (#607)
|
||||
- **Unified web & CLI ingestion pipeline** — complete architectural migration of the web app from a self-contained WASM browser app to a thin client backed by the CLI server; new `gitnexus-shared` package for cross-package type unification (#536)
|
||||
- New server endpoints: `/api/heartbeat` (SSE liveness), `/api/info`, `/api/repos`, `/api/file`, `/api/grep`, `/api/analyze` (SSE progress), `/api/embed`, `/api/mcp` (MCP-over-StreamableHTTP)
|
||||
|
|
@ -106,19 +109,23 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **ESLint v9** — flat config with `unused-imports` auto-removal, `@typescript-eslint` rules, React hooks rules, CI `lint` job (#564)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **OpenCode MCP configuration** — corrected README MCP setup for OpenCode which requires `command` as an array containing both executable and arguments (#363)
|
||||
- **litellm security** — excluded vulnerable versions 1.82.7 and 1.82.8 in eval harness `pyproject.toml` (#580)
|
||||
|
||||
### Changed
|
||||
|
||||
- **Reduced explicit `any` types** — 128 `no-explicit-any` warnings eliminated (689 → 561, 19% reduction) across `NodeProperties` index signature, ~80 `SyntaxNode` substitutions, typed worker protocol, and graphology community detection (#566)
|
||||
|
||||
### Docs
|
||||
|
||||
- Added `gitnexus-shared` build step to web UI quick start instructions (#585)
|
||||
- Added enterprise offering section to README (#579)
|
||||
|
||||
## [1.4.10] - 2026-03-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- **MCP server install via npx** — resolve tree-sitter peer dependency conflicts that broke `npx -y gitnexus@latest mcp` (#537, #538)
|
||||
- Downgrade tree-sitter from ^0.25.0 to ^0.21.1 (only npm version where all 14 parsers agree)
|
||||
- Align all parser versions to their highest ^0.21.x-compatible releases
|
||||
|
|
@ -129,6 +136,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
## [1.4.9] - 2026-03-26
|
||||
|
||||
### Added
|
||||
|
||||
- **COBOL language support** — standalone regex processor for fixed-format and free-format COBOL, JCL, COPY/REPLACING with pseudotext (#498)
|
||||
- 95% language feature coverage: CALL USING, EXEC SQL/CICS/DLI, DECLARATIVES, SET, INSPECT, INITIALIZE, STRING/UNSTRING, SORT/MERGE with INPUT/OUTPUT PROCEDURE, GO TO DEPENDING ON, MOVE CORRESPONDING, nested programs with per-program scoping
|
||||
- 90+ review findings resolved across 20 review cycles with 241 tests (180 unit + 61 integration)
|
||||
|
|
@ -145,11 +153,13 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **Filter panel** — additional node types (#519)
|
||||
|
||||
### Changed
|
||||
|
||||
- **BUILT_IN_NAMES** split into per-language provider fields (#523)
|
||||
- **tree-sitter** upgraded to 0.25.0 with all grammar packages (#516)
|
||||
- **Impact tool** — batched chunking and entry-point grouping for enrichment (#507)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **COBOL CRLF** — all `split('\n')` calls use `/\r?\n/` for Windows compatibility
|
||||
- **COBOL nested programs** — all graph edges (CALL, CANCEL, CICS, ENTRY, SQL, SEARCH) use `owningModuleId()` for correct attribution
|
||||
- **COBOL callAccum** — multi-line CALL USING with verb boundary detection, Area A paragraph guard, EXEC entry flush, division/END PROGRAM flush
|
||||
|
|
@ -162,6 +172,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
## [1.4.8] - 2026-03-23
|
||||
|
||||
### Added
|
||||
|
||||
- **Type resolution Milestone D — Phases 10–13** consolidated into a single milestone with full integration test coverage across 11 languages (#387)
|
||||
- Phase A/B/C: overload disambiguation via argument literal types, constructor-visible virtual dispatch via `constructorTypeMap`, `parameterTypes` extraction in `extractMethodSignature`
|
||||
- Phase 14 enhancements: single-pass seeding, Tarjan's SCC for cyclic resolution, cross-file return types
|
||||
|
|
@ -180,6 +191,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **Pre-commit hooks** via husky for typecheck + unit tests
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Python import alias resolution** — `import X as Y` now routes module aliases directly to `moduleAliasMap` in import processor (#417, #461)
|
||||
- **Python module-qualified calls** resolved via `moduleAliasMap` (#337)
|
||||
- **Python module-qualified constructor calls** (Issue #337)
|
||||
|
|
@ -201,17 +213,20 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **package-lock.json** synced with `onnxruntime-node ^1.24.0`
|
||||
|
||||
### Changed
|
||||
|
||||
- **Unified language dispatch** with compile-time exhaustive tables
|
||||
- **Prepare script simplified** — removed `scripts/prepare.cjs`
|
||||
- **Switched from .githooks to husky** for pre-commit hooks
|
||||
- **`@claude` workflow** restricted to maintainers and above via `author_association` check
|
||||
|
||||
### Performance
|
||||
|
||||
- **O(1) per-chunk synthesis guard** using `boolean[]` instead of Set
|
||||
- **`sizeBefore` optimization** in type resolution
|
||||
- **Token truncation** improvements
|
||||
|
||||
### Chore
|
||||
|
||||
- Strengthened Python module-import tests, un-skipped match/case, added perf guard
|
||||
- Added positive and negative tests for all 4 bug fixes
|
||||
- E2e tests for stale detection, sequential enrichment, stability (#396)
|
||||
|
|
@ -222,6 +237,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
## [1.4.7] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- **Phase 8 field/property type resolution** — ACCESSES edges with `declaredType` for field reads/writes (#354)
|
||||
- **Phase 9 return-type variable binding** — call-result variable binding across 11 languages (#379)
|
||||
- `extractPendingAssignment` in per-language type extractors captures `let x = getUser()` patterns
|
||||
|
|
@ -235,6 +251,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **MCP tool/resource descriptions** updated to reflect Phase 9 ACCESSES edge semantics and `declaredType` property
|
||||
|
||||
### Fixed
|
||||
|
||||
- **mcp**: server crashes under parallel tool calls (#326, #349)
|
||||
- **parsing**: undefined error on languages missing from call routers (#364)
|
||||
- **web**: add missing Kotlin entries to `Record<SupportedLanguages>` maps
|
||||
|
|
@ -243,12 +260,14 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **docs**: corrected stale "single-pass" claims in type-resolution-system.md to reflect walk+fixpoint architecture
|
||||
|
||||
### Changed
|
||||
|
||||
- **Upgrade `@ladybugdb/core` to 0.15.2** and remove segfault workarounds (#374)
|
||||
- **type-resolution-roadmap.md** overhauled — completed phases condensed to summaries, Phases 10–14 added with full engineering specs
|
||||
|
||||
## [1.4.6] - 2026-03-18
|
||||
|
||||
### Added
|
||||
|
||||
- **Phase 7 type resolution** — return-aware loop inference for call-expression iterables (#341)
|
||||
- `ReturnTypeLookup` interface with `lookupReturnType` / `lookupRawReturnType` split
|
||||
- `ForLoopExtractorContext` context object replacing positional `(node, env)` signature
|
||||
|
|
@ -261,6 +280,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- `pendingCallResults` infrastructure (dormant — Phase 9 work)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **impact**: return structured error + partial results instead of crashing (#345)
|
||||
- **impact**: add `HAS_METHOD` and `OVERRIDES` to `VALID_RELATION_TYPES` (#350)
|
||||
- **cli**: write tool output to stdout via fd 1 instead of stderr (#346)
|
||||
|
|
@ -271,11 +291,13 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- **docs**: correct `pendingCallResults` description in roadmap and system docs
|
||||
|
||||
### Chore
|
||||
|
||||
- Add `.worktrees/` to `.gitignore`
|
||||
|
||||
## [1.4.5] - 2026-03-17
|
||||
|
||||
### Added
|
||||
|
||||
- **Ruby language support** for CLI and web (#111)
|
||||
- **TypeEnvironment API** with constructor inference, self/this/super resolution (#274)
|
||||
- **Return type inference** with doc-comment parsing (JSDoc, PHPDoc, YARD) and per-language type extractors (#284)
|
||||
|
|
@ -297,10 +319,12 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- `hookTimeout: 120000` in vitest config for CI beforeAll hooks
|
||||
|
||||
### Changed
|
||||
|
||||
- **Migrated from KuzuDB to LadybugDB v0.15** (#275)
|
||||
- Dynamically discover and install agent skills in CLI (#270)
|
||||
|
||||
### Performance
|
||||
|
||||
- Worker pool threshold — skip worker creation for small repos (<15 files or <512KB total)
|
||||
- AST walk pruning via `SKIP_SUBTREE_TYPES` for leaf-only nodes (string, comment, number literals)
|
||||
- Pre-computed `interestingNodeTypes` set — single Set.has() replaces 3 checks per AST node
|
||||
|
|
@ -308,6 +332,7 @@ All notable changes to GitNexus will be documented in this file.
|
|||
- Replace `.children?.find()` with manual for loops in `extractFunctionName` to eliminate array allocations
|
||||
|
||||
### Fixed
|
||||
|
||||
- Same-directory Python import resolution (#328)
|
||||
- Ruby method-level call resolution, HAS_METHOD edges, and dispatch table (#278)
|
||||
- C++ fixture file casing for case-sensitive CI
|
||||
|
|
|
|||
|
|
@ -30,20 +30,20 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
|
|||
|
||||
### Editor Support
|
||||
|
||||
| Editor | MCP | Skills | Hooks (auto-augment) | Support |
|
||||
|--------|-----|--------|---------------------|---------|
|
||||
| **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
|
||||
| **Cursor** | Yes | Yes | — | MCP + Skills |
|
||||
| **Codex** | Yes | Yes | — | MCP + Skills |
|
||||
| **Windsurf** | Yes | — | — | MCP |
|
||||
| **OpenCode** | Yes | Yes | — | MCP + Skills |
|
||||
| Editor | MCP | Skills | Hooks (auto-augment) | Support |
|
||||
| --------------- | --- | ------ | -------------------- | ------------ |
|
||||
| **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
|
||||
| **Cursor** | Yes | Yes | — | MCP + Skills |
|
||||
| **Codex** | Yes | Yes | — | MCP + Skills |
|
||||
| **Windsurf** | Yes | — | — | MCP |
|
||||
| **OpenCode** | Yes | Yes | — | MCP + Skills |
|
||||
|
||||
> **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context.
|
||||
|
||||
### Community Integrations
|
||||
|
||||
| Agent | Install | Source |
|
||||
|-------|---------|--------|
|
||||
| Agent | Install | Source |
|
||||
| -------------------- | ---------------------------- | ------------------------------------------------------- |
|
||||
| [pi](https://pi.dev) | `pi install npm:pi-gitnexus` | [pi-gitnexus](https://github.com/tintinweb/pi-gitnexus) |
|
||||
|
||||
## MCP Setup (manual)
|
||||
|
|
@ -115,36 +115,36 @@ The result is a **LadybugDB graph database** stored locally in `.gitnexus/` with
|
|||
|
||||
Your AI agent gets these tools automatically:
|
||||
|
||||
| Tool | What It Does | `repo` Param |
|
||||
|------|-------------|--------------|
|
||||
| `list_repos` | Discover all indexed repositories | — |
|
||||
| `query` | Process-grouped hybrid search (BM25 + semantic + RRF) | Optional |
|
||||
| `context` | 360-degree symbol view — categorized refs, process participation | Optional |
|
||||
| `impact` | Blast radius analysis with depth grouping and confidence | Optional |
|
||||
| `detect_changes` | Git-diff impact — maps changed lines to affected processes | Optional |
|
||||
| `rename` | Multi-file coordinated rename with graph + text search | Optional |
|
||||
| `cypher` | Raw Cypher graph queries | Optional |
|
||||
| Tool | What It Does | `repo` Param |
|
||||
| ---------------- | ---------------------------------------------------------------- | ------------ |
|
||||
| `list_repos` | Discover all indexed repositories | — |
|
||||
| `query` | Process-grouped hybrid search (BM25 + semantic + RRF) | Optional |
|
||||
| `context` | 360-degree symbol view — categorized refs, process participation | Optional |
|
||||
| `impact` | Blast radius analysis with depth grouping and confidence | Optional |
|
||||
| `detect_changes` | Git-diff impact — maps changed lines to affected processes | Optional |
|
||||
| `rename` | Multi-file coordinated rename with graph + text search | Optional |
|
||||
| `cypher` | Raw Cypher graph queries | Optional |
|
||||
|
||||
> With one indexed repo, the `repo` param is optional. With multiple, specify which: `query({query: "auth", repo: "my-app"})`.
|
||||
|
||||
## MCP Resources
|
||||
|
||||
| Resource | Purpose |
|
||||
|----------|---------|
|
||||
| `gitnexus://repos` | List all indexed repositories (read first) |
|
||||
| `gitnexus://repo/{name}/context` | Codebase stats, staleness check, and available tools |
|
||||
| `gitnexus://repo/{name}/clusters` | All functional clusters with cohesion scores |
|
||||
| `gitnexus://repo/{name}/cluster/{name}` | Cluster members and details |
|
||||
| `gitnexus://repo/{name}/processes` | All execution flows |
|
||||
| `gitnexus://repo/{name}/process/{name}` | Full process trace with steps |
|
||||
| `gitnexus://repo/{name}/schema` | Graph schema for Cypher queries |
|
||||
| Resource | Purpose |
|
||||
| --------------------------------------- | ---------------------------------------------------- |
|
||||
| `gitnexus://repos` | List all indexed repositories (read first) |
|
||||
| `gitnexus://repo/{name}/context` | Codebase stats, staleness check, and available tools |
|
||||
| `gitnexus://repo/{name}/clusters` | All functional clusters with cohesion scores |
|
||||
| `gitnexus://repo/{name}/cluster/{name}` | Cluster members and details |
|
||||
| `gitnexus://repo/{name}/processes` | All execution flows |
|
||||
| `gitnexus://repo/{name}/process/{name}` | Full process trace with steps |
|
||||
| `gitnexus://repo/{name}/schema` | Graph schema for Cypher queries |
|
||||
|
||||
## MCP Prompts
|
||||
|
||||
| Prompt | What It Does |
|
||||
|--------|-------------|
|
||||
| `detect_impact` | Pre-commit change analysis — scope, affected processes, risk level |
|
||||
| `generate_map` | Architecture documentation from the knowledge graph with mermaid diagrams |
|
||||
| Prompt | What It Does |
|
||||
| --------------- | ------------------------------------------------------------------------- |
|
||||
| `detect_impact` | Pre-commit change analysis — scope, affected processes, risk level |
|
||||
| `generate_map` | Architecture documentation from the knowledge graph with mermaid diagrams |
|
||||
|
||||
## CLI Commands
|
||||
|
||||
|
|
@ -200,21 +200,21 @@ TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift,
|
|||
|
||||
### Language Feature Matrix
|
||||
|
||||
| Language | Imports | Named Bindings | Exports | Heritage | Type Annotations | Constructor Inference | Config | Frameworks | Entry Points |
|
||||
|----------|---------|----------------|---------|----------|-----------------|---------------------|--------|------------|-------------|
|
||||
| TypeScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| JavaScript | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ |
|
||||
| Python | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Java | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Kotlin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| C# | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Go | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Rust | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| PHP | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Ruby | ✓ | — | ✓ | ✓ | — | ✓ | — | ✓ | ✓ |
|
||||
| Swift | — | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| C | — | — | ✓ | — | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Language | Imports | Named Bindings | Exports | Heritage | Type Annotations | Constructor Inference | Config | Frameworks | Entry Points |
|
||||
| ---------- | ------- | -------------- | ------- | -------- | ---------------- | --------------------- | ------ | ---------- | ------------ |
|
||||
| TypeScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| JavaScript | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ |
|
||||
| Python | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Java | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Kotlin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| C# | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Go | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Rust | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| PHP | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Ruby | ✓ | — | ✓ | ✓ | — | ✓ | — | ✓ | ✓ |
|
||||
| Swift | — | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| C | — | — | ✓ | — | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
|
||||
**Imports** — cross-file import resolution · **Named Bindings** — `import { X as Y }` / re-export tracking · **Exports** — public/exported symbol detection · **Heritage** — class inheritance, interfaces, mixins · **Type Annotations** — explicit type extraction for receiver resolution · **Constructor Inference** — infer receiver type from constructor calls (`self`/`this` resolution included for all languages) · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-cli
|
||||
description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\""
|
||||
description: 'Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: "Index this repo", "Reanalyze the codebase", "Generate a wiki"'
|
||||
---
|
||||
|
||||
# GitNexus CLI Commands
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-debugging
|
||||
description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\""
|
||||
description: 'Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: "Why is X failing?", "Where does this error come from?", "Trace this bug"'
|
||||
---
|
||||
|
||||
# Debugging with GitNexus
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-exploring
|
||||
description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\""
|
||||
description: 'Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: "How does X work?", "What calls this function?", "Show me the auth flow"'
|
||||
---
|
||||
|
||||
# Exploring Codebases with GitNexus
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-guide
|
||||
description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\""
|
||||
description: 'Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: "What GitNexus tools are available?", "How do I use GitNexus?"'
|
||||
---
|
||||
|
||||
# GitNexus Guide
|
||||
|
|
@ -19,8 +19,8 @@ For any task involving code understanding, debugging, impact analysis, or refact
|
|||
|
||||
## Skills
|
||||
|
||||
| Task | Skill to read |
|
||||
| -------------------------------------------- | ------------------- |
|
||||
| Task | Skill to read |
|
||||
| -------------------------------------------- | ---------------------------- |
|
||||
| Understand architecture / "How does X work?" | `gitnexus-exploring` |
|
||||
| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` |
|
||||
| Trace bugs / "Why is X failing?" | `gitnexus-debugging` |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-impact-analysis
|
||||
description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\""
|
||||
description: 'Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: "Is it safe to change X?", "What depends on this?", "What will break?"'
|
||||
---
|
||||
|
||||
# Impact Analysis with GitNexus
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-pr-review
|
||||
description: "Use when the user wants to review a pull request, understand what a PR changes, assess risk of merging, or check for missing test coverage. Examples: \"Review this PR\", \"What does PR #42 change?\", \"Is this PR safe to merge?\""
|
||||
description: 'Use when the user wants to review a pull request, understand what a PR changes, assess risk of merging, or check for missing test coverage. Examples: "Review this PR", "What does PR #42 change?", "Is this PR safe to merge?"'
|
||||
---
|
||||
|
||||
# PR Review with GitNexus
|
||||
|
|
@ -43,23 +43,23 @@ description: "Use when the user wants to review a pull request, understand what
|
|||
|
||||
## Review Dimensions
|
||||
|
||||
| Dimension | How GitNexus Helps |
|
||||
| --- | --- |
|
||||
| **Correctness** | `context` shows callers — are they all compatible with the change? |
|
||||
| **Blast radius** | `impact` shows d=1/d=2/d=3 dependents — anything missed? |
|
||||
| **Completeness** | `detect_changes` shows all affected flows — are they all handled? |
|
||||
| **Test coverage** | `impact({includeTests: true})` shows which tests touch changed code |
|
||||
| Dimension | How GitNexus Helps |
|
||||
| -------------------- | --------------------------------------------------------------------- |
|
||||
| **Correctness** | `context` shows callers — are they all compatible with the change? |
|
||||
| **Blast radius** | `impact` shows d=1/d=2/d=3 dependents — anything missed? |
|
||||
| **Completeness** | `detect_changes` shows all affected flows — are they all handled? |
|
||||
| **Test coverage** | `impact({includeTests: true})` shows which tests touch changed code |
|
||||
| **Breaking changes** | d=1 upstream items that aren't updated in the PR = potential breakage |
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Signal | Risk |
|
||||
| --- | --- |
|
||||
| Changes touch <3 symbols, 0-1 processes | LOW |
|
||||
| Changes touch 3-10 symbols, 2-5 processes | MEDIUM |
|
||||
| Changes touch >10 symbols or many processes | HIGH |
|
||||
| Changes touch auth, payments, or data integrity code | CRITICAL |
|
||||
| d=1 callers exist outside the PR diff | Potential breakage — flag it |
|
||||
| Signal | Risk |
|
||||
| ---------------------------------------------------- | ---------------------------- |
|
||||
| Changes touch <3 symbols, 0-1 processes | LOW |
|
||||
| Changes touch 3-10 symbols, 2-5 processes | MEDIUM |
|
||||
| Changes touch >10 symbols or many processes | HIGH |
|
||||
| Changes touch auth, payments, or data integrity code | CRITICAL |
|
||||
| d=1 callers exist outside the PR diff | Potential breakage — flag it |
|
||||
|
||||
## Tools
|
||||
|
||||
|
|
@ -146,18 +146,22 @@ Structure your review as:
|
|||
**Risk: LOW / MEDIUM / HIGH / CRITICAL**
|
||||
|
||||
### Changes Summary
|
||||
|
||||
- <N> symbols changed across <M> files
|
||||
- <P> execution flows affected
|
||||
|
||||
### Findings
|
||||
|
||||
1. **[severity]** Description of finding
|
||||
- Evidence from GitNexus tools
|
||||
- Affected callers/flows
|
||||
|
||||
### Missing Coverage
|
||||
|
||||
- Callers not updated in PR: ...
|
||||
- Untested flows: ...
|
||||
|
||||
### Recommendation
|
||||
|
||||
APPROVE / REQUEST CHANGES / NEEDS DISCUSSION
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: gitnexus-refactoring
|
||||
description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\""
|
||||
description: 'Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: "Rename this function", "Extract this into a module", "Refactor this class", "Move this to a separate file"'
|
||||
---
|
||||
|
||||
# Refactoring with GitNexus
|
||||
|
|
|
|||
|
|
@ -1361,12 +1361,20 @@ export function extractWebhooks(filePath: string, content: string, out: Extracte
|
|||
const lowerPath = filePath.toLowerCase();
|
||||
|
||||
// Skip test files — they often mock webhook patterns (e.g. Stripe constructEvent in test mocks)
|
||||
if (lowerPath.includes('/test') || lowerPath.includes('.test.') || lowerPath.includes('.spec.') || lowerPath.includes('__test')) return;
|
||||
if (
|
||||
lowerPath.includes('/test') ||
|
||||
lowerPath.includes('.test.') ||
|
||||
lowerPath.includes('.spec.') ||
|
||||
lowerPath.includes('__test')
|
||||
)
|
||||
return;
|
||||
|
||||
// Count newlines before offset without allocating substring + split array
|
||||
const lineAt = (offset: number): number => {
|
||||
let n = 0;
|
||||
for (let i = 0; i < offset; i++) { if (content.charCodeAt(i) === 10) n++; }
|
||||
for (let i = 0; i < offset; i++) {
|
||||
if (content.charCodeAt(i) === 10) n++;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
|
|
@ -1374,33 +1382,58 @@ export function extractWebhooks(filePath: string, content: string, out: Extracte
|
|||
const eventTypes: string[] = [];
|
||||
const caseRe = /case\s+['"]([.\w-]+)['"]/g;
|
||||
let m;
|
||||
while ((m = caseRe.exec(content)) !== null) { eventTypes.push(m[1]); }
|
||||
while ((m = caseRe.exec(content)) !== null) {
|
||||
eventTypes.push(m[1]);
|
||||
}
|
||||
const idx = content.indexOf('constructEvent');
|
||||
out.push({ filePath, name: 'stripe-webhook', kind: 'stripe', eventTypes,
|
||||
lineNumber: idx > -1 ? lineAt(idx) : 0 });
|
||||
out.push({
|
||||
filePath,
|
||||
name: 'stripe-webhook',
|
||||
kind: 'stripe',
|
||||
eventTypes,
|
||||
lineNumber: idx > -1 ? lineAt(idx) : 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const edgeFnMatch = filePath.match(/supabase\/functions\/([\w-]+)\/index\.ts$/);
|
||||
if (edgeFnMatch && content.includes('Deno.serve')) {
|
||||
out.push({ filePath, name: edgeFnMatch[1], kind: 'edge-function', eventTypes: [],
|
||||
lineNumber: lineAt(content.indexOf('Deno.serve')) });
|
||||
out.push({
|
||||
filePath,
|
||||
name: edgeFnMatch[1],
|
||||
kind: 'edge-function',
|
||||
eventTypes: [],
|
||||
lineNumber: lineAt(content.indexOf('Deno.serve')),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (content.includes('postgres_changes')) {
|
||||
const channelRe = /\.channel\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
||||
let m;
|
||||
while ((m = channelRe.exec(content)) !== null) {
|
||||
out.push({ filePath, name: `realtime:${m[1]}`, kind: 'realtime', eventTypes: [],
|
||||
lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
name: `realtime:${m[1]}`,
|
||||
kind: 'realtime',
|
||||
eventTypes: [],
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (lowerPath.includes('webhook') || lowerPath.includes('hook')) {
|
||||
const hasSigVerify = content.includes('x-webhook-signature') || content.includes('x-hub-signature')
|
||||
|| content.includes('verify');
|
||||
const hasSigVerify =
|
||||
content.includes('x-webhook-signature') ||
|
||||
content.includes('x-hub-signature') ||
|
||||
content.includes('verify');
|
||||
if (hasSigVerify) {
|
||||
const pathParts = filePath.split('/');
|
||||
const name = pathParts[pathParts.length - 1].replace(/\.(ts|js|tsx|jsx)$/, '');
|
||||
out.push({ filePath, name: `webhook:${name}`, kind: 'generic', eventTypes: [], lineNumber: 0 });
|
||||
out.push({
|
||||
filePath,
|
||||
name: `webhook:${name}`,
|
||||
kind: 'generic',
|
||||
eventTypes: [],
|
||||
lineNumber: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1409,13 +1442,25 @@ export function extractWebhooks(filePath: string, content: string, out: Extracte
|
|||
* Extract message-queue producer/consumer patterns from source code.
|
||||
* Detects: BullMQ, RabbitMQ (amqplib), Kafka (kafkajs), AWS SQS, Celery.
|
||||
*/
|
||||
export function extractQueuePatterns(filePath: string, content: string, out: ExtractedQueuePattern[]): void {
|
||||
export function extractQueuePatterns(
|
||||
filePath: string,
|
||||
content: string,
|
||||
out: ExtractedQueuePattern[],
|
||||
): void {
|
||||
const lowerPath = filePath.toLowerCase();
|
||||
if (lowerPath.includes('/test') || lowerPath.includes('.test.') || lowerPath.includes('.spec.') || lowerPath.includes('__test')) return;
|
||||
if (
|
||||
lowerPath.includes('/test') ||
|
||||
lowerPath.includes('.test.') ||
|
||||
lowerPath.includes('.spec.') ||
|
||||
lowerPath.includes('__test')
|
||||
)
|
||||
return;
|
||||
|
||||
const lineAt = (offset: number): number => {
|
||||
let n = 0;
|
||||
for (let i = 0; i < offset; i++) { if (content.charCodeAt(i) === 10) n++; }
|
||||
for (let i = 0; i < offset; i++) {
|
||||
if (content.charCodeAt(i) === 10) n++;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
|
|
@ -1423,59 +1468,132 @@ export function extractQueuePatterns(filePath: string, content: string, out: Ext
|
|||
const bullQueueRe = /new\s+Queue\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
||||
let m;
|
||||
while ((m = bullQueueRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'producer', queueName: m[1], method: 'Queue', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'producer',
|
||||
queueName: m[1],
|
||||
method: 'Queue',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const bullWorkerRe = /new\s+Worker\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = bullWorkerRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'consumer', queueName: m[1], method: 'Worker', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'consumer',
|
||||
queueName: m[1],
|
||||
method: 'Worker',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const bullProcessRe = /\.process\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = bullProcessRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'consumer', queueName: m[1], method: 'process', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'consumer',
|
||||
queueName: m[1],
|
||||
method: 'process',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
|
||||
// RabbitMQ (amqplib) — sendToQueue / publish = producer, consume / assertQueue + consume = consumer
|
||||
const amqpSendRe = /\.sendToQueue\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = amqpSendRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'producer', queueName: m[1], method: 'sendToQueue', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'producer',
|
||||
queueName: m[1],
|
||||
method: 'sendToQueue',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const amqpPublishRe = /\.publish\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = amqpPublishRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'producer', queueName: `${m[1]}/${m[2]}`, method: 'publish', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'producer',
|
||||
queueName: `${m[1]}/${m[2]}`,
|
||||
method: 'publish',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const amqpConsumeRe = /\.consume\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = amqpConsumeRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'consumer', queueName: m[1], method: 'consume', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'consumer',
|
||||
queueName: m[1],
|
||||
method: 'consume',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
|
||||
// Kafka (kafkajs) — producer.send({topic: '...'}) = producer, consumer.subscribe({topic: '...'}) = consumer
|
||||
const kafkaSendRe = /\.send\s*\(\s*\{[^}]*topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = kafkaSendRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'producer', queueName: m[1], method: 'send', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'producer',
|
||||
queueName: m[1],
|
||||
method: 'send',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const kafkaSubRe = /\.subscribe\s*\(\s*\{[^}]*topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = kafkaSubRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'consumer', queueName: m[1], method: 'subscribe', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'consumer',
|
||||
queueName: m[1],
|
||||
method: 'subscribe',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
|
||||
// AWS SQS — sendMessage/sendMessageBatch with QueueUrl = producer, receiveMessage = consumer
|
||||
const sqsSendRe = /sendMessage(?:Batch)?\s*\(\s*\{[^}]*QueueUrl\s*:\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = sqsSendRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'producer', queueName: m[1], method: 'sendMessage', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'producer',
|
||||
queueName: m[1],
|
||||
method: 'sendMessage',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const sqsRecvRe = /receiveMessage\s*\(\s*\{[^}]*QueueUrl\s*:\s*['"`]([^'"`]+)['"`]/g;
|
||||
while ((m = sqsRecvRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'consumer', queueName: m[1], method: 'receiveMessage', lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'consumer',
|
||||
queueName: m[1],
|
||||
method: 'receiveMessage',
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
|
||||
// Celery (Python) — @app.task / .delay() / .apply_async()
|
||||
const celeryTaskRe = /@(?:app|celery)\.task[^]*?def\s+(\w+)/g;
|
||||
while ((m = celeryTaskRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'consumer', queueName: m[1], method: 'task', handlerName: m[1], lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'consumer',
|
||||
queueName: m[1],
|
||||
method: 'task',
|
||||
handlerName: m[1],
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
const celeryCallRe = /(\w+)\.(delay|apply_async)\s*\(/g;
|
||||
while ((m = celeryCallRe.exec(content)) !== null) {
|
||||
out.push({ filePath, role: 'producer', queueName: m[1], method: m[2], lineNumber: lineAt(m.index) });
|
||||
out.push({
|
||||
filePath,
|
||||
role: 'producer',
|
||||
queueName: m[1],
|
||||
method: m[2],
|
||||
lineNumber: lineAt(m.index),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -452,14 +452,14 @@ export const streamAllCSVsToDisk = async (
|
|||
);
|
||||
break;
|
||||
case 'Webhook': {
|
||||
const eventTypes = node.properties.eventTypes as string[] || [];
|
||||
const eventTypes = (node.properties.eventTypes as string[]) || [];
|
||||
const eventTypesStr = `[${eventTypes.map((e: string) => `'${e.replace(/'/g, "''")}'`).join(',')}]`;
|
||||
await webhookWriter.addRow(
|
||||
[
|
||||
escapeCSVField(node.id),
|
||||
escapeCSVField(node.properties.name || ''),
|
||||
escapeCSVField(node.properties.filePath || ''),
|
||||
escapeCSVField(node.properties.kind as string || ''),
|
||||
escapeCSVField((node.properties.kind as string) || ''),
|
||||
escapeCSVField(eventTypesStr),
|
||||
].join(','),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3221,27 +3221,44 @@ export class LocalBackend {
|
|||
};
|
||||
}
|
||||
|
||||
private async webhookMap(repo: RepoHandle, params: { kind?: string; name?: string }): Promise<any> {
|
||||
private async webhookMap(
|
||||
repo: RepoHandle,
|
||||
params: { kind?: string; name?: string },
|
||||
): Promise<any> {
|
||||
await this.ensureInitialized(repo.id);
|
||||
const kindFilter = params.kind ? `AND n.kind = $kind` : '';
|
||||
const nameFilter = params.name ? `AND n.name CONTAINS $name` : '';
|
||||
const queryParams: Record<string, any> = {};
|
||||
if (params.kind) queryParams.kind = params.kind;
|
||||
if (params.name) queryParams.name = params.name;
|
||||
const rows = await executeParameterized(repo.id, `
|
||||
const rows = await executeParameterized(
|
||||
repo.id,
|
||||
`
|
||||
MATCH (n:Webhook)
|
||||
WHERE n.id STARTS WITH 'Webhook:' ${kindFilter} ${nameFilter}
|
||||
RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.kind AS kind, n.eventTypes AS eventTypes
|
||||
`, queryParams);
|
||||
const triggerRows = await executeParameterized(repo.id, `
|
||||
`,
|
||||
queryParams,
|
||||
);
|
||||
const triggerRows = await executeParameterized(
|
||||
repo.id,
|
||||
`
|
||||
MATCH (f:File)-[r:CodeRelation {type: 'TRIGGERS'}]->(w:Webhook)
|
||||
RETURN f.filePath AS source, w.name AS target, r.reason AS reason
|
||||
`, {});
|
||||
`,
|
||||
{},
|
||||
);
|
||||
const webhooks = rows.map((r: any) => ({
|
||||
id: r.id, name: r.name, filePath: r.filePath, kind: r.kind, eventTypes: r.eventTypes || [],
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
filePath: r.filePath,
|
||||
kind: r.kind,
|
||||
eventTypes: r.eventTypes || [],
|
||||
}));
|
||||
const triggers = triggerRows.map((r: any) => ({
|
||||
source: r.source, target: r.target, reason: r.reason,
|
||||
source: r.source,
|
||||
target: r.target,
|
||||
reason: r.reason,
|
||||
}));
|
||||
return { webhooks, triggers, total: webhooks.length };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
export class User {
|
||||
save() {}
|
||||
getName() { return ''; }
|
||||
getName() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function getUser() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export class Config {
|
||||
validate(): boolean { return true; }
|
||||
validate(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export function getConfig(): Config {
|
||||
return new Config();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export class Config {
|
||||
validate(): boolean { return true; }
|
||||
validate(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export function getConfig(): Config {
|
||||
return new Config();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
export class User {
|
||||
save(): void {}
|
||||
getName(): string { return ''; }
|
||||
getName(): string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
export function getUser(): User {
|
||||
return new User();
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
import { Slot } from 'expo-router';
|
||||
export default function AuthLayout() { return <Slot />; }
|
||||
export default function AuthLayout() {
|
||||
return <Slot />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
import { useRouter } from 'expo-router';
|
||||
export default function Login() { const router = useRouter(); router.push('/'); return null; }
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
router.push('/');
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
import { Slot } from 'expo-router';
|
||||
export default function Layout() { return <Slot />; }
|
||||
export default function Layout() {
|
||||
return <Slot />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export default function Home() { return null; }
|
||||
export default function Home() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export default function Profile() { return null; }
|
||||
export default function Profile() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
import { Link } from 'expo-router';
|
||||
export default function Settings() { return <Link href="/profile">Go</Link>; }
|
||||
export default function Settings() {
|
||||
return <Link href="/profile">Go</Link>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
import { useLocalSearchParams } from 'expo-router';
|
||||
export default function UserDetail() { const { id } = useLocalSearchParams(); return null; }
|
||||
export default function UserDetail() {
|
||||
const { id } = useLocalSearchParams();
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
7
gitnexus/test/fixtures/expo-app/package.json
vendored
7
gitnexus/test/fixtures/expo-app/package.json
vendored
|
|
@ -1 +1,6 @@
|
|||
{"name": "expo-app-fixture", "dependencies": {"expo-router": "^3.0.0"}}
|
||||
{
|
||||
"name": "expo-app-fixture",
|
||||
"dependencies": {
|
||||
"expo-router": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
gitnexus/test/fixtures/group/group.yaml
vendored
2
gitnexus/test/fixtures/group/group.yaml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
# Test group for integration / fixture-driven tests
|
||||
version: 1
|
||||
name: test-group
|
||||
description: "Test group for integration tests"
|
||||
description: 'Test group for integration tests'
|
||||
|
||||
repos:
|
||||
app/backend: test-backend
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
{ "name": "@monorepo/auth", "version": "1.0.0" }
|
||||
{
|
||||
"name": "@monorepo/auth",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
{ "name": "@monorepo/gateway", "version": "1.0.0" }
|
||||
{
|
||||
"name": "@monorepo/gateway",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
{ "name": "@monorepo/orders", "version": "1.0.0" }
|
||||
{
|
||||
"name": "@monorepo/orders",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ export async function GET() {
|
|||
const grants = await fetchGrants();
|
||||
return NextResponse.json({ data: grants, pagination: { page: 1, total: grants.length } });
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: 'Failed to fetch grants', message: String(err) }, { status: 400 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch grants', message: String(err) },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { NextResponse } from 'next/server';
|
|||
import { withAuth } from '../../../middleware/auth';
|
||||
import { withRateLimit } from '../../../middleware/rate-limit';
|
||||
|
||||
export const GET = withAuth(withRateLimit(async (req: Request) => {
|
||||
return NextResponse.json({ items: [], count: 0 });
|
||||
}));
|
||||
export const GET = withAuth(
|
||||
withRateLimit(async (req: Request) => {
|
||||
return NextResponse.json({ items: [], count: 0 });
|
||||
}),
|
||||
);
|
||||
|
||||
export const POST = async (req: Request) => {
|
||||
const body = await req.json();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ export async function GrantsList() {
|
|||
const { data, pagination } = await res.json();
|
||||
return (
|
||||
<ul>
|
||||
{data.map((g: any) => <li key={g.id}>{g.title}</li>)}
|
||||
{data.map((g: any) => (
|
||||
<li key={g.id}>{g.title}</li>
|
||||
))}
|
||||
<p>Page {pagination.page}</p>
|
||||
</ul>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
export async function useMulti() {
|
||||
const [grantsRes, secureRes] = await Promise.all([
|
||||
fetch('/api/grants'),
|
||||
fetch('/api/secure'),
|
||||
]);
|
||||
const [grantsRes, secureRes] = await Promise.all([fetch('/api/grants'), fetch('/api/secure')]);
|
||||
const grants = await grantsRes.json();
|
||||
const secure = await secureRes.json();
|
||||
return { grants: grants.data, secure: secure.items, meta: grants.meta };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
export class ApiError extends Error {
|
||||
constructor(public statusCode: number, message: string) {
|
||||
constructor(
|
||||
public statusCode: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
export async function fetchGrants() {
|
||||
return [{ id: 1, title: 'Grant A' }, { id: 2, title: 'Grant B' }];
|
||||
return [
|
||||
{ id: 1, title: 'Grant A' },
|
||||
{ id: 2, title: 'Grant B' },
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper1() { return 1; }
|
||||
export function helper1() {
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper10() { return 10; }
|
||||
export function helper10() {
|
||||
return 10;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper11() { return 11; }
|
||||
export function helper11() {
|
||||
return 11;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper12() { return 12; }
|
||||
export function helper12() {
|
||||
return 12;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper13() { return 13; }
|
||||
export function helper13() {
|
||||
return 13;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper2() { return 2; }
|
||||
export function helper2() {
|
||||
return 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper3() { return 3; }
|
||||
export function helper3() {
|
||||
return 3;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper4() { return 4; }
|
||||
export function helper4() {
|
||||
return 4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper5() { return 5; }
|
||||
export function helper5() {
|
||||
return 5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper6() { return 6; }
|
||||
export function helper6() {
|
||||
return 6;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper7() { return 7; }
|
||||
export function helper7() {
|
||||
return 7;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper8() { return 8; }
|
||||
export function helper8() {
|
||||
return 8;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
export function helper9() { return 9; }
|
||||
export function helper9() {
|
||||
return 9;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
class Animal {
|
||||
speak() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
speak() {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
static classify(name) {
|
||||
return "mammal";
|
||||
}
|
||||
static classify(name) {
|
||||
return 'mammal';
|
||||
}
|
||||
|
||||
breathe() {
|
||||
return true;
|
||||
}
|
||||
breathe() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Dog extends Animal {
|
||||
speak() {
|
||||
return "woof";
|
||||
}
|
||||
speak() {
|
||||
return 'woof';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Animal, Dog };
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ const { Animal, Dog } = require('./animal');
|
|||
|
||||
const dog = new Dog();
|
||||
const sound = dog.speak();
|
||||
const category = Animal.classify("dog");
|
||||
const category = Animal.classify('dog');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class BaseModel {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { BaseModel };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const { BaseModel } = require('./Base');
|
||||
|
||||
class User extends BaseModel {
|
||||
serialize() { return ''; }
|
||||
serialize() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
module.exports = { User };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class Repo {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { Repo };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
class User {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
process() {
|
||||
this.save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class BaseModel {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { BaseModel };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class Repo {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { Repo };
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ class User {
|
|||
/**
|
||||
* @returns {User[]}
|
||||
*/
|
||||
function getUsers() { return []; }
|
||||
function getUsers() {
|
||||
return [];
|
||||
}
|
||||
|
||||
module.exports = { User, getUsers };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class Repo {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { Repo };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class User {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { User };
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
*/
|
||||
class Address {
|
||||
/** @returns {boolean} */
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ const { User } = require('./models');
|
|||
/**
|
||||
* @returns {User}
|
||||
*/
|
||||
function getUser() { return new User(); }
|
||||
function getUser() {
|
||||
return new User();
|
||||
}
|
||||
|
||||
module.exports = { getUser };
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
class Address {
|
||||
city = '';
|
||||
city = '';
|
||||
}
|
||||
|
||||
class User {
|
||||
name = '';
|
||||
address = null;
|
||||
name = '';
|
||||
address = null;
|
||||
}
|
||||
|
||||
module.exports = { Address, User };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const { User, Address } = require('./models');
|
||||
|
||||
function updateUser() {
|
||||
const user = new User();
|
||||
user.name = "Alice";
|
||||
user.address = new Address();
|
||||
const user = new User();
|
||||
user.name = 'Alice';
|
||||
user.address = new Address();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export function GrantsList({ slug }: { slug: string }) {
|
||||
const data = fetch(`/api/organizations/${slug}/grants`).then(r => r.json());
|
||||
const data = fetch(`/api/organizations/${slug}/grants`).then((r) => r.json());
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export function useGrants() {
|
||||
const data = fetch('/api/grants').then(r => r.json());
|
||||
const data = fetch('/api/grants').then((r) => r.json());
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
{"autoload":{"psr-4":{"App\\\\":"src/"}}}
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
{"autoload":{"psr-4":{"App\\\\":"src/"}}}
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
{"autoload":{"psr-4":{"App\\\\":"src/"}}}
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": { "App\\": "app/" }
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": { "App\\": "app/" }
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"autoload": {
|
||||
"psr-4": { "App\\": "app/" }
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// TypeScript middleware — should NOT be resolved by Python imports
|
||||
export function handleRequest(req: Request): Response {
|
||||
return new Response("ok");
|
||||
return new Response('ok');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ import { NextResponse } from 'next/server';
|
|||
export async function GET() {
|
||||
const courses = await getCourses();
|
||||
const articles = await getArticles();
|
||||
return NextResponse.json({ 'courses': courses, 'articles': articles });
|
||||
return NextResponse.json({ courses: courses, articles: articles });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export function GdprExport() {
|
||||
const data = fetch('/api/gdpr/export', { method: 'POST' }).then(r => r.json());
|
||||
const data = fetch('/api/gdpr/export', { method: 'POST' }).then((r) => r.json());
|
||||
const link = document.createElement('a');
|
||||
link.href = data.url;
|
||||
document.body.appendChild(link);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export function SearchBar() {
|
||||
const data = fetch('/api/search').then(r => r.json());
|
||||
const data = fetch('/api/search').then((r) => r.json());
|
||||
console.log(data.courses);
|
||||
console.log(data.articles);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export function UserList() {
|
||||
const res = fetch('/api/users').then(r => r.json());
|
||||
const res = fetch('/api/users').then((r) => r.json());
|
||||
const items = res.data;
|
||||
const err = res.error;
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { User } from './user';
|
||||
import { Repo } from './repo';
|
||||
|
||||
function getUser(): User { return new User(); }
|
||||
function getRepo(): Repo { return new Repo(); }
|
||||
function getUser(): User {
|
||||
return new User();
|
||||
}
|
||||
function getRepo(): Repo {
|
||||
return new Repo();
|
||||
}
|
||||
|
||||
export function processEntities(): void {
|
||||
const u: User = getUser();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Greeting } from './greeting';
|
||||
|
||||
export class A {
|
||||
greet(): Greeting { return new Greeting(); }
|
||||
greet(): Greeting {
|
||||
return new Greeting();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { User } from './user';
|
||||
import { Repo } from './repo';
|
||||
|
||||
function getUser(): User { return new User(); }
|
||||
function getRepo(): Repo { return new Repo(); }
|
||||
function getUser(): User {
|
||||
return new User();
|
||||
}
|
||||
function getRepo(): Repo {
|
||||
return new Repo();
|
||||
}
|
||||
|
||||
// Multi-hop forward-declared chain: a → b → c (source order)
|
||||
// All three should resolve because the post-walk pass processes in order.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function processUndefined(x: User | undefined) {
|
|||
}
|
||||
}
|
||||
|
||||
const processFuncExpr = function(x: User | null) {
|
||||
const processFuncExpr = function (x: User | null) {
|
||||
if (x !== null) {
|
||||
x.save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { User } from './user';
|
||||
import { Repo } from './repo';
|
||||
|
||||
function findUser(): User | null { return new User(); }
|
||||
function findRepo(): Repo | undefined { return new Repo(); }
|
||||
function findUser(): User | null {
|
||||
return new User();
|
||||
}
|
||||
function findRepo(): Repo | undefined {
|
||||
return new Repo();
|
||||
}
|
||||
|
||||
// Nullable type + assignment chain: the nullable union must be stripped
|
||||
// before the alias can resolve to User.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
export class Address {
|
||||
city: string = '';
|
||||
save(): boolean { return true; }
|
||||
save(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class User {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
import { User } from './models';
|
||||
export function getUser(): User { return new User(); }
|
||||
export function getUser(): User {
|
||||
return new User();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
function greet(name: string, greeting: string = "Hello"): string {
|
||||
return greeting + ", " + name;
|
||||
function greet(name: string, greeting: string = 'Hello'): string {
|
||||
return greeting + ', ' + name;
|
||||
}
|
||||
|
||||
function search(query: string, limit?: number): string[] {
|
||||
|
|
@ -7,8 +7,8 @@ function search(query: string, limit?: number): string[] {
|
|||
}
|
||||
|
||||
function process() {
|
||||
greet("Alice");
|
||||
greet("Bob", "Hi");
|
||||
search("test");
|
||||
search("test", 10);
|
||||
greet('Alice');
|
||||
greet('Bob', 'Hi');
|
||||
search('test');
|
||||
search('test', 10);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
function lookup(id: number): string;
|
||||
function lookup(name: string): string;
|
||||
function lookup(key: number | string): string {
|
||||
return String(key);
|
||||
return String(key);
|
||||
}
|
||||
|
||||
function process() {
|
||||
lookup(42);
|
||||
lookup("alice");
|
||||
lookup(42);
|
||||
lookup('alice');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export interface IRepository {
|
||||
find(id: number): string;
|
||||
find(name: string): string;
|
||||
save(data: string): void;
|
||||
find(id: number): string;
|
||||
find(name: string): string;
|
||||
save(data: string): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { IRepository } from './repository';
|
||||
|
||||
export class SqlRepository implements IRepository {
|
||||
find(id: number): string;
|
||||
find(name: string): string;
|
||||
find(arg: number | string): string {
|
||||
return typeof arg === 'number' ? 'found-by-id' : 'found-by-name';
|
||||
}
|
||||
save(data: string): void {
|
||||
console.log(data);
|
||||
}
|
||||
find(id: number): string;
|
||||
find(name: string): string;
|
||||
find(arg: number | string): string {
|
||||
return typeof arg === 'number' ? 'found-by-id' : 'found-by-name';
|
||||
}
|
||||
save(data: string): void {
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class Dog extends Animal {
|
|||
export function run(): void {
|
||||
// Virtual dispatch: declared as Animal, constructed as Dog
|
||||
const animal: Animal = new Dog();
|
||||
animal.fetchBall(); // Only Dog has fetchBall — proves virtual dispatch override
|
||||
animal.fetchBall(); // Only Dog has fetchBall — proves virtual dispatch override
|
||||
|
||||
// Direct type: no override needed
|
||||
const dog: Dog = new Dog();
|
||||
dog.fetchBall(); // Direct resolution to Dog#fetchBall
|
||||
dog.fetchBall(); // Direct resolution to Dog#fetchBall
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { User, Address } from './models';
|
|||
|
||||
function updateUser(user: User) {
|
||||
// Write access: user.name = "Alice"
|
||||
user.name = "Alice";
|
||||
user.name = 'Alice';
|
||||
|
||||
// Write access: user.address = new Address()
|
||||
user.address = new Address();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ILogger } from './models';
|
||||
|
||||
export class ConsoleLogger implements ILogger {
|
||||
log(message: string): void {
|
||||
console.log(message);
|
||||
}
|
||||
log(message: string): void {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
export interface ILogger {
|
||||
log(message: string): void;
|
||||
log(message: string): void;
|
||||
}
|
||||
|
||||
export class BaseService {
|
||||
protected name: string = '';
|
||||
protected name: string = '';
|
||||
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { BaseService, ILogger } from './models';
|
|||
import { ConsoleLogger } from './logger';
|
||||
|
||||
export class UserService extends BaseService implements ILogger {
|
||||
log(message: string): void {
|
||||
const logger = new ConsoleLogger();
|
||||
logger.log(message);
|
||||
}
|
||||
log(message: string): void {
|
||||
const logger = new ConsoleLogger();
|
||||
logger.log(message);
|
||||
}
|
||||
|
||||
getUsers(): string[] {
|
||||
return [];
|
||||
}
|
||||
getUsers(): string[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export class Repo {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export class User {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export class Repo {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export class User {
|
||||
save() { return true; }
|
||||
save() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
export class Repo {
|
||||
name: string;
|
||||
constructor(name: string) { this.name = name; }
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
save(): void {}
|
||||
}
|
||||
|
||||
export function getRepos(): Repo[] {
|
||||
return [new Repo("main")];
|
||||
return [new Repo('main')];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
export class User {
|
||||
name: string;
|
||||
constructor(name: string) { this.name = name; }
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
save(): void {}
|
||||
}
|
||||
|
||||
export function getUsers(): User[] {
|
||||
return [new User("alice")];
|
||||
return [new User('alice')];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export class BaseModel<T> {
|
||||
save(): boolean { return true; }
|
||||
save(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue