Skip to content

Interactive Perspective Selector with Enhanced SDNA Debugging#624

Merged
lucksus merged 25 commits intodevfrom
cli-fixes-and-improvements
Sep 5, 2025
Merged

Interactive Perspective Selector with Enhanced SDNA Debugging#624
lucksus merged 25 commits intodevfrom
cli-fixes-and-improvements

Conversation

@lucksus
Copy link
Copy Markdown
Member

@lucksus lucksus commented Sep 5, 2025

Interactive Perspective Selector with Enhanced SDNA Debugging

Overview

This PR introduces a major UX improvement to the AD4M CLI by replacing the static perspective listing with an interactive terminal UI, and significantly enhances SDNA debugging capabilities with comprehensive authorship tracking and Prolog loading status visualization.

🎯 Key Features

Interactive Perspective Selector

  • Terminal UI: Beautiful ratatui-based interface for perspective selection
  • Navigation: Arrow keys (↑/↓) and vim-style (k/j) navigation
  • Rich Information: Shows perspective name, UUID, neighbourhood status, and sharing status
  • Seamless Integration: Direct launch into REPL for selected perspective
  • Clean Exit: Multiple exit options (q, Escape)

Enhanced SDNA Debugging

  • Multi-Author Tracking: Displays all unique authors for both name and code links
  • Prolog Loading Status: Real-time checking of which SDNA is loaded in Prolog engine
  • Visual Distinction: Color-coded display (loaded = bright syntax highlighting, not loaded = muted)
  • Class Grouping: Groups SDNA by class name, showing each unique code instance once
  • Per-Snippet Authorship: Shows individual author for each code snippet
  • Loading Verification: Accurate Prolog fact checking with regex parsing

Improved REPL Commands

  • New all-links Command: Simple command to display all links in a perspective
  • Renamed query to get_links: More intuitive and descriptive command name
  • Accurate Help Documentation: Fixed help text to match actual parser signatures
  • Enhanced Command Syntax: Proper documentation of add_link with required status parameter
  • Enhanced sdna Command: Added optional class filtering with unified functionality

🔧 Technical Changes

New Dependencies

ratatui = "0.26.0"      # Terminal UI framework
crossterm = "0.27.0"    # Cross-platform terminal manipulation

Core Files Modified

cli/src/perspectives.rs

  • New Function: interactive_perspective_selector() - Main UI loop
  • Terminal Management: Raw mode, alternate screen, mouse capture
  • Event Handling: Comprehensive key handling with proper state management
  • UI Rendering: Layout, styling, borders, colors with ratatui
  • REPL Integration: Seamless transition to perspective REPL

rust-client/src/perspective_proxy.rs

  • Enhanced get_dna(): Returns grouped SDNA with consolidated authorship
  • New Method: is_sdna_loaded() - Checks Prolog loading status
  • Regex Parsing: Extracts subject_class facts from SDNA code
  • Prolog Integration: General query with variable binding comparison
  • Multi-Author Support: Collects all unique authors per relationship

cli/src/repl.rs

  • Updated Commands: Enhanced sdna and sdna-authors commands
  • Color Coding: Different themes for loaded vs not-loaded code
  • Per-Snippet Display: Shows individual code instance authors
  • Loading Status: Visual indicators (✓ LOADED / ✗ NOT LOADED)
  • White Separators: Clear distinction between multiple code instances
  • New all_links() Function: Simple command to display all links
  • Renamed link_query() to get_links(): More intuitive function name
  • Fixed Help Documentation: Accurate command syntax matching parser signatures
  • Enhanced sdna() Function: Unified command with optional class filtering

🎨 User Experience Improvements

Before

$ ad4m perspectives
Name: My Perspective
ID: abc-123-def
Shared URL: <not shared as Neighbourhood>
# ... more text output

After

$ ad4m perspectives
┌─────────────────────────────────────┐
│        AD4M Perspective Selector    │
└─────────────────────────────────────┘
┌─ Select a perspective (↑/↓ arrows, Enter to select, q to quit) ─┐
│ > My Perspective (abc-123-def) [Neighbourhood: p-diff-sync]    │
│   Another Perspective (def-456-ghi) [Shared]                   │
│   Private Perspective (ghi-789-jkl)                            │
└─────────────────────────────────────────────────────────────────┘
┌─ Selection: 1 of 3 ─┐
└─────────────────────┘
┌─ Navigation: ↑/↓ arrows, Enter: select perspective, q: quit ─┐
└─────────────────────────────────────────────────────────────────┘

SDNA Display Enhancement

$ sdna
================
SDNA Class: Person
Name Authors: did:key:alice, did:key:bob
Code Authors: did:key:alice, did:key:charlie
✓ Same authors for name and code
================
% Author: did:key:alice ✓ LOADED

[Bright, colorful syntax highlighting - loaded code]

% --- Next SDNA Instance ---
% Author: did:key:charlie ✗ NOT LOADED

[Muted, dark gray text - not loaded code]
================

REPL Command Improvements

# New all-links command
$ all-links
All links in perspective:
[Link display with proper formatting]

# Renamed and improved get_links command
$ get_links()
$ get_links("alice", "knows", "bob")
$ get_links("alice", "_", "_")

# Fixed add_link syntax documentation
$ add_link("alice", "knows", "active", "bob")
$ add_link("alice", "knows", "_", "bob")

# Enhanced sdna command with optional filtering
$ sdna                    # Show all SDNA classes
$ sdna Person            # Show only Person class SDNA
$ sdna "My Custom Class" # Show only specific class

🔍 Technical Details

Prolog Loading Detection

  • Regex Pattern: ^\s*subject_class\("([^"]+)",\s*("([^"]+)"|([^,\s)]+))\)\s*\.\s*$
  • Query Strategy: General subject_class(Name, Ref). with variable binding comparison
  • Result Verification: Iterates through Prolog results to find matching facts
  • Error Handling: Graceful fallback when Prolog queries fail

Multi-Author Tracking

  • Data Structure: HashMap<String, (Vec<String>, Vec<String>, Vec<(String, String)>)>
  • Deduplication: Uses HashSet for efficient author comparison
  • Consolidation: Groups by class name, shows each code instance once
  • Authorship Context: Indicates if name and code authors match

