Skip to content

Commit 628cc67

Browse files
author
Test User
committed
Add --direction UPSTREAM|DOWNSTREAM to collect command
Enable bidirectional traversal for the collect command. UPSTREAM (default) traverses derivedFrom relations to ancestors. DOWNSTREAM traverses derive relations to descendants, enabling enumeration of all children under impact_scope parent entries.
1 parent a947a1b commit 628cc67

File tree

16 files changed

+355
-39
lines changed

16 files changed

+355
-39
lines changed

claude-plugins/commands/collect.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
---
22
allowed-tools: Read, Bash(reqvire:*)
33
argument-hint: <element-name>
4-
description: Collect and summarize requirement context via derivedFrom chain
4+
description: Collect and summarize requirement context via derivedFrom chain (upstream) or derive chain (downstream)
55
model: claude-sonnet-4-5
66
---
77

88
# Collect Requirement Context
99

10-
Collect and present a comprehensive summary of requirement context via the derivedFrom chain.
10+
Collect and present a comprehensive summary of requirement context via the derivedFrom chain (upstream, default) or derive chain (downstream).
1111

1212
## Model Context
1313

claude-plugins/commands/generate-tasks.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,24 @@ Generate implementation task plan from requirement changes on a feature branch.
3737
reqvire change-impact --git-commit=$BASE_COMMIT --json --output /tmp/impact.json
3838
```
3939

40-
3. **Review impact scope** (from JSON `impact_scope[]`):
40+
3. **Review impact scope and enumerate covered elements** (from JSON `impact_scope[]`):
4141

42-
The `impact_scope` array shows the per-branch common parent requirements covering all impacted elements. Use this to understand the high-level affected model areas before diving into details.
42+
The `impact_scope` array shows the per-branch common parent requirements covering all impacted elements. For each scope root, use downstream collect to enumerate all covered children:
43+
```bash
44+
reqvire collect "<scope-root-name>" --direction DOWNSTREAM --json --output /tmp/scope_<name>.json
45+
```
46+
47+
This returns the scope root and all its descendants, giving you the complete list of affected elements under each scope entry.
4348

4449
4. **For each changed requirement:**
4550

46-
Get full context using collect:
51+
Get full upstream context using collect:
4752
```bash
4853
reqvire collect "<requirement-name>" --json --output /tmp/req_<requirement-id>.json
4954
```
5055

5156
This provides:
52-
- Complete requirement chain via derivedFrom relations
57+
- Complete ancestor chain via derivedFrom relations (upstream)
5358
- All parent requirements for context
5459
- Refinement elements (specifications, constraints, behaviors) that refine the requirement
5560
- Attached design documents

claude-plugins/skills/syseng/SKILL.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,29 +144,32 @@ Refinements are owned via `refinedBy` on the requirement (refinement gets auto-g
144144
3. Never guess - read files before making changes
145145
4. Validate after each significant change
146146
5. When reading requirements, always check for **attachments** (documents, diagrams, images)
147-
6. Use `reqvire collect` to gather full context from requirement chains (ancestors + attachments)
147+
6. Use `reqvire collect` to gather full context from requirement chains (ancestors or descendants + attachments)
148148

149149
Use `reqvire collect` to gather complete context for a requirement:
150150

151151
```bash
152-
# Get full requirement chain with all ancestor content and attachments
152+
# Get ancestor chain (upstream - default)
153153
reqvire collect "Feature Requirement"
154154

155+
# Get all descendants (downstream)
156+
reqvire collect "Feature Requirement" --direction DOWNSTREAM
157+
155158
# JSON format for programmatic use
156159
reqvire collect "Feature Requirement" --json
160+
reqvire collect "Feature Requirement" --direction DOWNSTREAM --json
157161
```
158162

159163
**When to use collect:**
160-
- Before implementing a requirement - get full specification context
164+
- **Upstream (default)**: Get full ancestor specification context before implementing
165+
- **Downstream**: Enumerate all children under a parent (e.g., from impact_scope entries)
161166
- When analyzing impact of changes - understand complete requirement chain
162167
- When creating tasks from requirements - gather all related specifications
163168
- When reviewing requirements - see full derivation hierarchy with sources
164169

165-
The collect command traverses `derivedFrom` relations upward and includes:
166-
- All ancestor requirement content
167-
- Attached markdown files (read as content)
168-
- Attached refinement elements (specifications, constraints, behaviors)
169-
- Source citations for traceability
170+
The collect command supports two directions:
171+
- **UPSTREAM** (default): Traverses `derivedFrom` relations upward — ancestors, specs, attachments
172+
- **DOWNSTREAM**: Traverses `derive` relations downward — all children to leaf elements
170173

171174
## Command Reference
172175

claude-plugins/skills/syseng/reference/CreatingTasks.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,31 +49,46 @@ The change-impact command identifies:
4949
- **Requirement → Implementation**: May need implementation updates
5050
- **Verification changes**: Generally don't propagate upward
5151

52+
### Step 1.5: Enumerate Covered Elements from Impact Scope
53+
54+
For each entry in `impact_scope[]`, use downstream collect to find all covered children:
55+
56+
```bash
57+
# Get all descendants under each scope root
58+
reqvire collect "<scope-root-name>" --direction DOWNSTREAM --json --output /tmp/scope_<name>.json
59+
```
60+
61+
This ensures no elements are missed — `impact_scope` entries are common parents that may cover multiple added/changed children.
62+
5263
### Step 2: Gather Full Requirement Context
5364

54-
For each changed requirement, collect complete context:
65+
For each changed requirement (from `added[]`, `changed[]`, or enumerated via downstream collect), gather upstream context:
5566

5667
```bash
57-
# Get full requirement chain with ancestors and attachments
68+
# Get full ancestor chain with attachments (upstream - default)
5869
reqvire collect "<requirement-name>" --json --output /tmp/req_<requirement-id>.json
5970

