Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Bounded relation traversal**: Added `jumbo relations traverse` with deterministic breadth-first traversal, typed root inference, directed graph semantics, depth and edge limits, relation filters, hop-grouped text output, and stable structured output.

### Changed

- **Relation list filters**: Expanded `jumbo relations list` with direction, relation type, related entity type, strength, and entity ID filters while preserving active-only status as the default.

## [3.17.0] - 2026-07-16

### Changed
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Memory and context orchestration for coding agents.
| [Audience Commands](reference/commands/audiences.md) | add, list, update, remove |
| [Audience Pain Commands](reference/commands/audience-pains.md) | add, list, update |
| [Value Proposition Commands](reference/commands/value-propositions.md) | add, list, update, remove |
| [Relation Commands](reference/commands/relations.md) | add, list, remove |
| [Relation Commands](reference/commands/relations.md) | add, list, traverse, remove |
| [Work Commands](reference/commands/work.md) | pause, resume |
| [Maintenance Commands](reference/commands/maintenance.md) | heal, evolve |
| [Worker Commands](reference/commands/worker.md) | view |
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/commands/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Complete reference for all Jumbo CLI commands.
| [Audience Commands](audiences.md) | add, list, update, remove |
| [Audience Pain Commands](audience-pains.md) | add, list, update |
| [Value Proposition Commands](value-propositions.md) | add, list, update, remove |
| [Relation Commands](relations.md) | add, list, remove |
| [Relation Commands](relations.md) | add, list, traverse, remove |
| [Work Commands](work.md) | pause, resume |
| [Maintenance Commands](maintenance.md) | heal, evolve |
| [Telemetry Commands](telemetry.md) | status, enable, disable |
Expand Down
49 changes: 46 additions & 3 deletions docs/reference/commands/relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar:
order: 12
---

Add, list, and remove relationships between entities in the knowledge graph — linking goals, components, decisions, and other entities.
Add, list, traverse, and remove relationships between entities in the knowledge graph — linking goals, components, decisions, and other entities.

---

Expand Down Expand Up @@ -67,20 +67,63 @@ List all knowledge graph relations.
| Option | Description |
|--------|-------------|
| `--entity-type <type>` | Filter by entity type, e.g. `goal`, `decision`, `component` |
| `--entity-id <id>` | Filter by entity ID (requires `--entity-type`) |
| `-s, --status <status>` | Filter by status: `active`, `removed`, `all` (default: `active`) |
| `--entity-id <id>` | Filter by entity ID |
| `-d, --direction <direction>` | Filter relative to the entity: `in`, `out`, or `both` (default: `both`) |
| `--relation-type <type>` | Filter by relation type |
| `--related-entity-type <type>` | Filter by the type at the opposite endpoint |
| `--strength <strength>` | Filter by strength: `strong`, `medium`, or `weak` |
| `-s, --status <status>` | Filter by status: `active`, `deactivated`, `removed`, or `all` (default: `active`) |

### Examples

```bash
> jumbo relations list
> jumbo relations list --entity-type goal
> jumbo relations list --entity-type component --entity-id comp_abc123
> jumbo relations list --entity-id comp_abc123 --direction out --relation-type requires
> jumbo relations list --related-entity-type decision --strength strong
> jumbo relations list --status all
```

---

## jumbo relations traverse

Traverse a bounded portion of the relation graph from one entity. Traversal uses deterministic breadth-first search and preserves every relation's original direction.

### Synopsis

```bash
> jumbo relations traverse --id <id> [options]
```

### Options

| Option | Description |
|--------|-------------|
| `-i, --id <id>` | Entity ID at the traversal root (required) |
| `--entity-type <type>` | Root entity type; inferred when the ID identifies one endpoint type |
| `--depth <depth>` | Traversal depth from `1` through `5` (default: `1`) |
| `-d, --direction <direction>` | Traversal direction: `in`, `out`, or `both` (default: `both`) |
| `--relation-type <type>` | Filter by relation type |
| `--related-entity-type <type>` | Filter each expansion by the opposite endpoint type |
| `--strength <strength>` | Filter by strength: `strong`, `medium`, or `weak` |
| `-s, --status <status>` | Filter by status: `active`, `deactivated`, `removed`, or `all` (default: `active`) |
| `--limit <limit>` | Maximum distinct edges from `1` through `1000` (default: `100`) |

If an ID appears under multiple entity types, specify `--entity-type`. Results include the resolved root, distinct nodes with their minimum hop distance, directed edges, reached depth, limit, and truncation state. Text output groups results by hop; `--format json` returns the stable structured graph result.

### Examples

```bash
> jumbo relations traverse --id goal_abc123
> jumbo relations traverse --id goal_abc123 --entity-type goal --depth 3
> jumbo relations traverse --id comp_abc123 --direction out --relation-type requires --limit 250
> jumbo relations traverse --id comp_abc123 --depth 2 --format json
```

---

## jumbo relation remove

Remove a relation from the knowledge graph.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Complete command documentation.
| [Audience Commands](commands/audiences.md) | add, list, update, remove |
| [Audience Pain Commands](commands/audience-pains.md) | add, list, update |
| [Value Proposition Commands](commands/value-propositions.md) | add, list, update, remove |
| [Relation Commands](commands/relations.md) | add, list, remove |
| [Relation Commands](commands/relations.md) | add, list, traverse, remove |
| [Work Commands](commands/work.md) | pause, resume |
| [Maintenance Commands](commands/maintenance.md) | heal, evolve |
| [Worker Commands](commands/worker.md) | view |
12 changes: 10 additions & 2 deletions src/application/context/relations/get/GetRelationsRequest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { EntityTypeValue } from "../../../../domain/relations/Constants.js";
import { EntityTypeValue, RelationStrengthValue } from "../../../../domain/relations/Constants.js";
import { RelationDirection } from "./RelationDirection.js";
import { RelationStatusFilter } from "./RelationStatusFilter.js";
import { RelationNodeReference } from "./RelationNodeReference.js";