Terminal UI Architecture

  • State Management: Persistent ListState for proper selection tracking
  • Event Loop: Clean key handling without debug interference
  • Layout System: Responsive layout with proper constraints
  • Color Scheme: Professional styling with clear visual hierarchy

🐛 Bug Fixes

SDNA Command Restoration

  • Fixed: Broken sdna command after link topology changes
  • Solution: Updated to use new two-link topology (self → name literal → code)
  • Verification: Proper extraction of class names from name literals

Color Coding Issues

  • Problem: ANSI color prefixes being overwritten by syntax highlighting
  • Solution: Theme-based approach (Solarized light for loaded, no highlighting for not loaded)
  • Result: Clear visual distinction without color conflicts

ListState Management

  • Problem: Selection state not properly tracked across renders
  • Solution: Persistent ListState with proper synchronization
  • Result: Smooth navigation without rendering artifacts

REPL Command Improvements

  • Problem: Inconsistent command naming and inaccurate help documentation
  • Solution: Renamed query to get_links, added all-links command, fixed help text
  • Result: Clear, intuitive commands with accurate documentation

SDNA Command Enhancement

  • Problem: Separate handling for sdna and sdna <class> with different functionality
  • Solution: Unified command with optional class filtering using the same rich get_dna method
  • Result: Consistent functionality with optional filtering and better error messages

🧪 Testing

Manual Testing Scenarios

  1. Empty Perspectives: Graceful handling with informative message
  2. Single Perspective: Direct selection and REPL launch
  3. Multiple Perspectives: Navigation and selection
  4. SDNA Loading: Verification of Prolog fact detection
  5. Multi-Author SDNA: Proper grouping and authorship display
  6. Terminal Cleanup: Proper restoration of terminal state
  7. REPL Commands: Testing all-links, get_links, and add_link commands
  8. Help Documentation: Verification of accurate command syntax
  9. SDNA Filtering: Testing sdna and sdna <class> functionality

Edge Cases Handled

  • No Perspectives: Clean exit with message
  • Prolog Errors: Graceful fallback for loading status
  • Terminal Interruption: Proper cleanup on Ctrl+C
  • Invalid Key Events: Ignored without errors

📚 Documentation Updates

CLI Help Text

  • Updated sdna command description
  • Updated sdna-authors command description
  • Added interactive perspective selector usage
  • Added all-links command documentation
  • Updated get_links command documentation (renamed from query)
  • Fixed add_link command syntax documentation
  • Updated sdna command documentation with optional filtering

Code Comments

  • Comprehensive documentation for new functions
  • Clear explanation of Prolog integration
  • Detailed UI architecture comments

🚀 Performance Considerations

Efficient Data Structures

  • HashMap Grouping: O(1) lookup for SDNA grouping
  • HashSet Deduplication: Efficient author comparison
  • Lazy Evaluation: Prolog queries only when needed

Terminal Performance

  • Minimal Redraws: Only re-render when state changes
  • Efficient Event Handling: Direct key code matching
  • Memory Management: Proper cleanup of terminal resources

🔮 Future Enhancements

Potential Improvements

  • Search Functionality: Filter perspectives by name
  • Sorting Options: Sort by name, creation date, etc.
  • Batch Operations: Select multiple perspectives
  • SDNA Validation: Check for syntax errors
  • Author Permissions: Show which SDNA will be loaded based on authorship

Technical Debt

  • Error Handling: More granular error messages
  • Configuration: Customizable color schemes
  • Accessibility: Screen reader support
  • Testing: Automated UI testing

📋 Checklist

  • Interactive perspective selector implemented
  • SDNA command restored and enhanced
  • Multi-author tracking added
  • Prolog loading status detection
  • Color-coded SDNA display
  • Proper terminal state management
  • Clean error handling
  • Documentation updated
  • Manual testing completed
  • No breaking changes to existing functionality
  • New all-links command added
  • query command renamed to get_links
  • Help documentation accuracy fixed
  • README documentation updated
  • Enhanced sdna command with optional class filtering

🎉 Impact

This PR significantly improves the developer experience when working with AD4M perspectives and SDNA:

  1. Faster Workflow: Interactive selection vs. manual UUID copying
  2. Better Debugging: Clear visibility into SDNA authorship and loading
  3. Professional UI: Modern terminal interface with proper navigation
  4. Comprehensive Information: All relevant data visible at once
  5. Seamless Integration: Direct launch into REPL without interruption
  6. Intuitive Commands: Clear, descriptive command names (get_links, all-links)
  7. Accurate Documentation: Help text that matches actual functionality
  8. Enhanced Usability: Simple commands for common operations
  9. Flexible SDNA Viewing: Optional class filtering with consistent functionality

The changes maintain backward compatibility while providing a much more user-friendly interface for perspective management and SDNA debugging.

Summary by CodeRabbit

  • New Features

    • Interactive terminal UI for selecting perspectives with keyboard navigation.
    • New REPL commands: sdna-authors, get_links/all-links, classes alias; welcome banner and system utilities (help, clear, version, exit).
  • Improvements

    • Richer SDNA views: per-class summaries, per-instance listings, authorship comparison and loaded-state highlighting.
    • Better output rendering for arrays and literal-encoded values; typo fixed.
    • New runtime APIs to query SDNA and loading state.
  • Documentation

    • README updated with revised REPL commands and examples.
  • Chores

    • Build embeds commit/version metadata; added terminal UI deps and CI orb removed.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 5, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces non-interactive perspective listing with an interactive Ratatui/Crossterm TUI selector; adds terminal UI deps and Git metadata in the build; expands REPL commands (get_links/all-links, help, system, sdna, sdna-authors); refactors SDNA retrieval in TS and Rust with per-class lookup and loaded checks; improves formatting for nested values.

Changes

