Using arkouda
In repositories that record decisions and requirements as Markdown files with YAML frontmatter (conventionally under docs/adr/ and docs/prd/), arkouda is the CLI for finding, reading, validating, and scaffolding them. Before you decide, check what's already been decided. Before you build, check what's already required. After you decide, capture it.
An arkouda directory is an Open Knowledge Format (OKF) v0.2 knowledge bundle: each document is a concept whose id is its path within the bundle without the .md suffix (security/mtls.md → security/mtls). index.md and log.md are reserved by OKF and are never concepts.
If a repo has no such directory yet but the arkouda binary is installed, this skill is also the right one to reach for: arkouda new enforces the schema from the first file.
Concept types
Arkouda has two built-in types, and picking the wrong one produces a document that fails arkouda check.
ADR (--type adr, the default) |
PRD (--type prd) |
|
|---|---|---|
| Answers | Why is the software built this way? | What is the software supposed to do? |
| Write one when | you commit to a library, datastore, transport, layout, convention, or trade-off | you start on a feature whose scope, boundaries, or success criteria aren't written down |
lifecycle |
proposed, accepted, superseded, deprecated, rejected |
draft, in-review, approved, shipped, abandoned, superseded |
| Required sections | Status, Context, Decision, Consequences |
Status, Problem, Requirements, Non-Goals, Success Metrics |
| Primary section | Decision |
Requirements |
| Default directory | docs/adr |
docs/prd |
The two are linked from the PRD side: a PRD's decisions frontmatter key lists the concept ids of the ADRs that shaped it. Traverse from a requirement to its rationale by reading that key — never by restating the decision inside the PRD.
Apart from Status, the two share no section headings. A PRD has no ## Context; its motivation is ## Problem. Asking for a section the concept's type doesn't have is an error, not a near-miss. If you don't know which type a concept is, run arkouda list -l and read the type column, or just omit the section name and let arkouda pick the primary one.
The project may define more
A project can declare its own types with [[types]] in .arkoudarc.toml, and it can replace the built-in ADR or PRD contract with its own. So the two tables above describe arkouda's defaults, not necessarily this repo. Before you scaffold anything in an unfamiliar repo:
arkouda --help # nothing about types here — the type set is per project
arkouda list -l # the type column shows which types are actually in use
# `.arkoudarc.toml` is discovered by walking *up* from the working directory,
# so reading only `./.arkoudarc.toml` misses the config from a nested dir.
d=$PWD; until [ -f "$d/.arkoudarc.toml" ] || [ "$d" = / ]; do d=$(dirname "$d"); done
cat "$d/.arkoudarc.toml" 2>/dev/null # the authoritative list of this repo's types
A [[types]] table gives its type a slug (what --type takes), an okf_type (what its documents declare), a status lifecycle, optionally required_sections and a primary_section, a default_dir, and optionally a template. Read the table and follow it exactly as you would the built-in contracts — arkouda new --type <slug> scaffolds from it, and arkouda check enforces it.
arkouda new --type <slug> with an unknown slug lists the slugs that do exist, so that error is the fastest way to see this repo's types if there is no config file to read.
When to use
Reach for this skill any time you're about to make a non-trivial decision or start non-trivial work. Concretely:
- Before writing code that picks a library, framework, datastore, encoding, transport, or other "we now depend on X" commitment. (ADR)
- Before changing a public interface, file layout, schema, naming convention, or directory structure. (ADR)
- Before refactoring away from a pattern you didn't introduce — you may be about to undo a deliberate decision. (ADR)
- Before building a feature of any size: check whether a PRD already defines its scope, its non-goals, and what success means. (PRD)
- When the user asks "did we ever decide on X?", "why is it done this way?", "what are we building?", or "is X in scope?".
- When the user asks for a new ADR or PRD, or to mark one superseded.
- Whenever you land in an unfamiliar repo with an arkouda collection.
A 5-second arkouda list | xargs rg -i <topic> is cheaper than redoing a debate that's already in the file, or building something a PRD explicitly listed as a non-goal.
Philosophy
Three principles shape arkouda's behaviour, and explain why some defaults look minimal:
- Defer to Unix tools. Arkouda earns subcommands only where standard shell tools (
rg,grep,cat,awk,xargs) cannot. Content search, full-file printing, counting, and slicing are left to the shell — the CLI emits structured output you compose with the rest of your toolbox. Hence: nosearch, no full-fileshow. - Primary-section defaults.
arkouda listprints one path per line (no header, no padding) so it pipes cleanly. Each type has one section carrying its substance, soarkouda section <id>with no section name prints that one —## Decisionfor an ADR,## Requirementsfor a PRD. Supporting sections are named explicitly. - Standard format over bespoke. Documents are stored as OKF concepts, and a concept's
typeis a frontmatter field, so any OKF-aware consumer can read them and one bundle can hold both types.
The source rationale lives in arkouda's own repo, in the ADRs defer-to-unix-tools, ls-style-list-and-decision, adopt-okf, and support-product-requirements-documents.
Where documents live
The location varies between repos. Don't hardcode docs/adr/ in pipelines — ask arkouda. Run arkouda list to get the actual paths for the repo you're in.
Arkouda finds bundles; nothing configures them. A bundle is the topmost
directory that directly contains an OKF concept — a non-reserved .md whose
frontmatter declares a type — and everything beneath it belongs to that
bundle, so a nested concept keeps its path in its id. list, check,
section, and index read every bundle found; type comes from frontmatter,
never from the directory.
The walk skips .git, node_modules, target, vendor, dist, build,
out, .next, .venv, venv, __pycache__, coverage, and hidden
directories.
To scope one invocation to part of the tree, use --dir <path> or
ADR_DIR=<path>. It narrows the walk rather than declaring a root.
arkouda new writes into the first bundle that already holds a concept of the
type it is creating, then the --dir you named, then that type's default_dir.
.arkoudarc.toml no longer says where bundles are. A dirs key is an error
naming its replacement; delete it. The file's remaining jobs are declaring
[[types]] and the telemetry toggle.
A concept id is the document's path within its bundle, minus the .md suffix — not just the filename. A top-level use-postgres.md has the id use-postgres; a nested security/mtls.md has the id security/mtls. arkouda section accepts the full concept id (security/mtls), the bare stem (mtls), or the filename.
Commands
Five subcommands, each doing something the shell can't:
arkouda list [--sort id|timestamp|status] [--type <slug>] [-l]— one path per line. Pipe straight intoxargs/rg/cat/wc. With-l, a headerlessID TYPE STATUS TIMESTAMP PATH TITLE — DESCRIPTIONtable for human skimming and forawk.--typefilters by frontmatter type; valid slugs are this project's, not a fixedadr|prd.arkouda section <id> [<name>]— body of that concept's primary section (Decisionfor an ADR,Requirementsfor a PRD). Give a<name>for any other heading (context,consequences,problem,non-goals,success metrics,status, or custom). Errors if the section is missing. For the full file, resolve the path througharkouda listandcatit.arkouda check— validates in three tiers: OKF conformance for every concept, arkouda's frontmatter profile for concepts whosetypethe project configures, and that type's status vocabulary and required sections. Exit 0 clean, 1 on any error. Each diagnostic carries a code (E000–E015) and a fix hint. Warnings never fail the run. A concept whosetypeno[[types]]table configures is checked for OKF conformance only and warned about (E005) — it is neither skipped nor a failure.arkouda new "<title>" [--type <slug>] [--id <slug>] [--status <value>] [--description "<one-line summary>"]— scaffold a new concept with today's date, from that type's template. Defaults to--type adr. That default keeps working when a project redefines theadrslug — you get its contract instead of the built-in one — and fails only when the project has noadrslug at all, in which case the error names the slugs it does have.--statusmust come from the chosen type's own vocabulary and defaults to the first of its lifecycle (proposedfor an ADR,draftfor a PRD); it is written tolifecycle, and the OKFstatusit projects onto is written alongside it. Default id is a slug from the title. The description should summarize what was decided or what is being built, not just the topic. Refreshesindex.mdif the bundle has one.arkouda index— regenerate each bundle'sindex.md, an OKF §6 listing of every concept under# <Type>then## <Status>. Read it to see the whole collection at a glance without opening any file.
Global flags: --dir <path> (also ADR_DIR), -q/--quiet. Run arkouda --help or arkouda <subcommand> --help for the authoritative surface.
There is intentionally no search subcommand and no full-file show — rg/grep and cat already do those.
One-liners
arkouda list is the path source — it's where the documents actually are in this repo.
# Orient in an unfamiliar repo — the type column tells you what's here
arkouda list -l && arkouda check
# Paths of everything (for piping)
arkouda list
# Search for a topic — let list provide the search roots
arkouda list | xargs rg -i <topic>
# Read the substance of a specific concept (Decision, or Requirements)
arkouda section use-postgres
# Read another section instead
arkouda section use-postgres consequences
arkouda section bulk-import non-goals
# Read the whole document — resolve the path through list
cat "$(arkouda list | grep -F /use-postgres.md)"
# Paths of accepted ADRs only (note: status is $3, path is $5)
arkouda list -l --type adr | awk '$3=="accepted" {print $5}'
# Count concepts by status
arkouda list -l | awk '{print $3}' | sort | uniq -c
# Count concepts by type
arkouda list -l | awk '{print $2}' | sort | uniq -c
# Most recent N concepts
arkouda list -l --sort timestamp | tail -10
# Stream every primary section in the collection
arkouda list | while read f; do
id=$(basename "$f" .md)
printf '## %s\n\n' "$id"
arkouda section "$id"
printf '\n'
done
# Which ADRs shaped a PRD
arkouda list | grep -F /bulk-import.md | xargs sed -n '/^decisions:/,/^[a-z_]*:/p'
# Scaffold and validate
arkouda new "Adopt Tracing" --description "Use OpenTelemetry across services."
arkouda new "Bulk Import" --type prd --description "Import loose Markdown files as ADRs."
arkouda check
Workflows
Before deciding — search what's already there:
arkouda list | xargs rg -i <topic> # content search across everything
arkouda list -l --type adr | awk '$3=="accepted"' # accepted decisions only
arkouda section <id> # read the meat of a hit
Before building — check the requirements, especially the non-goals:
arkouda list --type prd | xargs rg -i <feature>
arkouda section <prd-id> # the Requirements section
arkouda section <prd-id> non-goals # what is explicitly out of scope
After deciding — capture it:
arkouda new "<Title>" --description "<one-line summary of what was decided>"
# arkouda new prints the path it created — open that file and fill in
# Context, Decision, Consequences
arkouda check
Writing a PRD
arkouda new "<Title>" --type prd --description "<what is being built, for whom>"
# Fill in Problem, Requirements, Non-Goals, Success Metrics (required), and
# Approach / Open Questions (scaffolded, optional). Then link the decisions
# that shaped it by adding their concept ids to frontmatter:
# decisions:
# - adopt-okf
# - defer-to-unix-tools
arkouda check
Supersede an existing document
- Resolve the path:
path=$(arkouda list | grep -F /<old-id>.md). cat "$path"to see the current frontmatter, then edit: changestatus: superseded(valid for both types) and addsuperseded_by: <new-concept-id>(the full bundle-relative id, e.g.security/mtls, not just the stem).arkouda new "<New Title>" [--type prd]for the replacement.arkouda checkto confirm both files still validate, and that thesuperseded_byreference resolves (a dangling one isE015).
Shape (what check enforces)
The frontmatter is OKF v0.1. There is no id key — the concept id is the bundle-relative path without .md. Required keys are the same for both types: type, title, description, status, timestamp. Required sections differ, and type is what selects them.
An ADR
---
type: Architecture Decision Record # required by OKF; selects this contract
title: Use Postgres
description: One-line summary of the decision (what was decided).
tags: [] # optional
timestamp: 2026-05-06 # ISO 8601 date or datetime
status: proposed # proposed | accepted | superseded | deprecated | rejected
deciders: [] # optional
---
# Use Postgres # H1 must equal title
## Status
Proposed
## Context
Why we are deciding this.
## Decision
What we decided.
## Consequences
What follows from the decision.
Sections from Michael Nygard's ADR template.
A PRD
---
type: Product Requirements Document
title: Bulk ADR Import
description: One-line summary of what is being built and for whom.
tags: []
timestamp: 2026-08-07
status: draft # draft | in-review | approved | shipped | abandoned | superseded
resource: https://github.com/org/repo/issues/42 # optional: the tracker item
owner: [] # optional; mirrors an ADR's deciders
target_release: 2026-09-01 # optional
decisions: # optional; concept ids of the ADRs that shaped this
- adopt-okf
---
# Bulk ADR Import # H1 must equal title
## Status
Draft
## Problem
Who has it, why it matters, and why now.
## Approach # scaffolded, not required
The shape of the solution, in a paragraph.
## Requirements
What the software must do. The primary section.
## Non-Goals
What this explicitly does not cover.
## Success Metrics
How we will know it worked.
## Open Questions # scaffolded, not required
status, deciders, superseded_by, owner, target_release, and decisions are OKF producer extensions; type, title, description, resource, tags, and timestamp are OKF's own fields.
Arkouda validates document structure, not requirement content. It does not enforce FR-### numbering, P1/P2 priorities, or EARS acceptance-criteria syntax. Those are good conventions to use inside a ## Requirements section; the validator's contract is headings and frontmatter.
When check reports errors
Each diagnostic has a code; the hint usually tells you the exact fix.
- E000 unparseable file → the file must start with YAML frontmatter delimited by
---. - E001/E002 missing or empty required field → add the field with a real value. For a timestamp, that means
generated.at(or a legacytimestamp). - E003 invalid
lifecycle→ use a value from this type's vocabulary; the hint lists them. - E018
statusis not an OKF status →statusholds OKF'sdraft | stable | deprecated. A per-type value likeacceptedbelongs inlifecycle. - E019
statuscontradictslifecycle→statusis the projection oflifecycle, not a second opinion. The hint names the value to write. Absentstatuscounts asstable, so it disagrees too when the lifecycle projects elsewhere. - E020 (warning) the per-type value is in
status→ arkouda's pre-0.7 spelling. Writelifecycle: <value>andstatus: <projection>. Reading the old spelling still works. - E004 concept id is not a lowercase slug → rename the file (and any parent dirs) to letters, digits, single hyphens.
- E005 (warning) no configured type declares this
type→ either the value is a typo (fix it to a configuredokf_type; the hint lists them), or the project has not declared this type yet. Until it does, the concept is checked for OKF conformance only — its status and sections are not validated. A warning rather than an error because a conformant OKF bundle may legitimately hold types this project has not described. - E006 invalid instant → ISO 8601.
generated.at,verified[].at, andstale_afterneed an explicit offset (2026-05-06T14:30:00Z); a legacytimestampmay be a plain date. - E007/E008 missing or wrong H1 → first heading must be
# <title>. These are arkouda's contract, not OKF's — OKF §4.2 requires no body sections at all — so they fire only for a concept whosetypethe project configures. Don't add an H1 to an unconfigured concept to silence a diagnosticarkouda checknever emitted for it. - E009 missing required section → add the named
## Section. Which ones are required depends ontype, and a project's own type may require none at all. - E010 duplicate concept id across files → make ids unique.
- E011
index.mdfrontmatter → only a bundle-root index may have it, and onlyokf_version. - E012
log.mdheading is not## YYYY-MM-DD. - E013 (warning) bundle declares an OKF version arkouda doesn't implement.
- E014 (warning)
index.mdis stale → runarkouda index. - E015 (warning) a
decisionsorsuperseded_byentry doesn't resolve to a loaded concept → fix the id, or widen--dirif it lives in a bundle this run didn't load. It's a warning precisely because arkouda can't tell a broken reference from an out-of-scope one. - E016 (warning) the concept is past its
stale_afterinstant → re-check the content and movestale_afterforward, or drop the key. Worth heeding before you rely on the concept: it says the author expected it to need review by now. - E017 (warning) the concept uses v0.1's
timestamp→ replace it withgenerated: { by: human:<id>, at: <date>T00:00:00Z }. Reading the old key still works, so this never fails anything.
What not to do
- Don't make a non-trivial decision, or start non-trivial work, without first checking what's already recorded.
- Don't hardcode
docs/adr/in pipelines — different repos put these documents elsewhere via.arkoudarc.toml. Usearkouda listto discover the actual paths. - Don't reach for the default
--type adrwhen you're describing what to build; that's a PRD. And don't file a technical trade-off as a PRD. - Don't ask a PRD for its
## Contextor an ADR for its## Requirements— apart fromStatus, the section vocabularies don't overlap. Omit the section name to get the right one for the type. - Don't restate a decision inside a PRD. Link it with the
decisionsfrontmatter key and let the ADR carry the reasoning. - Don't write or edit these files freehand without running
arkouda checkafterwards — the schema is strict for every type the project configures. - Don't invent statuses. Each type has its own closed list, and
shippedon an ADR (oracceptedon a PRD) is anE003. - Don't move or rename a published document after creation — its path within the bundle is its concept id, so links,
superseded_by, anddecisionsvalues pointing at it will break. Create a new one and mark the old onesupersededinstead. - Don't add an
id:key to frontmatter; it was removed when arkouda moved to OKF. The concept id comes from the path within the bundle. - Don't hand-edit
index.md— it is generated byarkouda index, and edits are overwritten.log.mdis yours to maintain: arkouda never writes it, only validates that its headings are## YYYY-MM-DD. Neither file is ever a concept; both are reserved by OKF. - Don't commit documents whose
arkouda checkfails — CI is likely to enforce it. Do read the warnings too: anE005on a document you just wrote means you gave it atypethis project doesn't configure, and nothing checked its shape. - Don't assume
--type adrmeans the built-in ADR, or that it exists at all. A project can redefine theadrslug with its own sections and statuses, or drop it by giving decisions a different slug. Read the discovered.arkoudarc.toml, or letarkouda new --type <anything>list the slugs in its error.