You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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: selectperspective, 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
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.
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
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.
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.
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.
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.
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.
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
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.
- 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.
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.
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.
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.
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.
⏰ 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.
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);
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)
[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.
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;staticSYNTAX_SET:OnceLock<SyntaxSet> = OnceLock::new();staticTHEME_SET:OnceLock<ThemeSet> = OnceLock::new();
And update highlight usage to pass ps (already a &SyntaxSet) rather than &ps where applicable.
- 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.
⏰ 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Enhanced SDNA Debugging
Improved REPL Commands
all-linksCommand: Simple command to display all links in a perspectivequerytoget_links: More intuitive and descriptive command nameadd_linkwith required status parametersdnaCommand: Added optional class filtering with unified functionality🔧 Technical Changes
New Dependencies
Core Files Modified
cli/src/perspectives.rsinteractive_perspective_selector()- Main UI looprust-client/src/perspective_proxy.rsget_dna(): Returns grouped SDNA with consolidated authorshipis_sdna_loaded()- Checks Prolog loading statussubject_classfacts from SDNA codecli/src/repl.rssdnaandsdna-authorscommandsall_links()Function: Simple command to display all linkslink_query()toget_links(): More intuitive function namesdna()Function: Unified command with optional class filtering🎨 User Experience Improvements
Before
After
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
🔍 Technical Details
Prolog Loading Detection
^\s*subject_class\("([^"]+)",\s*("([^"]+)"|([^,\s)]+))\)\s*\.\s*$subject_class(Name, Ref).with variable binding comparisonMulti-Author Tracking
HashMap<String, (Vec<String>, Vec<String>, Vec<(String, String)>)>HashSetfor efficient author comparisonTerminal UI Architecture
ListStatefor proper selection tracking🐛 Bug Fixes
SDNA Command Restoration
sdnacommand after link topology changesColor Coding Issues
ListState Management
ListStatewith proper synchronizationREPL Command Improvements
querytoget_links, addedall-linkscommand, fixed help textSDNA Command Enhancement
sdnaandsdna <class>with different functionalityget_dnamethod🧪 Testing
Manual Testing Scenarios
all-links,get_links, andadd_linkcommandssdnaandsdna <class>functionalityEdge Cases Handled
📚 Documentation Updates
CLI Help Text
sdnacommand descriptionsdna-authorscommand descriptionall-linkscommand documentationget_linkscommand documentation (renamed fromquery)add_linkcommand syntax documentationsdnacommand documentation with optional filteringCode Comments
🚀 Performance Considerations
Efficient Data Structures
Terminal Performance
🔮 Future Enhancements
Potential Improvements
Technical Debt
📋 Checklist
all-linkscommand addedquerycommand renamed toget_linkssdnacommand with optional class filtering🎉 Impact
This PR significantly improves the developer experience when working with AD4M perspectives and SDNA:
get_links,all-links)The changes maintain backward compatibility while providing a much more user-friendly interface for perspective management and SDNA debugging.
Summary by CodeRabbit
New Features
Improvements
Documentation
Chores