Cohort / File(s) Summary
CLI deps & build metadata
cli/Cargo.toml, cli/build.rs
Add ratatui = "0.26.0" and crossterm = "0.27.0"; build script emits GIT_COMMIT_HASH and GIT_DIRTY and adds cargo:rerun-if-changed for .git metadata.
CLI docs / README
cli/README.md
Add sdna-authors; introduce System Commands (help, clear, version, exit); change link management signatures (add_link requires status); update get_links signature and add all-links alias and _ guidance.
Interactive perspective selector (CLI)
cli/src/perspectives.rs
Replace non-interactive listing with interactive TUI (raw mode, alternate screen, scrollable list, navigation, Enter select, quit handling); selection restores terminal and launches REPL; prints message when empty.
REPL command surface & UX
cli/src/repl.rs
Add get_links / all-links, classes alias, help and system commands, sdna and sdna-authors views; welcome banner; explicit exit; reordered dispatch; per-instance SDNA load checks and highlighted rendering.
Formatting & literal decoding (CLI)
cli/src/formatting.rs
Add recursive deconstruct_value and array/object support; add maybe_decode_literal to decode literal:// URIs; fix typo and improve link/prolog printing.
Core TS PerspectiveProxy SDNA changes
core/src/perspectives/PerspectiveProxy.ts
getSdna now aggregates SDNA from has_subject_class, has_flow, has_custom_sdna; add getSdnaForClass(className); minor duplicate-avoidance refactor.
Rust client PerspectiveProxy API & logic
rust-client/src/perspective_proxy.rs
get_dna returns per-class structured data; add is_sdna_loaded, get_dna_for_class, get_neighbourhood_author, get_local_agent_did; switch predicate to has_custom_sdna and add loading-check logic.
CI configuration
.circleci/config.yml
Remove rust: circleci/rust@1.6.1 orb declaration; other CI steps unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as cli::perspectives
  participant TUI as Ratatui/Crossterm
  participant RPC as AD4M Client

  User->>CLI: run (no subcommand)
  CLI->>TUI: enable raw mode, alt screen
  CLI->>RPC: fetch perspectives
  alt no perspectives
    CLI->>TUI: restore terminal
    CLI-->>User: "No perspectives found!"
  else perspectives available
    loop navigate
      User->>TUI: up/down/enter/q
      TUI->>CLI: selection change / command
    end
    opt enter
      CLI->>TUI: restore terminal
      CLI->>RPC: fetch selected perspective
      CLI-->>User: launch REPL for selection
    end
    opt quit
      CLI->>TUI: restore terminal
      CLI-->>User: exit
    end
  end
Loading
sequenceDiagram
  autonumber
  participant REPL as CLI REPL
  participant RUST as rust-client::PerspectiveProxy
  participant CORE as core::PerspectiveProxy
  participant Store as Link Store

  REPL->>RUST: get_dna()
  RUST->>Store: query has_subject_class / has_flow / has_custom_sdna
  Store-->>RUST: class/name/code edges
  RUST->>Store: fetch name authors & code authors
  RUST-->>REPL: per-class SDNA + authors
  loop per SDNA code
    REPL->>RUST: is_sdna_loaded(code)
    RUST->>Store: verify subject_class facts for code
    RUST-->>REPL: loaded? (true/false)
  end

  REPL->>RUST: get_dna_for_class(class)
  RUST->>CORE: getSdnaForClass(class)
  CORE->>Store: resolve class name -> sdna
  CORE-->>RUST: sdna or null
  RUST-->>REPL: sdna + authors or None
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jhweir

Poem

I tap the keys where terminals glow,
A rabbit hops through lists that show.
SDNA authors, bright and gray,
Help and version guide my way.
I nibble bugs, then off I go—deploy! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dee3d7c and 8eb0871.

