Bundles and concepts
A bundle is a directory of .md files that forms one specification. Loading a bundle
walks that directory, decides what each file is from its name, parses the concepts, and
indexes them into a graph.
What each file becomes
Section titled “What each file becomes”Filename alone determines how a file is treated, in this order:
| Filename | Treated as | In the graph? |
|---|---|---|
index.md, log.md |
Reserved file | No |
<name>.<type-token>.md |
Concept | Yes, as a node |
Anything else, such as AGENTS.md |
Ignored | No |
Concepts
Section titled “Concepts”A concept is one markdown file: YAML frontmatter plus a markdown body. Its ID is the
bundle-relative path with .md removed, always with forward slashes:
specification/deliver-email.requirement.md -> specification/deliver-email.requirementThat ID is what graph --from takes and what appears in every JSON output.
The <type-token> segment is the kebab-case form of the frontmatter type:
login.requirement.md type: Requirementauth.system.md type: Systemcheckout.user-journey.md type: UserJourneyIf the token and the type disagree, validation warns. The full token list is in
Node types.
Reserved files
Section titled “Reserved files”index.md and log.md are reserved at every directory level. They carry no type, never
become nodes, and are not counted as concepts. Both are generated by
index: index.md lists the concepts in its directory grouped by
type, and log.md at the bundle root accumulates dated entries.
Because they are generated, hand edits are reverted by the next index run.
Ignored files
Section titled “Ignored files”Any other .md file with no .<type-token> segment is skipped. This is what lets AGENTS.md,
README.md, or scratch notes live inside a bundle directory without becoming malformed
concepts.
Skipped files are reported rather than silently dropped:
$ npx graph-spec-cli validate spec/1 file(s) ignored (no type token): AGENTS.md
27 concept(s), 0 error(s), 0 warning(s)validate --json carries the same list in an ignored array.
This rule is a deliberate departure from plain OKF, which treats every non-reserved file as a concept. GraphSpec uses the profile’s filename convention to decide membership so that docs and specs can share a directory.
Frontmatter
Section titled “Frontmatter”Only type is structurally required, and it is the one field OKF itself demands. The profile
adds required fields per type (status on Requirement, level on TestScenario, and so on).
Recommended on every concept:
---type: Requirementtitle: Deliver Email Notificationdescription: The system must email the user within one minute of a qualifying event.status: acceptedtags: [notifications]relations: refines: - /specification/notify.requirement.md---title and description are what index and
query render, so a concept without them produces thin listings. Unknown
frontmatter keys are preserved rather than rejected, per OKF’s extension rule.
Body sections
Section titled “Body sections”Each type has conventional H1 sections (# Acceptance Criteria for Requirement,
# Given/When/Then for TestScenario). These are not enforced, but following them keeps
bundles legible to anyone who has read another one. The parser extracts H1 sections, so they
are addressable.
Directory layout
Section titled “Directory layout”There is none. Group concepts however the project wants. The product/, architecture/,
specification/, glossary/ split used in examples is one convention, not a requirement.
Directories do create implicit parent to child structural edges, which
graph --structure can include. Those are separate from typed relations and
are excluded by default.
Directories that are never walked
Section titled “Directories that are never walked”.git, node_modules, dist, and .vscode are skipped. A bundle nested inside one of them
is invisible to the loader.