6071
# Also save human-readable format for reference
6172
reqvire collect "<requirement-name>" > /tmp/req_context_<requirement-id>.md
6273

74+
# Get all descendants under a requirement (downstream)
75+
reqvire collect "<requirement-name>" --direction DOWNSTREAM --json --output /tmp/req_<requirement-id>_tree.json
76+
6377
# Get direct requirement details
6478
reqvire search --filter-id="<requirement-id>" --json
6579
```
6680

6781
**Why use `reqvire collect` for task generation:**
68-
- Gathers complete requirement chain via `derivedFrom` relations
69-
- Shows parent requirements (the "why" context)
82+
- **Upstream (default)**: Gathers ancestor chain via `derivedFrom` — the "why" context
83+
- **Downstream**: Enumerates all children via `derive` — find everything under a scope root
7084
- Includes all specifications and design documents
7185
- Captures constraints and validation rules
7286
- Provides full implementation context in one command
7387
- Saves to `/tmp` for developer reference during implementation
7488

7589
**What collect provides:**
76-
- All ancestor requirement content
90+
- **Upstream**: All ancestor requirement content (parent chain to root)
91+
- **Downstream**: All descendant requirement content (children to leaves)
7792
- Attached markdown files (read as content)
7893
- Attached refinement elements (specifications, constraints, behaviors)
7994
- Source citations for traceability
@@ -205,12 +220,13 @@ When analyzing requirements for task generation:
205220
| To Understand This | Use This Command |
206221
|--------------------|------------------|
207222
| What requirements changed | `reqvire change-impact --git-commit=<hash> --json` |
208-
| **Full requirement context** | `reqvire collect "<name>" --json --output /tmp/req_<id>.json` |
223+
| **Full requirement context (ancestors)** | `reqvire collect "<name>" --json --output /tmp/req_<id>.json` |
209224
| Requirement direct content | `reqvire search --filter-id="<id>" --json` |
210225
| What verifies a requirement | `reqvire traces --filter-id="<id>" --json` |
211226
| Which tests to run | Extract `satisfiedBy` from verification via `reqvire search` |
212227
| Implementation status | Check `satisfiedBy` relations in requirement |
213-
| Requirement hierarchy | `reqvire collect "<name>"` shows complete derivedFrom chain |
228+
| Requirement hierarchy (up) | `reqvire collect "<name>"` shows derivedFrom ancestor chain |
229+
| Requirement hierarchy (down) | `reqvire collect "<name>" --direction DOWNSTREAM` shows all descendants |
214230

215231
**Why use commands instead of reading files:**
216232
- Automatic relation following

cli/src/cli.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -490,12 +490,16 @@ pub enum Commands {
490490
output: Option<String>,
491491
},
492492

493-
/// Collect content from requirement chain via derivedFrom relations
494-
#[clap(override_help = "Collect content from requirement chain via derivedFrom relations\n\nCOLLECT OPTIONS:\n <ELEMENT_NAME> Name of the requirement element to collect from\n --json Output results in JSON format\n --output <FILE> Save JSON output to file (requires --json)")]
493+
/// Collect content from requirement chain
494+
#[clap(override_help = "Collect content from requirement chain\n\nCOLLECT OPTIONS:\n <ELEMENT_NAME> Name of the requirement element to collect from\n --direction <DIR> Traversal direction: UPSTREAM (default) or DOWNSTREAM\n --json Output results in JSON format\n --output <FILE> Save JSON output to file (requires --json)")]
495495
Collect {
496496
/// Name of the requirement element to collect from
497497
element_name: String,
498498

499+
/// Traversal direction: UPSTREAM (ancestors) or DOWNSTREAM (descendants)
500+
#[clap(long, value_name = "DIRECTION", default_value = "UPSTREAM", help_heading = "COLLECT OPTIONS")]
501+
direction: String,
502+
499503
/// Output results in JSON format
500504
#[clap(long, help_heading = "COLLECT OPTIONS")]
501505
json: bool,
@@ -1365,14 +1369,23 @@ pub fn handle_command(
13651369
}
13661370
return Ok(0);
13671371
},
1368-
Some(Commands::Collect { element_name, json, output }) => {
1372+
Some(Commands::Collect { element_name, direction, json, output }) => {
13691373
validate_output_requires_json(&output, json)?;
1374+
let collect_direction = match direction.to_uppercase().as_str() {
1375+
"UPSTREAM" => report_collect::CollectDirection::Upstream,
1376+
"DOWNSTREAM" => report_collect::CollectDirection::Downstream,
1377+
_ => {
1378+
eprintln!("error: invalid direction '{}'. Valid values: UPSTREAM, DOWNSTREAM", direction);
1379+
return Ok(1);
1380+
}
1381+
};
13701382
let git_root = git_commands::get_git_root_dir()?;
13711383
let report_output = report_collect::generate_collect_report(
13721384
&model_manager.graph_registry,
13731385
&element_name,
13741386
&git_root,
13751387
json,
1388+
collect_direction,
13761389
)?;
13771390
if json {
13781391
handle_json_output(&report_output, &output)?;

core/src/report_collect.rs

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,25 @@ use std::collections::HashSet;
77
use std::fs;
88
use std::path::Path;
99

10+
/// Direction of traversal for content collection
11+
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
12+
#[serde(rename_all = "lowercase")]
13+
pub enum CollectDirection {
14+
/// Traverse derivedFrom relations upward to ancestors (default)
15+
Upstream,
16+
/// Traverse derive relations downward to descendants
17+
Downstream,
18+
}
19+
20+
impl std::fmt::Display for CollectDirection {
21+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22+
match self {
23+
CollectDirection::Upstream => write!(f, "upstream"),
24+
CollectDirection::Downstream => write!(f, "downstream"),
25+
}
26+
}
27+
}
28+
1029
/// Source type for collected content items
1130
#[derive(Debug, Clone, Serialize)]
1231
#[serde(rename_all = "snake_case")]
@@ -50,6 +69,7 @@ pub struct CollectMetadata {
5069
#[derive(Debug, Serialize)]
5170
pub struct CollectReport {
5271
pub starting_element: String,
72+
pub direction: CollectDirection,
5373
pub items: Vec<CollectedItem>,
5474
pub metadata: CollectMetadata,
5575
}
@@ -60,6 +80,7 @@ pub fn generate_collect_report(
6080
element_name: &str,
6181
git_root: &Path,
6282
json_output: bool,
83+
direction: CollectDirection,
6384
) -> Result<String, ReqvireError> {
6485
// Find element by name
6586
let element_id = registry
@@ -95,18 +116,26 @@ pub fn generate_collect_report(
95116
}
96117
}
97118

98-
// Collect ancestor chain via derivedFrom relations
99-
let ancestor_chain = collect_ancestor_chain(registry, &element_id);
119+
// Collect chain based on direction
120+
let chain = match direction {
121+
CollectDirection::Upstream => collect_ancestor_chain(registry, &element_id),
122+
CollectDirection::Downstream => collect_descendant_chain(registry, &element_id),
123+
};
100124

101125
// Build collected items
102126
let mut items: Vec<CollectedItem> = Vec::new();
103127
let mut element_count = 0;
104128
let mut refinement_count = 0;
105129
let mut attachment_count = 0;
106130

107-
// Process ancestors first (root first, depth 0)
108-
// ancestor_chain is ordered from starting element to root, so we reverse
109-
for (depth, elem_id) in ancestor_chain.iter().rev().enumerate() {
131+
// For upstream: chain is start→root, reverse to get root first (depth 0)
132+
// For downstream: chain is already start→leaves (start at depth 0)
133+
let ordered_chain: Vec<&String> = match direction {
134+
CollectDirection::Upstream => chain.iter().rev().collect(),
135+
CollectDirection::Downstream => chain.iter().collect(),
136+
};
137+
138+
for (depth, elem_id) in ordered_chain.iter().enumerate() {
110139
if let Some(elem) = registry.get_element(elem_id) {
111140
// Add element content
112141
items.push(CollectedItem {
@@ -147,6 +176,7 @@ pub fn generate_collect_report(
147176

148177
let report = CollectReport {
149178
starting_element: element_id,
179+
direction,
150180
items,
151181
metadata: CollectMetadata {
152182
element_count,
@@ -206,6 +236,47 @@ fn collect_ancestor_chain(registry: &GraphRegistry, start_id: &str) -> Vec<Strin
206236
chain
207237
}
208238

239+
/// Collect the descendant chain following derive relations (parent to children)
240+
fn collect_descendant_chain(registry: &GraphRegistry, start_id: &str) -> Vec<String> {
241+
let mut chain = Vec::new();
242+
let mut visited = HashSet::new();
243+
let mut current_level = vec![start_id.to_string()];
244+
245+
while !current_level.is_empty() {
246+
// Sort for deterministic ordering
247+
let mut sorted_level = current_level.clone();
248+
sorted_level.sort();
249+
250+
for elem_id in &sorted_level {
251+
if visited.contains(elem_id) {
252+
continue;
253+
}
254+
visited.insert(elem_id.clone());
255+
chain.push(elem_id.clone());
256+
}
257+
258+
// Find next level (children via derive)
259+
let mut next_level = Vec::new();
260+
for elem_id in &sorted_level {
261+
if let Some(elem) = registry.get_element(elem_id) {
262+
for rel in &elem.relations {
263+
if rel.relation_type.name == "derive" {
264+
if let relation::LinkType::Identifier(target_id) = &rel.target.link {
265+
if !visited.contains(target_id) {
266+
next_level.push(target_id.clone());
267+
}
268+
}
269+
}
270+
}
271+
}
272+
}
273+
274+
current_level = next_level;
275+
}
276+
277+
chain
278+
}
279+
209280
/// Collect content from an attachment
210281
fn collect_attachment_content(
211282
registry: &GraphRegistry,
@@ -481,6 +552,12 @@ fn extract_element_name(identifier: &str) -> String {
481552
mod tests {
482553
use super::*;
483554

555+
#[test]
556+
fn test_collect_direction_display() {
557+
assert_eq!(format!("{}", CollectDirection::Upstream), "upstream");
558+
assert_eq!(format!("{}", CollectDirection::Downstream), "downstream");
559+
}
560+
484561
#[test]
485562
fn test_extract_element_name() {
486563
assert_eq!(

requirements/Functional/Output/Reporting.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ When requested the system shall provide human readable and machine readable Syst
2828

2929
### Collect Content from Requirement Chain
3030

31-
The system shall collect and consolidate all content from a requirement element and its ancestors via derivedFrom relations, including refinedBy targets (refinement elements and specification files) and attachment contents, and output with source citations in text or JSON format.
31+
The system shall collect and consolidate all content from a requirement element and its related requirements via derivedFrom relations (upstream to ancestors) or derive relations (downstream to descendants), including refinedBy targets (refinement elements and specification files) and attachment contents, and output with source citations in text or JSON format.
3232

3333
#### Details
3434
The system shall define:
3535
- Content collection rules for elements, refinedBy targets, and attachments
3636
- Output format specifications for text and JSON modes
37+
- Direction-based traversal: upstream (ancestors via derivedFrom) or downstream (descendants via derive)
3738

3839
#### Metadata
3940
* type: requirement

requirements/Functional/Output/Specifications.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ Technical specification for content collection from requirement chains.
1313

1414
**Traversal Rules:**
1515
- Start from specified requirement element
16-
- Traverse derivedFrom relations in reverse direction (child to parents)
17-
- Continue until root ancestors reached (elements with no derivedFrom)
16+
- When direction is UPSTREAM (default):
17+
- Traverse derivedFrom relations in reverse direction (child to parents)
18+
- Continue until root ancestors reached (elements with no derivedFrom)
19+
- When direction is DOWNSTREAM:
20+
- Traverse derive relations in forward direction (parent to children)
21+
- Continue until leaf descendants reached (elements with no derive to other requirements)
1822
- Include the starting element in output
1923

2024
**Content Collection:**
@@ -31,8 +35,9 @@ Technical specification for content collection from requirement chains.
3135

3236
**Output Ordering:**
3337
- Flat list structure (no nesting)
34-
- Ancestors first (depth 0 = root), then descendants
35-
- Same-level elements sorted alphabetically by name or file path
38+
- When direction is UPSTREAM: ancestors first (depth 0 = root), then starting element
39+
- When direction is DOWNSTREAM: starting element first (depth 0), then descendants at increasing depth
40+
- Same-depth elements sorted alphabetically by name or file path
3641

3742
**Error Handling:**
3843
- Element not found: Error with message

0 commit comments

Comments
 (0)