📒 Files selected for processing (3)
  • cli/README.md (2 hunks)
  • cli/src/repl.rs (6 hunks)
  • rust-client/src/perspective_proxy.rs (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cli-fixes-and-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/README.md (1)

223-231: Add code-block language and clarify sdna-authors usage

  • Change the opening fence to ```text for consistency.
  • sdna-authors is a standalone REPL command (no <class> argument), as confirmed by its exact-match handling in repl.rs.
🧹 Nitpick comments (11)
cli/Cargo.toml (1)

49-50: Confirm MSRV compatibility and optionally pin features

  • ratatui 0.26.0 MSRV = 1.67.0 and crossterm 0.27.0 MSRV = 1.58, both ≤ your project MSRV (Rust 1.84.0) (docs.rs)
  • To shrink your build surface, disable default features and enable only what you use:
    ratatui   = { version = "0.26.0", default-features = false, features = ["crossterm"] }
    crossterm = { version = "0.27.0", default-features    = false }

(Optional) re-enable events or event-stream on crossterm if you add input handling.

cli/README.md (1)

233-246: Fix MD040 (fenced code language) for “System Commands” block

Add a language to the fenced block to satisfy markdownlint and keep formatting consistent.

Apply:

-```
+```text
 # Show help
 help

 # Clear the screen
 clear

 # Show version information
 version

 # Exit the REPL
 exit

</blockquote></details>
<details>
<summary>cli/build.rs (1)</summary><blockquote>

`17-41`: **Make git change detection more robust (watch refs/packed-refs too)**

HEAD rarely changes on branch commits; refs files (or packed-refs) do. Add rerun triggers for those to refresh GIT_COMMIT_HASH reliably.

Apply:
```diff
     // Re-run if git information changes
     println!("cargo:rerun-if-changed=.git/HEAD");
     println!("cargo:rerun-if-changed=.git/index");
+    // Also watch refs and packed-refs so new commits retrigger builds
+    println!("cargo:rerun-if-changed=.git/refs");
+    println!("cargo:rerun-if-changed=.git/packed-refs");
core/src/perspectives/PerspectiveProxy.ts (2)

785-822: Deduplicate SDNA entries and harden literal parsing

Current aggregation can return duplicates and will throw if a target isn’t a literal URL. Add a seen-set and guard parsing.

Apply:

-        const allSdnaCode: string[] = [];
+        const allSdnaCode: string[] = [];
+        const seen = new Set<string>();
@@
-                for (const sdnaLink of sdnaLinks) {
-                    const code = Literal.fromUrl(sdnaLink.data.target).get();
-                    if (typeof code === 'string') {
-                        allSdnaCode.push(code);
-                    }
-                }
+                for (const sdnaLink of sdnaLinks) {
+                    try {
+                        const code = Literal.fromUrl(sdnaLink.data.target).get();
+                        if (typeof code === 'string' && !seen.has(code)) {
+                            seen.add(code);
+                            allSdnaCode.push(code);
+                        }
+                    } catch (e) {
+                        console.warn('Skipping non-literal SDNA target:', sdnaLink.data.target);
+                    }
+                }

827-854: Minor: avoid duplicate toUrl() calls and document multi-link behavior

  • Cache name literal URL; return first result is fine, but note behavior or select latest deterministically.

Apply:

-        const nameLiteral = Literal.from(className);
+        const nameLiteral = Literal.from(className);
+        const nameLiteralUrl = nameLiteral.toUrl();
@@
-            target: nameLiteral.toUrl(),
+            target: nameLiteralUrl,
@@
-        const sdnaLinks = await this.get(new LinkQuery({
-            source: nameLiteral.toUrl(),
+        const sdnaLinks = await this.get(new LinkQuery({
+            source: nameLiteralUrl,
             predicate: "ad4m://sdna"
         }));
rust-client/src/perspective_proxy.rs (3)

120-199: Make SDNA load-check query stricter, cheaper, and compatible with infer() output variants

  • Use a bound query per fact (avoid fetching all bindings), handle Value::Bool and Value::Array like is_subject_instance(), and drop the trailing dot in the query (other calls omit it). Also allow optional trailing comments in the regex. Minor: compile the regex once (not in the loop).
-        // Handle both quoted and unquoted ref values, and require it to be the only content on the line
-        let subject_class_regex = Regex::new(r#"^\s*subject_class\("([^"]+)",\s*("([^"]+)"|([^,\s)]+))\)\s*\.\s*$"#).unwrap();
+        // Handle both quoted and unquoted refs; allow trailing comments
+        let subject_class_regex = Regex::new(
+            r#"^\s*subject_class\("([^"]+)",\s*("([^"]+)"|([^,\s)]+))\)\s*\.\s*(%.*)?$"#,
+        ).unwrap();
@@
-            // Use a general query with both variables to check if the fact exists
-            let query = "subject_class(Name, Ref).".to_string();
-            
-            match self.infer(query).await {
+            // Prefer a bound query; accept Array or Bool results
+            let query = format!(r#"subject_class("{}", {})"#, class_name, ref_value);
+            match self.infer(query).await {
                 Ok(results) => {
-                    // Check if any of the results have both Name and Ref matching our expected values
-                    if let Some(array) = results.as_array() {
-                        let mut found_match = false;
-                        for result in array {
-                            // Look for both Name and Ref bindings in this result
-                            if let Some(result_obj) = result.as_object() {
-                                let name_binding = result_obj.get("Name");
-                                let ref_binding = result_obj.get("Ref");
-                                
-                                // Check if both bindings exist and match our expected values
-                                if let (Some(name), Some(ref_val)) = (name_binding, ref_binding) {
-                                    if let (Some(bound_name), Some(bound_ref)) = (name.as_str(), ref_val.as_str()) {
-                                        if bound_name == class_name && bound_ref == ref_value {
-                                            found_match = true;
-                                            break;
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                        
-                        if found_match {
-                            continue;
-                        } else {
-                            return Ok(false);
-                        }
-                    } else {
-                        return Ok(false);
-                    }
+                    if let Some(array) = results.as_array() {
+                        if array.is_empty() { return Ok(false); }
+                    } else if let Some(b) = results.as_bool() {
+                        if !b { return Ok(false); }
+                    } else {
+                        return Ok(false);
+                    }
                 }
                 Err(_) => {
                     return Ok(false);
                 }
             }

201-291: Stabilize output order and dedupe author lists

  • Stable ordering improves UX in the REPL. Also sort/dedup authors per class before returning.
-        let mut result = Vec::new();
-        for (class_name, (name_authors, code_authors, sdna_codes_with_authors)) in class_groups {
-            result.push((class_name, sdna_codes_with_authors, name_authors, code_authors));
-        }
-        
-        Ok(result)
+        let mut result = Vec::new();
+        for (class_name, (mut name_authors, mut code_authors, mut sdna_codes_with_authors)) in class_groups {
+            name_authors.sort();
+            name_authors.dedup();
+            code_authors.sort();
+            code_authors.dedup();
+            sdna_codes_with_authors.sort_by(|a, b| a.0.cmp(&b.0)); // by code content
+            result.push((class_name, sdna_codes_with_authors, name_authors, code_authors));
+        }
+        result.sort_by(|a, b| a.0.cmp(&b.0)); // by class name
+        Ok(result)

590-600: Public stubs risk API lock-in

These methods are public but unimplemented. Either wire them to the client now or gate behind cfg(test) or document as experimental to avoid API commitment.

cli/src/perspectives.rs (1)

277-465: Ensure terminal cleanup on all exit paths; simplify scrolling

  • Add a small RAII guard (or finally-style cleanup) so raw mode/alt screen are always restored, even on errors.
  • The manual scroll bookkeeping isn’t used to drive rendering. ListState handles selection; remove unused scroll/list_height or set ListState’s offset explicitly.
-    let mut selected = 0;
-    let mut scroll = 0;
-    let list_height = 20; // Maximum visible items
+    let mut selected = 0;
     let mut list_state = ratatui::widgets::ListState::default();
     list_state.select(Some(selected));
@@
-                KeyCode::Up | KeyCode::Char('k') => {
-                    if selected > 0 {
-                        selected -= 1;
-                        if selected < scroll {
-                            scroll = selected;
-                        }
-                        list_state.select(Some(selected));
-                    }
-                }
+                KeyCode::Up | KeyCode::Char('k') => {
+                    if selected > 0 {
+                        selected -= 1;
+                        list_state.select(Some(selected));
+                    }
+                }
@@
-                KeyCode::Down | KeyCode::Char('j') => {
-                    if selected < all_perspectives.len() - 1 {
-                        selected += 1;
-                        if selected >= scroll + list_height {
-                            scroll = selected - list_height + 1;
-                        }
-                        list_state.select(Some(selected));
-                    }
-                }
+                KeyCode::Down | KeyCode::Char('j') => {
+                    if selected + 1 < all_perspectives.len() {
+                        selected += 1;
+                        list_state.select(Some(selected));
+                    }
+                }

If you prefer manual control, set the list_state offset instead of keeping a separate scroll counter.

cli/src/repl.rs (2)

190-206: Avoid build-time panics if env vars are missing; use option_env! with fallbacks

Build scripts may not always set these vars. Use option_env! to harden.

-            println!(
-                "\x1b[36mAD4M CLI Version: \x1b[97m{}",
-                env!("CARGO_PKG_VERSION")
-            );
-            println!("\x1b[36mPackage: \x1b[97m{}", env!("CARGO_PKG_NAME"));
-            println!(
-                "\x1b[36mGit Commit: \x1b[97m{} ({})",
-                env!("GIT_COMMIT_HASH"),
-                env!("GIT_DIRTY")
-            );
-            println!("\x1b[36mHomepage: \x1b[97m{}", env!("CARGO_PKG_HOMEPAGE"));
-            println!(
-                "\x1b[36mRepository: \x1b[97m{}",
-                env!("CARGO_PKG_REPOSITORY")
-            );
-            println!("\x1b[36mLicense: \x1b[97m{}", env!("CARGO_PKG_LICENSE"));
+            println!("\x1b[36mAD4M CLI Version: \x1b[97m{}", env!("CARGO_PKG_VERSION"));
+            println!("\x1b[36mPackage: \x1b[97m{}", env!("CARGO_PKG_NAME"));
+            println!(
+                "\x1b[36mGit Commit: \x1b[97m{} ({})",
+                option_env!("GIT_COMMIT_HASH").unwrap_or("unknown"),
+                option_env!("GIT_DIRTY").unwrap_or("unknown")
+            );
+            println!("\x1b[36mHomepage: \x1b[97m{}", option_env!("CARGO_PKG_HOMEPAGE").unwrap_or("-"));
+            println!("\x1b[36mRepository: \x1b[97m{}", option_env!("CARGO_PKG_REPOSITORY").unwrap_or("-"));
+            println!("\x1b[36mLicense: \x1b[97m{}", option_env!("CARGO_PKG_LICENSE").unwrap_or("-"));

220-221: Guard against missing syntax; prefer Prolog theme but fall back to plain text

find_syntax_by_extension("pl") may return Perl; unwrap() can panic on missing syntax. Prefer Prolog by token, then pl, then Plain Text.

-                let syntax = ps.find_syntax_by_extension("pl").unwrap();
+                let syntax = ps
+                    .find_syntax_by_token("prolog")
+                    .or_else(|| ps.find_syntax_by_extension("pl"))
+                    .unwrap_or_else(|| ps.find_syntax_plain_text());
@@
-                let syntax = ps.find_syntax_by_extension("pl").unwrap();
+                let syntax = ps
+                    .find_syntax_by_token("prolog")
+                    .or_else(|| ps.find_syntax_by_extension("pl"))
+                    .unwrap_or_else(|| ps.find_syntax_plain_text());

Optional: cache SyntaxSet/ThemeSet via once_cell::sync::Lazy to avoid reloading on each command.

Also applies to: 335-336

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d05eeed and 975a18f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • cli/Cargo.toml (1 hunks)
  • cli/README.md (1 hunks)
  • cli/build.rs (1 hunks)
  • cli/src/perspectives.rs (3 hunks)
  • cli/src/repl.rs (5 hunks)
  • core/src/perspectives/PerspectiveProxy.ts (2 hunks)
  • rust-client/src/perspective_proxy.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
cli/src/perspectives.rs (1)
cli/src/repl.rs (1)
  • repl_loop (656-741)
core/src/perspectives/PerspectiveProxy.ts (2)
test-runner/example/languages/social-context.js (1)
  • LinkQuery (4016-4054)
core/src/Literal.ts (1)
  • Literal (9-84)
rust-client/src/perspective_proxy.rs (1)
rust-client/src/literal.rs (2)
  • from_url (27-36)
  • from_string (38-43)
cli/src/repl.rs (1)
core/src/perspectives/PerspectiveProxy.ts (1)
  • PerspectiveProxy (293-1258)
🪛 markdownlint-cli2 (0.17.2)
cli/README.md

234-234: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
core/src/perspectives/PerspectiveProxy.ts (1)

1069-1073: Nice: prevents duplicate collections from prototypes

The uniqueness check and isSubjectInstance exclusion look good. No further action.

cli/src/perspectives.rs (1)

121-125: Good default: launches interactive selector when no command is passed

Nice UX improvement.

cli/src/repl.rs (1)

83-94: LGTM: “classes” command is a simple, useful alias

Clear UX win.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/repl.rs (1)

521-526: Regex bug: subject(..)[..] = .. also matches <= and preempts collection add.

[\s--<]*= allows < before =, so lines with <= match this rule and won’t reach subject_add_collection. Tighten the regex to require exactly = and consider ordering collection before set.

Apply this diff:

-    let subject_set_prop =
-        Regex::new(r"subject\(\s*(?P<base>\S+)\s*\)\[(?P<name>\S+)\][\s--<]*=\s*(?P<value>\S+)")
+    let subject_set_prop =
+        Regex::new(r"subject\(\s*(?P<base>\S+)\s*\)\[(?P<name>\S+)\]\s*=\s*(?P<value>\S+)")
             .expect("Error parsing subject_set_prop regex");

Optional: dispatch subject_add_collection before subject_set_prop for extra safety.

♻️ Duplicate comments (1)
cli/src/repl.rs (1)

114-201: Fix invalid help example; keep examples self-consistent.

The trailing tip still shows an invalid example ?Person knows Bob. Replace with a valid Prolog example and note the trailing period.

Apply this diff:

-        println!("\x1b[97m================");
-        println!("\x1b[90mTip: Use uppercase letters for variables in Prolog queries");
-        println!("\x1b[90mExample: ?Person knows Bob");
-        println!("\x1b[97m================");
+        println!("\x1b[97m================");
+        println!("\x1b[90mTip: Variables start uppercase in Prolog; end queries with a period.");
+        println!("\x1b[90mExample: subject(X, 'Person').");
+        println!("\x1b[97m================");
🧹 Nitpick comments (9)
cli/README.md (3)

198-205: Link command docs align with parser; add a quick example.

Looks good and matches the REPL regex. Consider adding a one-line example to reduce ambiguity (e.g., add_link(alice, knows, present, bob)).


202-208: get_links docs are accurate; note equivalence with all-links.

Clear and matches implementation. Maybe add that get_links() with no args is equivalent to all-links.


241-254: Fix markdownlint MD040: specify a language for fenced block.

Add a language hint (text) to the System Commands block.

Apply this diff:

-#### System Commands
-```
+#### System Commands
+```text
 # Show help
 help
@@
 # Exit the REPL
 exit

</blockquote></details>
<details>
<summary>cli/src/repl.rs (6)</summary><blockquote>

`45-82`: **Regex handles optional args; consider quoted terms support.**

Current `\S+` tokens forbid spaces. If users paste IRIs or values with spaces, parsing fails. Optional: accept single/double-quoted terms.

Apply this minimal diff to allow quoted tokens:

```diff
-    let get_links_regex = Regex::new(
-        r"get_links\(\s*(?P<source>\S+)?(,\s*(?P<predicate>\S+))?(,\s*(?P<target>\S+))?\)",
-    )
+    let get_links_regex = Regex::new(
+        r#"get_links\(\s*(?P<source>"[^"]+"|'[^']+'|\S+)?(,\s*(?P<predicate>"[^"]+"|'[^']+'|\S+))?(,\s*(?P<target>"[^"]+"|'[^']+'|\S+))?\)"#,
+    )

84-99: Prefer &str over &String for params.

all_links (and other helpers) can take &str to avoid unnecessary String coupling.

Apply this diff:

-async fn all_links(perspective: &PerspectiveProxy, line: &String) -> bool {
+async fn all_links(perspective: &PerspectiveProxy, line: &str) -> bool {
-    if line == "all-links" {
+    if line == "all-links" {

And adjust call sites accordingly (pass &line instead of &line where needed).


245-349: Avoid per-call syntax/theme loads and unwrap panic.

  • Load SyntaxSet/ThemeSet once (lazy static) to reduce startup latency.
  • find_syntax_by_extension("pl").unwrap() can panic; fall back to plain text if missing.

Apply this change (add near top-level):

+use once_cell::sync::Lazy;
+static PS: Lazy<SyntaxSet> = Lazy::new(SyntaxSet::load_defaults_newlines);
+static TS: Lazy<ThemeSet> = Lazy::new(ThemeSet::load_defaults);

And replace within this function:

-                let ps = SyntaxSet::load_defaults_newlines();
-                let ts = ThemeSet::load_defaults();
-                let syntax = ps.find_syntax_by_extension("pl").unwrap();
+                let syntax = PS.find_syntax_by_extension("pl").unwrap_or_else(|| PS.find_syntax_plain_text());

Also update later HighlightLines::new(... &ts...) to &TS.


349-403: Duplicate syntax/theme init; reuse cached sets and handle missing syntax.

Same refactor as above applies here.

Apply analogous changes using PS/TS and find_syntax_plain_text().


404-489: Potentially slow N×await loop; batch is_sdna_loaded checks.

When many code instances exist, you await sequentially. Consider concurrent checks with futures::stream::FuturesUnordered or join_all.

Example:

-                        for (i, (sdna_code, code_author)) in
-                            sdna_codes_with_authors.iter().enumerate()
-                        {
-                            let is_loaded = match perspective.is_sdna_loaded(sdna_code).await {
-                                Ok(loaded) => loaded,
-                                Err(_) => false,
-                            };
+                        let results = futures::future::join_all(
+                            sdna_codes_with_authors.iter().map(|(code, _)| perspective.is_sdna_loaded(code))
+                        ).await;
+                        for (i, ((_, code_author), res)) in sdna_codes_with_authors.iter().zip(results).enumerate() {
+                            let is_loaded = res.unwrap_or(false);

587-590: Typo in error context: “classs”.

Fix spelling in context message.

Apply this diff:

-            .with_context(|| format!("Getting subject classs for {}", base))?;
+            .with_context(|| format!("Getting subject classes for {}", base))?;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 975a18f and 7d724b9.

📒 Files selected for processing (2)
  • cli/README.md (2 hunks)
  • cli/src/repl.rs (7 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
cli/README.md

242-242: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (5)
cli/README.md (2)

212-212: Variable/underscore guidance: good addition.

Consistent with parser behavior for get_links/add_link.


236-239: New sdna-authors command is documented clearly.

Matches the new REPL command and output semantics.

cli/src/repl.rs (3)

101-112: classes alias is straightforward.

Simple and correct mapping to subject_classes().


681-690: Welcome banner: nice touch.

Improves discoverability; consistent with help.


703-706: Dispatch order looks sane.

Help/system prioritized before Prolog; link commands come early; good.

Also applies to: 719-722

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/formatting.rs (1)

111-117: Avoid quoted JSON output and noisy logs when decoding literal://json

Using Value::to_string prints strings with quotes; also println! error logs pollute normal CLI output. Return unquoted strings when possible and send errors to stderr.

-                    Ok(expression) => return expression["data"].to_string(),
-                    Err(e) => {
-                        println!("Failed to decode json literal: {}", e);
-                        return decoded_json.to_string();
-                    }
+                    Ok(expression) => {
+                        // Prefer plain string when available; otherwise JSON form
+                        return match &expression["data"] {
+                            serde_json::Value::String(s) => s.clone(),
+                            other => other.to_string(),
+                        };
+                    }
+                    Err(e) => {
+                        eprintln!("Failed to decode json literal: {}", e);
+                        return decoded_json.to_string();
+                    }
♻️ Duplicate comments (2)
cli/src/repl.rs (2)

114-200: Remove outdated Prolog tip/example duplicated at footer

These lines reintroduce the invalid “?Person knows Bob” example already flagged previously. You’ve added correct examples above; drop the footer tip to avoid mixed guidance.

-        println!("\x1b[97m================");
-        println!("\x1b[90mTip: Use uppercase letters for variables in Prolog queries");
-        println!("\x1b[90mExample: ?Person knows Bob");
-        println!("\x1b[97m================");
+        println!("\x1b[97m================");

202-232: Make Git metadata robust: option_env! to avoid compile-time failures

env! will panic at compile-time when GIT_* vars aren’t provided by build.rs. Use option_env! with fallbacks.

         "version" => {
             println!(
                 "\x1b[36mAD4M CLI Version: \x1b[97m{}",
                 env!("CARGO_PKG_VERSION")
             );
             println!("\x1b[36mPackage: \x1b[97m{}", env!("CARGO_PKG_NAME"));
-            println!(
-                "\x1b[36mGit Commit: \x1b[97m{} ({})",
-                env!("GIT_COMMIT_HASH"),
-                env!("GIT_DIRTY")
-            );
+            let git_commit = option_env!("GIT_COMMIT_HASH").unwrap_or("unknown");
+            let git_dirty  = option_env!("GIT_DIRTY").unwrap_or("unknown");
+            println!("\x1b[36mGit Commit: \x1b[97m{} ({})", git_commit, git_dirty);
             println!("\x1b[36mHomepage: \x1b[97m{}", env!("CARGO_PKG_HOMEPAGE"));
             println!(
                 "\x1b[36mRepository: \x1b[97m{}",
                 env!("CARGO_PKG_REPOSITORY")
             );
             println!("\x1b[36mLicense: \x1b[97m{}", env!("CARGO_PKG_LICENSE"));
             true
         }
🧹 Nitpick comments (5)
cli/src/formatting.rs (2)

35-41: Deduplicate array formatting via deconstruct_value()

You already introduced deconstruct_value(); use it here to remove duplicate array-joining logic.

-                Value::Array(array) => {
-                    let array_items: Result<Vec<String>> = array
-                        .into_iter()
-                        .map(|item| deconstruct_value(item))
-                        .collect();
-                    format!("[{}]", array_items?.join(", "))
-                }
+                Value::Array(array) => deconstruct_value(Value::Array(array))?,
-                    Value::Array(array) => {
-                        let array_items: Result<Vec<String>> = array
-                            .into_iter()
-                            .map(|item| deconstruct_value(item))
-                            .collect();
-                        format!("[{}]", array_items?.join(", "))
-                    }
+                    Value::Array(array) => deconstruct_value(Value::Array(array))?,

Also applies to: 79-85


119-120: Use stderr for decode failures

These are diagnostics; print to stderr so the UI rendering isn’t mixed with error messages.

-                println!("Failed to decode url encoding");
+                eprintln!("Failed to decode url encoding");
cli/src/repl.rs (3)

13-16: Comment mismatch with actual add_link signature

The comment still says 3 args, but the parser expects 4 (source, predicate, status, target). Update to avoid confusion.

-    // add_link(source, predicate, target)
+    // add_link(source, predicate, status, target)

205-209: Use proper screen clear instead of printing 50 newlines

Cleaner UX and faster rendering; you already depend on crossterm.

-            // Clear the screen by printing multiple newlines
-            for _ in 0..50 {
-                println!();
-            }
+            // Clear screen and move cursor to top-left
+            print!("\x1b[2J\x1b[H");
+            use std::io::{Write, stdout};
+            let _ = stdout().flush();

366-369: Reset color after printing NOT LOADED code to avoid bleed

Without a reset, subsequent output may inherit gray.

-                            for line in LinesWithEndings::from(sdna_code) {
-                                print!("\x1b[90m{}", line);
-                            }
+                            for line in LinesWithEndings::from(sdna_code) {
+                                print!("\x1b[90m{}\x1b[0m", line);
+                            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7d724b9 and 1a78ebf.

📒 Files selected for processing (3)
  • .circleci/config.yml (0 hunks)
  • cli/src/formatting.rs (3 hunks)
  • cli/src/repl.rs (6 hunks)
💤 Files with no reviewable changes (1)
  • .circleci/config.yml
🧰 Additional context used
🧬 Code graph analysis (1)
cli/src/repl.rs (4)
rust-executor/src/perspectives/perspective_instance.rs (2)
  • get_links (1151-1188)
  • new (182-200)
core/src/perspectives/PerspectiveProxy.ts (1)
  • PerspectiveProxy (293-1258)
rust-client/src/perspective_proxy.rs (2)
  • new (24-29)
  • subject_classes (426-436)
cli/src/formatting.rs (1)
  • print_link (130-149)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (4)
cli/src/formatting.rs (1)

130-149: LGTM: literal decoding in link printer

Decoding source/target/predicate greatly improves readability without changing public API.

cli/src/repl.rs (3)

45-82: LGTM: flexible get_links parser

Optional groups with “_” sentinel map cleanly to Option; errors are surfaced succinctly.


84-99: LGTM: all-links alias

Clear alias to fetch all links; messaging is consistent.


657-665: LGTM: concise, helpful welcome banner

Clear guidance and consistent styling.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
rust-client/src/perspective_proxy.rs (1)

359-369: Don’t propagate on non-literal SDNA targets; skip gracefully.

This still uses ? and will fail the whole call on a single malformed target. Mirror the tolerant handling used in get_dna().

Apply:

-            if first_sdna_code.is_none() {
-                let literal = Literal::from_url(sdna_link.data.target.clone())?;
-                match literal.get() {
-                    Ok(LiteralValue::String(string)) => {
-                        first_sdna_code = Some(string);
-                    },
-                    _ => continue,
-                }
-            }
+            if first_sdna_code.is_none() {
+                if let Ok(literal) = Literal::from_url(sdna_link.data.target.clone()) {
+                    if let Ok(LiteralValue::String(string)) = literal.get() {
+                        first_sdna_code = Some(string);
+                    }
+                }
+            }
🧹 Nitpick comments (5)
cli/README.md (2)

236-249: Specify a language on fenced code block (markdownlint MD040).

Add a language hint (text) to the “System Commands” code fence for consistency and to satisfy linters.

Apply:

-```
+```text
 # Show help
 help
@@
 # Exit the REPL
 exit

---

`231-234`: **Minor style: add code-fence language for sdna/sdna-authors block.**

Same MD040 nit as above; prefer text to avoid incorrect syntax highlighting.

Suggested change:

```diff
-```
+```text
 # Show DNA/schema of a class
 sdna <class>
 
 # Show SDNA authorship information (debugging)
 sdna-authors

</blockquote></details>
<details>
<summary>rust-client/src/perspective_proxy.rs (3)</summary><blockquote>

`120-201`: **Make SDNA-loaded check more precise and cheaper.**

- Querying `subject_class(Name, Ref).` for each fact and scanning all rows is O(n²). Prefer direct checks per pair or one indexed query.
- Be consistent with query formatting: elsewhere in this file queries omit a trailing dot; mixing may cause inconsistent engine behavior.

Minimal change (precision + consistency):

```diff
-            // Use a general query with both variables to check if the fact exists
-            let query = "subject_class(Name, Ref).".to_string();
+            // Direct check for this exact fact (align with other queries: no trailing '.')
+            let query = format!(r#"subject_class("{}", "{}")"#, class_name, ref_value);

And allow boolean responses:

-                    } else {
-                        return Ok(false);
-                    }
+                    } else {
+                        return Ok(false);
+                    }
                 }
-                Err(_) => {
-                    return Ok(false);
-                }
+                Err(_) => return Ok(false),
             }

Optionally cache results or perform a single infer("subject_class(Name, Ref)") and build a HashSet<(Name,Ref)> to test membership.


261-284: Use sets for uniqueness to avoid O(n²) scans.

Vec::contains in tight loops for authors/codes is quadratic. Use HashSet alongside the Vecs for stable output + fast dedup.

Example pattern:

-                if !name_authors.contains(&name_author) {
-                    name_authors.push(name_author);
-                }
+                // Maintain a HashSet<String> name_authors_seen to guard pushes
+                if name_authors_seen.insert(name_author.clone()) {
+                    name_authors.push(name_author);
+                }

-                    if !code_authors.contains(&sdna_link.author) {
-                        code_authors.push(sdna_link.author.clone());
-                    }
+                    if code_authors_seen.insert(sdna_link.author.clone()) {
+                        code_authors.push(sdna_link.author.clone());
+                    }

604-614: Public stubs return placeholder data; guard call sites or mark TODO.

These methods return None/"local_agent". If exposed publicly, they can mislead downstream consumers. Either implement or annotate clearly and ensure callers handle None/placeholder.

Would you like a follow-up PR that wires these to the client (and adds unit tests)?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1a78ebf and 0162e96.

📒 Files selected for processing (3)
  • cli/README.md (2 hunks)
  • cli/src/repl.rs (6 hunks)
  • rust-client/src/perspective_proxy.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/src/repl.rs
🧰 Additional context used
🧬 Code graph analysis (1)
rust-client/src/perspective_proxy.rs (1)
rust-client/src/literal.rs (2)
  • from_url (27-36)
  • from_string (38-43)
🪛 LanguageTool
cli/README.md

[grammar] ~232-~232: There might be a mistake here.
Context: ... SDNA authorship information (debugging) sdna-authors ``` #### System Commands `...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
cli/README.md

234-234: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (1)
cli/README.md (1)

202-207: get_links and all-links docs look good.

Clear signatures and aliasing; matches the REPL changes summary.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/repl.rs (1)

67-69: Fix parameter order in get_links() call (predicate/target swapped).

Help shows get_links(, , ), but the call uses (source, target, predicate).

Apply this diff:

-        match perspective
-            .get(source, target, predicate, None, None, None)
-            .await
+        match perspective
+            .get(source, predicate, target, None, None, None)
+            .await
♻️ Duplicate comments (1)
cli/src/repl.rs (1)

193-195: Replace invalid Prolog example at footer; avoid conflicting guidance.

This exact issue was flagged earlier; it slipped back in here.

-        println!("\x1b[90mTip: Use uppercase letters for variables in Prolog queries");
-        println!("\x1b[90mExample: ?Person knows Bob");
+        println!("\x1b[90mTip: Variables start uppercase in Prolog (see examples above)");
🧹 Nitpick comments (4)
cli/src/repl.rs (4)

205-209: Use ANSI clear sequence instead of printing 50 newlines.

Smaller, faster, and truly clears the screen.

-            // Clear the screen by printing multiple newlines
-            for _ in 0..50 {
-                println!();
-            }
+            // Clear screen and move cursor home
+            print!("\x1b[2J\x1b[H");

246-248: Avoid reloading Syntax/Theme sets on every sdna call.

Load once with OnceLock to cut startup cost on repeated sdna runs.

-                // Load these once at the start of your program
-                let ps = SyntaxSet::load_defaults_newlines();
-                let ts = ThemeSet::load_defaults();
+                let ps = SYNTAX_SET.get_or_init(SyntaxSet::load_defaults_newlines);
+                let ts = THEME_SET.get_or_init(ThemeSet::load_defaults);

Add near the top of this file (outside the function):

use std::sync::OnceLock;

static SYNTAX_SET: OnceLock<SyntaxSet> = OnceLock::new();
static THEME_SET: OnceLock<ThemeSet> = OnceLock::new();

And update highlight usage to pass ps (already a &SyntaxSet) rather than &ps where applicable.


249-252: Prefer plain_text accessor; remove unwrap.

-                let syntax = ps
-                    .find_syntax_by_extension("pl")
-                    .unwrap_or_else(|| ps.find_syntax_by_name("Plain Text").unwrap());
+                let syntax = ps
+                    .find_syntax_by_extension("pl")
+                    .unwrap_or_else(|| ps.find_syntax_plain_text());

381-386: Reset color after printing NOT LOADED code.

Prevents gray bleed into subsequent output.

-                            for line in LinesWithEndings::from(sdna_code) {
-                                print!("\x1b[90m{}", line);
-                            }
+                            for line in LinesWithEndings::from(sdna_code) {
+                                print!("\x1b[90m{}\x1b[0m", line);
+                            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0162e96 and dee3d7c.

📒 Files selected for processing (1)
  • cli/src/repl.rs (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cli/src/repl.rs (4)
rust-executor/src/perspectives/perspective_instance.rs (2)
  • get_links (1151-1188)
  • new (182-200)
core/src/perspectives/PerspectiveProxy.ts (1)
  • PerspectiveProxy (293-1258)
rust-client/src/perspective_proxy.rs (2)
  • new (24-29)
  • subject_classes (440-450)
cli/src/formatting.rs (1)
  • print_link (130-149)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (6)
cli/src/repl.rs (6)

84-94: all-links handler looks good.

Straight-through to PerspectiveProxy::get(None, …) with clear output header.


101-107: classes command LGTM.

Matches rename and prints one per line as expected.


212-228: Version output: safe fallbacks for Git metadata look good.

env! for Cargo-provided fields is fine; option_env! for Git vars avoids build failures.


395-476: sdna-authors output reads well; loading checks per instance are helpful.

Nice summary with consolidated authorship and load markers.


673-680: Welcome banner: concise and helpful.

Good UX touch.


694-712: Dispatch ordering improvement LGTM.

Early routing to help/system/get_links/all-links reduces accidental Prolog runs.

jhweir
jhweir previously approved these changes Sep 5, 2025
@lucksus lucksus merged commit 24cc5ab into dev Sep 5, 2025
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants