diff --git a/crates/intent/README.md b/crates/intent/README.md index 8a95ecd..89180a1 100644 --- a/crates/intent/README.md +++ b/crates/intent/README.md @@ -28,13 +28,14 @@ standalone `intent review-fixtures` command uses the same function and retains ## Graph references `intent graph --json` keeps the existing `nodes` and `edges` arrays and adds one -source-located `references` record per explicit `req:` trace, local Markdown -link, normative A/T/R-style ID citation, and scoped decision or delta ordinal +source-located `references` record per explicit `req:` trace, Markdown link, +normative A/T/R-style ID citation, and scoped decision or delta ordinal citation. `syntax` distinguishes those four forms. Each record preserves the -written target, reports `resolved`, `ambiguous`, or `dangling`, and lists every -matching target location; link records also preserve their visible label and -normalized repository-relative path, while citation records expose the -normalized ID or ordinal and the scope used for resolution. +written target, reports `resolved`, `ambiguous`, `dangling`, or `external`, and +lists every matching target location. Link records also preserve their visible +label; local links expose their normalized repository-relative path, while +external links have no target location. Citation records expose the normalized +ID or ordinal and the scope used for resolution. ## `check` exiting 0 does not mean it read anything diff --git a/crates/intent/src/graph_references.rs b/crates/intent/src/graph_references.rs index bd1b3b5..a66cf2d 100644 --- a/crates/intent/src/graph_references.rs +++ b/crates/intent/src/graph_references.rs @@ -488,7 +488,7 @@ fn markdown_links_in(document: &Document) -> Vec { }; let raw = line[target_start..paren_end].trim(); let target = markdown_destination(raw); - if !target.is_empty() && !is_external_target(target) { + if !target.is_empty() { links.push(MarkdownLink { label: visible_label(&line[open + 1..close]), written_target: target.to_string(), @@ -539,6 +539,23 @@ fn resolve_markdown_link( link: &MarkdownLink, documents_by_path: &BTreeMap, ) -> ResolvedLink { + if is_external_target(&link.written_target) { + return ResolvedLink { + reference: GraphReference { + syntax: "markdown_link", + source: document.location(link.range.clone()), + written_target: link.written_target.clone(), + resolution: "external", + target_locations: Vec::new(), + label: Some(link.label.clone()), + resolved_target: None, + normalized_target: None, + scope: None, + }, + target_path: None, + }; + } + let decoded = percent_decode_minimal(&link.written_target); let (file_part, anchor) = split_anchor(&decoded); let lexical_target = if file_part.is_empty() { diff --git a/crates/intent/tests/intent_check.rs b/crates/intent/tests/intent_check.rs index e70a4fc..1a9b198 100644 --- a/crates/intent/tests/intent_check.rs +++ b/crates/intent/tests/intent_check.rs @@ -265,7 +265,7 @@ fn graph_json_exposes_files_ids_links_and_wikilinks() { } #[test] -fn graph_json_records_each_local_markdown_link_with_resolution_and_locations() { +fn graph_json_records_local_and_external_markdown_links() { let h = Harness::new(); fs::write( h.repo.join("context/intent/requirements.md"), @@ -274,7 +274,7 @@ fn graph_json_records_each_local_markdown_link_with_resolution_and_locations() { .expect("requirements"); fs::write( h.repo.join("context/intent/spec.md"), - "# Spec\n\nSee π [the requirement](./requirements.md#local-target), [missing](./missing.md), and [external](https://example.com/docs).\n", + "# Spec\n\nSee π [the requirement](./requirements.md#local-target), [missing](./missing.md), and [Decision 0042](https://github.com/compoundingtech/intent/commit/9561206).\n", ) .expect("spec"); @@ -292,11 +292,7 @@ fn graph_json_records_each_local_markdown_link_with_resolution_and_locations() { .iter() .filter(|reference| reference["syntax"] == "markdown_link") .collect::>(); - assert_eq!( - links.len(), - 2, - "external URLs are not local link references" - ); + assert_eq!(links.len(), 3); let resolved = links .iter() @@ -324,6 +320,18 @@ fn graph_json_records_each_local_markdown_link_with_resolution_and_locations() { assert_eq!(dangling["resolution"], "dangling"); assert_eq!(dangling["resolved_target"], "context/intent/missing.md"); assert!(dangling["target_locations"].as_array().unwrap().is_empty()); + + let external = links + .iter() + .find(|reference| { + reference["written_target"] + == "https://github.com/compoundingtech/intent/commit/9561206" + }) + .unwrap(); + assert_eq!(external["label"], "Decision 0042"); + assert_eq!(external["resolution"], "external"); + assert!(external["target_locations"].as_array().unwrap().is_empty()); + assert!(external.get("resolved_target").is_none()); } #[test]