export interface GetRelationsRequest {
readonly entity?: RelationNodeReference;
readonly entityType?: EntityTypeValue;
readonly entityId?: string;
readonly status: "active" | "deactivated" | "removed" | "all";
readonly direction?: RelationDirection;
readonly relationType?: string;
readonly relatedEntityType?: EntityTypeValue;
readonly strength?: RelationStrengthValue;
readonly status: RelationStatusFilter;
}
12 changes: 5 additions & 7 deletions src/application/context/relations/get/IRelationViewReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@
* Used by ListRelationsQueryHandler to retrieve relation list with optional filtering.
*/

import { RelationView } from "../RelationView.js";
import { EntityTypeValue } from "../../../../domain/relations/Constants.js";

export interface RelationListFilter {
entityType?: EntityTypeValue;
entityId?: string;
status?: "active" | "deactivated" | "removed" | "all";
}
import { RelationView } from "../RelationView.js";
import { RelationListFilter } from "./RelationListFilter.js";

export interface IRelationViewReader {
/**
Expand All @@ -19,4 +14,7 @@ export interface IRelationViewReader {
* @returns Array of relation views ordered by creation date
*/
findAll(filter?: RelationListFilter): Promise<RelationView[]>;

/** Returns the distinct projection endpoint types associated with an entity ID. */
findEndpointTypes(entityId: string): Promise<EntityTypeValue[]>;
}
33 changes: 33 additions & 0 deletions src/application/context/relations/get/LocalGetRelationsGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,51 @@ import { IGetRelationsGateway } from "./IGetRelationsGateway.js";
import { GetRelationsRequest } from "./GetRelationsRequest.js";
import { GetRelationsResponse } from "./GetRelationsResponse.js";
import { IRelationViewReader } from "./IRelationViewReader.js";
import { EntityType, RelationStrength } from "../../../../domain/relations/Constants.js";

export class LocalGetRelationsGateway implements IGetRelationsGateway {
constructor(
private readonly relationViewReader: IRelationViewReader
) {}

async getRelations(request: GetRelationsRequest): Promise<GetRelationsResponse> {
this.validate(request);
const entity = request.entity ?? (
request.entityType && request.entityId
? { entityType: request.entityType, entityId: request.entityId }
: undefined
);
const relations = await this.relationViewReader.findAll({
entity,
entityType: request.entityType,
entityId: request.entityId,
direction: request.direction,
relationType: request.relationType,
relatedEntityType: request.relatedEntityType,
strength: request.strength,
status: request.status,
});
return { relations };
}

private validate(request: GetRelationsRequest): void {
if (request.direction && !["in", "out", "both"].includes(request.direction)) {
throw new Error("Direction must be one of: in, out, both.");
}
if (!["active", "deactivated", "removed", "all"].includes(request.status)) {
throw new Error("Status must be one of: active, deactivated, removed, all.");
}
if (request.entityType && !Object.values(EntityType).includes(request.entityType)) {
throw new Error(`Entity type must be one of: ${Object.values(EntityType).join(", ")}.`);
}
if (request.entity && !Object.values(EntityType).includes(request.entity.entityType)) {
throw new Error(`Entity type must be one of: ${Object.values(EntityType).join(", ")}.`);
}
if (request.relatedEntityType && !Object.values(EntityType).includes(request.relatedEntityType)) {
throw new Error(`Related entity type must be one of: ${Object.values(EntityType).join(", ")}.`);
}
if (request.strength && !Object.values(RelationStrength).includes(request.strength)) {
throw new Error("Strength must be one of: strong, medium, weak.");
}
}
}
1 change: 1 addition & 0 deletions src/application/context/relations/get/RelationDirection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type RelationDirection = "in" | "out" | "both";
15 changes: 15 additions & 0 deletions src/application/context/relations/get/RelationListFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { EntityTypeValue, RelationStrengthValue } from "../../../../domain/relations/Constants.js";
import { RelationDirection } from "./RelationDirection.js";
import { RelationStatusFilter } from "./RelationStatusFilter.js";
import { RelationNodeReference } from "./RelationNodeReference.js";

export interface RelationListFilter {
readonly entity?: RelationNodeReference;
readonly entityType?: EntityTypeValue;
readonly entityId?: string;
readonly direction?: RelationDirection;
readonly relationType?: string;
readonly relatedEntityType?: EntityTypeValue;
readonly strength?: RelationStrengthValue;
readonly status?: RelationStatusFilter;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { EntityTypeValue } from "../../../../domain/relations/Constants.js";

export interface RelationNodeReference {
readonly entityType: EntityTypeValue;
readonly entityId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type RelationStatusFilter = "active" | "deactivated" | "removed" | "all";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RelationTraversalResult } from "./RelationTraversalResult.js";
import { TraverseRelationsRequest } from "./TraverseRelationsRequest.js";

export interface ITraverseRelationsGateway {
traverse(request: TraverseRelationsRequest): Promise<RelationTraversalResult>;
}
Loading
Loading