This document describes how to organize Topos specifications across multiple files for complex projects.
Real-world projects require splitting specifications across multiple files for:
- Separation of concerns: Different domains in different files
- Team ownership: Teams own their specs independently
- Scalability: Thousands of requirements don't fit in one file
- Reusability: Shared types and principles across projects
Topos supports multi-file projects with explicit imports, qualified references, and workspace-wide analysis.
project/
├── topos.toml # Project configuration
├── specs/
│ ├── common/
│ │ ├── principles.tps # Shared principles
│ │ ├── types.tps # Shared type definitions
│ │ └── errors.tps # Common error types
│ ├── domains/
│ │ ├── users/
│ │ │ ├── mod.tps # Module index
│ │ │ ├── requirements.tps
│ │ │ ├── concepts.tps
│ │ │ └── tasks.tps
│ │ ├── orders/
│ │ │ ├── mod.tps
│ │ │ ├── requirements.tps
│ │ │ ├── concepts.tps
│ │ │ └── tasks.tps
│ │ └── payments/
│ │ └── ...
│ └── main.tps # Root spec (optional)
└── src/ # Implementation
project/
├── topos.toml
├── specs/
│ ├── principles.tps # All principles
│ ├── requirements/
│ │ ├── users.tps
│ │ ├── orders.tps
│ │ └── payments.tps
│ ├── design/
│ │ ├── architecture.tps
│ │ └── components.tps
│ ├── concepts/
│ │ ├── users.tps
│ │ ├── orders.tps
│ │ └── payments.tps
│ └── tasks/
│ ├── sprint-1.tps
│ ├── sprint-2.tps
│ └── backlog.tps
└── src/
project/
├── topos.toml
├── specs/
│ ├── common.tps
│ ├── features/
│ │ ├── authentication.tps # All layers for auth
│ │ ├── checkout.tps # All layers for checkout
│ │ └── admin.tps # All layers for admin
│ └── integrations/
│ ├── stripe.tps
│ └── sendgrid.tps
└── src/
[project]
name = "ecommerce"
version = "1.0.0"
[specs]
root = "specs" # Spec files directory
include = ["**/*.tps"] # Files to include
exclude = ["**/drafts/**"] # Files to exclude
[principles]
# Principles files are inherited by all specs
inherit = ["specs/common/principles.tps"]
[requirements]
# Requirement ID prefixes by domain
prefixes = { users = "USR", orders = "ORD", payments = "PAY" }
[workspace]
# Related projects for cross-project references
dependencies = [
{ name = "shared-types", path = "../shared-types" }
]Use import to bring definitions into scope:
spec UserManagement
import from "./common/types.tps":
`Email`, `Identifier`, `DateTime`
import from "./common/errors.tps":
`ValidationError`, `NotFoundError`
import from "../orders/concepts.tps":
`Order` # Reference Order from another domain
# Now use imported types
Concept User:
field id (`Identifier`)
field email (`Email`)
field orders (`List` of `Order`)
# Import specific items
import from "path/to/file.tps":
`Name1`, `Name2`, `Name3`
# Import with alias
import from "path/to/file.tps":
`LongConceptName` as `Short`
# Import all exports
import from "path/to/file.tps": *
# Import module for qualified access
import "path/to/file.tps" as users
# Then use: `users.User`, `users.create_user`
# Relative to current file
import from "./sibling.tps": `Type`
import from "../parent/file.tps": `Type`
# Relative to project root (specs/)
import from "/common/types.tps": `Type`
# From dependency project
import from "shared-types/core.tps": `Type`
By default, all top-level definitions are exported. Use private to hide:
spec InternalModule
# Exported (default)
Concept User:
field id (`Identifier`)
# Not exported
private Concept InternalHelper:
field temp (`String`)
# Exported behavior
Behavior create_user:
...
# Not exported
private Behavior validate_internal:
...
Use mod.tps as a module's public interface:
# users/mod.tps
spec Users
# Re-export from subfiles
export from "./concepts.tps":
`User`, `UserStatus`
export from "./requirements.tps":
`REQ-USR-*` # Glob pattern for requirements
export from "./tasks.tps":
`TASK-USR-*`
# Don't export internal details
# (concepts.tps might have private helpers)
Consumers import from the module:
import from "./users/mod.tps":
`User`, `create_user`
# Or import the module
import "./users/mod.tps" as users
Reference definitions from other files without importing:
spec Orders
# Full path reference
Concept Order:
field customer (`/users/concepts.User`)
field payment (`/payments/concepts.Payment`)
- Local scope (current file)
- Explicit imports
- Inherited principles
- Project-wide (if enabled in config)
Requirements have project-wide unique IDs:
# In users/requirements.tps
## REQ-USR-001: User Registration
# In orders/requirements.tps
## REQ-ORD-001: Order Creation
# Cross-reference in any file
Behavior create_order:
Implements REQ-ORD-001.
requires:
user is registered # See REQ-USR-001
Tasks can depend on tasks in other files:
# In users/tasks.tps
## TASK-USR-001: Create User model [REQ-USR-001]
# In orders/tasks.tps
## TASK-ORD-001: Create Order model [REQ-ORD-001]
depends: TASK-USR-001 # Cross-file dependency
Principles in inherited files apply to all specs:
# common/principles.tps
spec CommonPrinciples
# Principles
- Test-First: All implementation follows TDD
- Security: All inputs validated
- Accessibility: WCAG 2.1 AA compliance
# topos.toml
[principles]
inherit = ["specs/common/principles.tps"]All specs in the project inherit these principles.
Domains can add additional principles:
# payments/principles.tps
spec PaymentPrinciples
# Inherits from common, adds:
# Principles
- PCI-Compliance: No card data on our servers
- Idempotency: All payment operations are idempotent
- Audit-Trail: All transactions logged
# payments/concepts.tps
spec PaymentConcepts
import principles from "./principles.tps"
# This file has both common + payment principles
Use prefixes to avoid ID collisions:
# topos.toml
[requirements]
prefixes = {
users = "USR",
orders = "ORD",
payments = "PAY",
admin = "ADM"
}Generated IDs:
REQ-USR-001,REQ-USR-002(users domain)REQ-ORD-001,REQ-ORD-002(orders domain)
Similar pattern for tasks:
TASK-USR-001, TASK-ORD-001, TASK-PAY-001
Concepts are namespaced by file path:
# users/concepts.tps defines users.User
# orders/concepts.tps defines orders.Order
# Explicit qualification when ambiguous
field user (`users.User`)
field order (`orders.Order`)
The LSP analyzes the entire workspace:
Workspace Analysis
═══════════════════════════════════════════════════════
Files: 24 spec files
Definitions: 45 concepts, 78 behaviors, 156 requirements
Cross-File Issues:
⚠ W201: Circular import detected
users/concepts.tps → orders/concepts.tps → users/concepts.tps
⚠ W104: Task without requirement link
payments/tasks.tps:45 - TASK-PAY-012
✗ E101: Undefined reference
orders/concepts.tps:23 - `users.UserProfile` not found
Did you mean `users.User`?
Traceability Summary:
Domain Requirements With Behaviors With Tasks Coverage
────────────────────────────────────────────────────────────
users 12 12 (100%) 10 (83%) 83%
orders 18 15 (83%) 12 (67%) 67%
payments 8 8 (100%) 4 (50%) 50%
────────────────────────────────────────────────────────────
Total 38 35 (92%) 26 (68%) 68%
LSP supports workspace-wide navigation:
- Go to Definition: Jumps across files
- Find All References: Searches entire workspace
- Rename Symbol: Updates all files
- Workspace Symbols: Search all definitions
Best when you have clear bounded contexts:
specs/
├── common/ # Shared across domains
├── identity/ # Auth, users, permissions
├── catalog/ # Products, categories
├── ordering/ # Orders, fulfillment
├── billing/ # Payments, invoices
└── shipping/ # Delivery, tracking
Each domain has:
- Own requirements (REQ-DOM-NNN)
- Own concepts and behaviors
- Own tasks
- Defined interfaces to other domains
Best when different roles work on different layers:
specs/
├── principles.tps # Architects
├── requirements/ # Product managers
├── design/ # Architects
├── concepts/ # Domain experts
└── tasks/ # Engineering leads
Best when teams own features end-to-end:
specs/
├── common.tps
└── features/
├── user-onboarding.tps # Team A
├── checkout-flow.tps # Team B
├── subscription.tps # Team C
└── analytics.tps # Team D
Each feature file contains all layers for that feature.
Best for evolving projects:
specs/
├── stable/ # Shipped, stable specs
│ ├── v1/
│ └── v2/
├── current/ # Current release
└── planned/ # Future releases
├── q1-2025/
└── q2-2025/
spec CommonTypes
Concept Identifier:
A unique identifier (UUID v4).
Concept Email:
A validated email address.
Concept Money:
field amount (`Decimal`)
field currency (`Currency`)
Concept Currency:
one of: USD, EUR, GBP, JPY
spec CorePrinciples
# Principles
- Test-First: All code has tests before implementation
- Security: All inputs validated, no SQL injection
- Accessibility: WCAG 2.1 AA for all UI
- Performance: API responses under 200ms p95
spec UserRequirements
## REQ-USR-001: User Registration
As a visitor, I want to register so I can access the platform.
when: user submits valid email and password
the system shall: create account with unverified status
acceptance:
given: visitor on registration page
when: enters valid credentials
then: account created
then: verification email sent
## REQ-USR-002: User Login
As a registered user, I want to log in to access my account.
when: user submits correct credentials
the system shall: create session and return token
acceptance:
given: verified user account
when: enters correct password
then: JWT token returned
then: session created
spec UserConcepts
import from "/common/types.tps":
`Identifier`, `Email`, `DateTime`
Concept User:
field id (`Identifier`): unique
field email (`Email`): unique
field password_hash (`Hash`)
field status (`UserStatus`): default: `unverified`
field created_at (`DateTime`)
Concept UserStatus:
one of: unverified, active, suspended
Behavior register:
Implements REQ-USR-001.
given:
email (`Email`)
password (`String`)
returns: `User` or `RegistrationError`
ensures:
`result.status` is `unverified`
Behavior login:
Implements REQ-USR-002.
given:
email (`Email`)
password (`String`)
returns: `Session` or `AuthError`
spec UserTasks
# Tasks
## TASK-USR-001: Create User model [REQ-USR-001]
file: src/domains/users/models/user.ts
tests: src/domains/users/models/user.test.ts
status: done
## TASK-USR-002: Create AuthService [REQ-USR-001, REQ-USR-002]
file: src/domains/users/services/auth-service.ts
tests: src/domains/users/services/auth-service.test.ts
depends: TASK-USR-001
status: in-progress
## TASK-USR-003: Create auth endpoints [REQ-USR-001, REQ-USR-002]
file: src/domains/users/controllers/auth-controller.ts
depends: TASK-USR-002
status: pending
spec OrderConcepts
import from "/common/types.tps":
`Identifier`, `Money`, `DateTime`
import from "/users/concepts.tps":
`User`
Concept Order:
field id (`Identifier`): unique
field customer (`User`) # Cross-domain reference
field items (`List` of `OrderItem`)
field total (`Money`)
field status (`OrderStatus`)
Concept OrderItem:
field product_id (`Identifier`)
field quantity (`Natural`)
field unit_price (`Money`)
Concept OrderStatus:
one of: pending, confirmed, shipped, delivered, cancelled
Behavior create_order:
Implements REQ-ORD-001.
given:
customer (`User`)
items (`List` of `OrderItem`)
returns: `Order` or `OrderError`
requires:
`customer.status` is `active` # Cross-domain constraint
`items` is not empty
# Initialize project structure
topos init
# Validate all specs in workspace
topos check
# Show cross-file traceability
topos trace
# Generate traceability report
topos report --format html --output report.html
# Find all references to a definition
topos references "users.User"
# Show dependency graph
topos deps --format mermaid- Use consistent naming conventions across files
- Keep related specs in the same directory
- Use
mod.tpsto define public interfaces - Namespace requirement and task IDs by domain
- Document cross-domain dependencies explicitly
- Create circular imports between domains
- Duplicate definitions across files
- Use absolute paths when relative works
- Mix unrelated concepts in one file
- Ignore cross-file traceability warnings
| File Type | Recommended Max | Split When |
|---|---|---|
| Requirements | 20 requirements | > 30 requirements |
| Concepts | 10 concepts | > 15 concepts |
| Behaviors | 15 behaviors | > 20 behaviors |
| Tasks | 30 tasks | > 50 tasks |
| Single feature | 200 lines | > 300 lines |