feat(p10-1a-2): add internal SourceSpan::Code variant + design §3.4 sync

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 15:52:01 +00:00
parent 5c265bb59f
commit 9f3edb7e24
4 changed files with 47 additions and 0 deletions

View File

@@ -142,6 +142,18 @@ pub enum SourceSpan {
start_ms: u64,
end_ms: u64,
},
/// p10-1A-2: AST-unit span for code ingest. Internal storage shape
/// (chunks.source_spans_json) — `citation_helper` maps this to the
/// wire `Citation::Code` (added 1A-1). `symbol` is the per-language
/// self-reference path (design §3.4); `<top-level>` / `<module>` for
/// glue regions, never null for an identified unit. `lang` is the
/// canonical code_lang.
Code {
line_start: u32,
line_end: u32,
symbol: Option<String>,
lang: Option<String>,
},
}
// ── Forward-declared stubs (§3.7a). Bodies are final per design. ────────
@@ -195,6 +207,24 @@ mod tests {
/// previously failed at serde runtime because `tag = "kind"` cannot
/// describe a newtype carrying a non-struct value. The struct-variant
/// shape used here is the §9 schema migration.
#[test]
fn source_span_code_round_trips_and_tags_lowercase() {
let s = SourceSpan::Code {
line_start: 10,
line_end: 42,
symbol: Some("foo::Bar::baz".to_string()),
lang: Some("rust".to_string()),
};
let v = serde_json::to_value(&s).unwrap();
assert_eq!(v["kind"], "code");
assert_eq!(v["line_start"], 10);
assert_eq!(v["line_end"], 42);
assert_eq!(v["symbol"], "foo::Bar::baz");
assert_eq!(v["lang"], "rust");
let back: SourceSpan = serde_json::from_value(v).unwrap();
assert_eq!(back, s);
}
#[test]
fn inline_serde_round_trip() {
let cases = vec![

View File

@@ -49,6 +49,13 @@ pub(crate) fn citation_from_first_span(
end_ms: *end_ms,
speaker: None,
},
// TODO(p10-1a-2 Task 3): map to Citation::Code
Some(SourceSpan::Code { .. }) => Citation::Line {
path,
start: 1,
end: 1,
section,
},
// Byte-spans don't have a Citation variant. Fall back to a
// Line citation pointing at the document head — better than
// fabricating a position. Spans-empty falls into the same

View File

@@ -455,6 +455,15 @@ fn describe_span(span: &kebab_core::SourceSpan) -> String {
SourceSpan::Time { start_ms, end_ms } => {
format!("Time {start_ms}-{end_ms} ms")
}
SourceSpan::Code {
line_start,
line_end,
symbol,
..
} => match symbol {
Some(sym) => format!("Code {line_start}-{line_end} ({sym})"),
None => format!("Code {line_start}-{line_end}"),
},
}
}

View File

@@ -565,6 +565,7 @@ pub enum SourceSpan {
Page { page: u32, char_start: Option<u32>, char_end: Option<u32> },
Region { x: u32, y: u32, w: u32, h: u32 },
Time { start_ms: u64, end_ms: u64 },
Code { line_start: u32, line_end: u32, symbol: Option<String>, lang: Option<String> }, // p10-1A-2: internal code-unit span (see tasks/p10/p10-1a-2)
}
```