okf-cli 0.1.2__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. okf_cli-0.3.1/.github/workflows/test.yml +17 -0
  2. okf_cli-0.3.1/AGENTS.md +163 -0
  3. {okf_cli-0.1.2 → okf_cli-0.3.1}/PKG-INFO +111 -35
  4. okf_cli-0.3.1/README.md +201 -0
  5. okf_cli-0.3.1/example/loose/deep/no-heading.md +4 -0
  6. okf_cli-0.3.1/example/loose/no-desc.md +6 -0
  7. okf_cli-0.3.1/example/loose/no-title.md +5 -0
  8. {okf_cli-0.1.2 → okf_cli-0.3.1}/pyproject.toml +10 -4
  9. okf_cli-0.3.1/src/okf/cli.py +45 -0
  10. okf_cli-0.3.1/src/okf/commands/__init__.py +0 -0
  11. okf_cli-0.1.2/src/okf/cli.py → okf_cli-0.3.1/src/okf/commands/bundle.py +24 -98
  12. okf_cli-0.3.1/src/okf/commands/list.py +41 -0
  13. okf_cli-0.3.1/src/okf/commands/show.py +53 -0
  14. okf_cli-0.3.1/src/okf/commands/validate.py +47 -0
  15. okf_cli-0.3.1/src/okf/core.py +185 -0
  16. okf_cli-0.3.1/tests/test_cli.py +825 -0
  17. okf_cli-0.3.1/uv.lock +238 -0
  18. okf_cli-0.1.2/README.md +0 -126
  19. okf_cli-0.1.2/tests/test_cli.py +0 -304
  20. okf_cli-0.1.2/uv.lock +0 -150
  21. {okf_cli-0.1.2 → okf_cli-0.3.1}/.gitignore +0 -0
  22. {okf_cli-0.1.2 → okf_cli-0.3.1}/LICENSE +0 -0
  23. {okf_cli-0.1.2 → okf_cli-0.3.1}/OKF_SPEC.md +0 -0
  24. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/README.md +0 -0
  25. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/datasets/sales.md +0 -0
  26. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/domain.md +0 -0
  27. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/playbooks/incident-response.md +0 -0
  28. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/playbooks/oncall-guide.md +0 -0
  29. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/tables/customers.md +0 -0
  30. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/tables/orders.md +0 -0
  31. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/tables/partitions/2026/q1/jan/january.md +0 -0
  32. {okf_cli-0.1.2 → okf_cli-0.3.1}/example/tables/partitions/daily.md +0 -0
  33. {okf_cli-0.1.2 → okf_cli-0.3.1}/src/okf/__init__.py +0 -0
@@ -0,0 +1,17 @@
1
+ name: Test
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.11"]
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v3
14
+ with:
15
+ python-version: ${{ matrix.python-version }}
16
+ - run: uv sync
17
+ - run: uv run pytest -q
@@ -0,0 +1,163 @@
1
+ # AGENTS.md — okf-cli contributor context
2
+
3
+ This file is for AI agents (and humans) picking up work on `okf-cli`.
4
+
5
+ ## Project overview
6
+
7
+ `okf-cli` is a Python CLI that converts plain markdown directories into
8
+ [Open Knowledge Format (OKF)](OKF_SPEC.md) bundles and provides tools to
9
+ inspect/validate them.
10
+
11
+ - **Input:** plain `.md` files starting with `# Title` and a `>` description block.
12
+ - **Output:** OKF-conformant markdown with YAML frontmatter, per-directory `index.md` files.
13
+ - **Operations:** `bundle`, `list`, `show`, `validate`.
14
+
15
+ ## Tech stack
16
+
17
+ - Python 3.11+
18
+ - Package/venv manager: **uv**
19
+ - CLI framework: **typer**
20
+ - YAML parsing: **pyyaml**
21
+ - Tests: **pytest**
22
+ - Lint/format: **ruff** (via `uvx ruff check .` / `uvx ruff format .`)
23
+ - Build: hatchling (configured in `pyproject.toml`)
24
+
25
+ Always use `uv` for dependency sync and running commands:
26
+
27
+ ```bash
28
+ uv sync # install/sync deps
29
+ uv run pytest -q # run tests
30
+ uv run okf --help # run CLI
31
+ uv run okf bundle example bundled --default-type reference --force
32
+ ```
33
+
34
+ ## Project layout
35
+
36
+ ```
37
+ okf-cli
38
+ ├── .github/workflows/test.yml # CI (pytest on 3.11)
39
+ ├── OKF_SPEC.md # OKF v0.1 specification
40
+ ├── README.md # user-facing docs
41
+ ├── AGENTS.md # this file
42
+ ├── pyproject.toml # project metadata + deps
43
+ ├── uv.lock # uv lockfile
44
+ ├── src/okf/
45
+ │ ├── cli.py # Typer entrypoint, registers commands
46
+ │ ├── core.py # shared parsing/formatting/conformance
47
+ │ └── commands/
48
+ │ ├── bundle.py # plain markdown → OKF bundle
49
+ │ ├── list.py # list concept IDs
50
+ │ ├── show.py # print concept by ID
51
+ │ └── validate.py # conformance check
52
+ └── tests/test_cli.py # pytest suite
53
+ ```
54
+
55
+ ## Architecture
56
+
57
+ ### `src/okf/cli.py`
58
+
59
+ - Creates the `typer.Typer` app named `okf`.
60
+ - Registers `bundle`, `list`, `show`, `validate`.
61
+ - No business logic here.
62
+
63
+ ### `src/okf/core.py`
64
+
65
+ - `RESERVED` — filenames `bundle` skips: `index.md`, `log.md`, `README.md`.
66
+ - `SPEC_RESERVED` — filenames reserved by the OKF spec: `index.md`, `log.md`.
67
+ - `build_frontmatter(...)` — generates YAML frontmatter using JSON-escaped strings.
68
+ - `parse_md(...)` — extracts title/description/body from plain markdown. Tries strict format first (line 1 `# Title`, `>` block); falls back leniently (title from line 0 only, description from first 80 chars of body). Never raises.
69
+ - `parse_frontmatter(...)` — parses YAML frontmatter with `yaml.safe_load`.
70
+ - `check_conformance(...)` — validates a directory against OKF §9, returns `(errors, warnings)`. Now enforces:
71
+ - §9.1: Non-reserved `.md` must have parseable frontmatter.
72
+ - §9.2: Frontmatter must have non-empty `type`.
73
+ - §9.3: `index.md` must not contain frontmatter (§6), except root `index.md` may have only `okf_version` (§11). `log.md` must not contain frontmatter (§7).
74
+ - Non-UTF-8 files are flagged as errors.
75
+
76
+ ### Commands
77
+
78
+ | Command | Input expected | Behavior |
79
+ |---|---|---|
80
+ | `bundle` | non-conformant plain markdown dir | generates OKF bundle; skips `index.md`, `log.md`, `README.md` with warnings; root files need `--default-type`; requires `--force` to overwrite existing output |
81
+ | `list` | OKF-conformant bundle | prints concept IDs; exits 1 if dir is not conformant |
82
+ | `show` | OKF-conformant bundle | prints concept file by ID; exits 1 if dir is not conformant |
83
+ | `validate` | any directory | prints conformance errors and summary per §9 |
84
+
85
+ ## Key conventions
86
+
87
+ ### Reserved filenames
88
+
89
+ - `bundle` treats `index.md`, `log.md`, `README.md` as reserved because it
90
+ often runs against raw repo directories that may contain these files.
91
+ - `list`, `show`, `validate` operate on already-conformant OKF bundles, so
92
+ they only treat `index.md` and `log.md` as reserved per the spec.
93
+ - `README.md` can be a valid concept in an OKF bundle.
94
+
95
+ ### OKF conformance (§9)
96
+
97
+ 1. Every non-reserved `.md` must have parseable YAML frontmatter.
98
+ 2. Every frontmatter must contain a non-empty `type` string.
99
+ 3. Reserved filenames (`index.md`, `log.md`) follow structure per §6/§7 when present.
100
+ - `index.md` must not contain frontmatter (§6), except root `index.md` may contain only `okf_version` (§11).
101
+ - `log.md` must not contain frontmatter (§7).
102
+ 4. All files must be valid UTF-8.
103
+
104
+ `check_conformance()` is the source of truth for this logic.
105
+
106
+ ### Frontmatter
107
+
108
+ `build_frontmatter()` outputs JSON-escaped YAML values, e.g.:
109
+
110
+ ```yaml
111
+ ---
112
+ type: "tables"
113
+ title: "Customer Orders"
114
+ ---
115
+ ```
116
+
117
+ `parse_frontmatter()` uses `yaml.safe_load()` and returns `None` for missing,
118
+ malformed, or non-dict frontmatter.
119
+
120
+ ## Testing
121
+
122
+ Run the full suite before finishing:
123
+
124
+ ```bash
125
+ uv run pytest -q
126
+ ```
127
+
128
+ Add tests for new behavior in `tests/test_cli.py`. Existing patterns:
129
+
130
+ - Unit tests for `core.py` helpers.
131
+ - CLI integration tests using `typer.testing.CliRunner` and `tmp_path`.
132
+ - Conformance tests for `validate`, `list`, `show`.
133
+
134
+ ## Common tasks
135
+
136
+ ### Regenerate the example bundle
137
+
138
+ ```bash
139
+ uv run okf bundle example bundled --default-type reference --force
140
+ uv run okf validate bundled
141
+ uv run okf list bundled
142
+ ```
143
+
144
+ `bundled/` is gitignored; it is only a local artifact. Use `--force` for re-runs.
145
+
146
+ ### Add a new CLI command
147
+
148
+ 1. Create `src/okf/commands/<name>.py` with a function taking `typer` arguments.
149
+ 2. Import and register it in `src/okf/cli.py` with `app.command()(fn)` or
150
+ `app.command("alias")(fn)`.
151
+ 3. Add tests in `tests/test_cli.py`.
152
+
153
+ ### Modify conformance behavior
154
+
155
+ Update `check_conformance()` in `src/okf/core.py`, then ensure
156
+ `validate`, `list`, and `show` all behave consistently.
157
+
158
+ ## Style guidelines
159
+
160
+ - Keep changes minimal. Prefer stdlib/installed dependencies over new ones.
161
+ - Do not add abstractions "for later".
162
+ - Use `Path` from `pathlib`, not string path manipulation.
163
+ - Use `uv` for all package/runtime commands.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: okf-cli
3
- Version: 0.1.2
3
+ Version: 0.3.1
4
4
  Summary: Open Knowledge Format tooling
5
5
  License: MIT License
6
6
 
@@ -24,13 +24,16 @@ License: MIT License
24
24
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
25
  SOFTWARE.
26
26
  License-File: LICENSE
27
- Requires-Python: >=3.13
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: pyyaml>=6
28
29
  Requires-Dist: typer>=0.15
29
30
  Description-Content-Type: text/markdown
30
31
 
31
32
  # okf-cli — Open Knowledge Format tooling
32
33
 
33
- Converts plain markdown into [OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)-conformant knowledge bundles. Domain experts write `# Title` then `> description` — `okf bundle` generates frontmatter, type, timestamps, and index files.
34
+ Converts plain markdown into [OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)-conformant knowledge bundles. Domain experts write `# Title` then `> description` — `okf bundle` generates frontmatter, type, timestamps, and index files. Files without strict formatting are parsed leniently: title from line 0 if present, description from body text.
35
+
36
+ Also validates bundles and lists concept IDs for consumption by other tools.
34
37
 
35
38
  ## Install
36
39
 
@@ -38,25 +41,19 @@ Converts plain markdown into [OKF](https://github.com/GoogleCloudPlatform/knowle
38
41
  uv tool install okf-cli
39
42
  ```
40
43
 
41
- Then run:
42
-
43
- ```bash
44
- okf bundle example # output → bundled/
45
- cat bundled/index.md
46
- cat bundled/tables/orders.md
47
- ```
48
-
49
44
  ### Dev quickstart
50
45
 
51
46
  ```bash
52
47
  uv sync
53
- uv run okf example
48
+ uv run okf --help
54
49
  ```
55
50
 
56
- ## Usage
51
+ ## Commands
52
+
53
+ ### `okf bundle` — convert plain markdown to OKF bundle
57
54
 
58
55
  ```
59
- okf <input-dir> [output-dir] [--default-type <name>]
56
+ okf bundle <input-dir> [output-dir] [--default-type <name>] [--force]
60
57
  ```
61
58
 
62
59
  | Argument | Description |
@@ -64,10 +61,86 @@ okf <input-dir> [output-dir] [--default-type <name>]
64
61
  | `input-dir` | Directory of plain `.md` files |
65
62
  | `output-dir` | Target directory (default: `bundled`) |
66
63
  | `--default-type` | Type for root-level files (skip root files if omitted) |
64
+ | `--force`, `-f` | Overwrite output directory if it exists |
65
+
66
+ ```bash
67
+ okf bundle example --default-type reference # first run → bundled/
68
+ okf bundle example --default-type reference --force # re-run
69
+ cat bundled/tables/orders.md
70
+ ```
71
+
72
+ ### `okf list` — list concept IDs in a bundle
73
+
74
+ ```
75
+ okf list <directory>
76
+ ```
77
+
78
+ Prints concept IDs (bundle-relative path with `.md` stripped) for every
79
+ concept file in the bundle. Reserved filenames (`index.md`, `log.md`)
80
+ are excluded. `README.md` is not reserved by the OKF spec and is listed
81
+ as a concept if present.
82
+
83
+ Requires the directory to be a valid OKF bundle; fails if any
84
+ non-reserved `.md` is missing frontmatter or a non-empty `type`.
85
+
86
+ ```bash
87
+ okf list bundled/
88
+ # datasets/sales
89
+ # playbooks/incident-response
90
+ # tables/orders
91
+ # …
92
+ ```
93
+
94
+ Useful for piping OKF bundles into other tooling:
95
+
96
+ ```bash
97
+ okf list bundled/ | xargs -I{} okf show bundled/ {}
98
+ ```
99
+
100
+ ### `okf show` — read a concept by ID
101
+
102
+ ```
103
+ okf show <directory> <concept-id>
104
+ ```
67
105
 
68
- ## Writing input files
106
+ Prints the full contents (frontmatter + body) of a concept file.
107
+ Concept IDs are the bundle-relative path with `.md` stripped — exactly
108
+ as printed by `okf list`.
69
109
 
70
- Every `.md` file must start with:
110
+ Requires the directory to be a valid OKF bundle.
111
+
112
+ ```bash
113
+ okf show bundled/ tables/orders
114
+ # ---
115
+ # type: "tables"
116
+ # title: "Customer Orders"
117
+ # ...
118
+ ```
119
+
120
+ Guards against path traversal and rejects reserved filenames.
121
+
122
+ ### `okf validate` — check OKF conformance
123
+
124
+ ```
125
+ okf validate <directory>
126
+ ```
127
+
128
+ Checks OKF v0.1 §9 conformance:
129
+
130
+ - Every non-reserved `.md` has parseable YAML frontmatter with non-empty `type`.
131
+ - Reserved filenames (`index.md`, `log.md`) follow spec structure (§6/§7).
132
+ - Non-UTF-8 files are flagged.
133
+
134
+ ```bash
135
+ okf validate bundled/
136
+ # 16 files: 16 ok
137
+ ```
138
+
139
+ ## Writing input files (for `bundle`)
140
+
141
+ ### Strict format (recommended)
142
+
143
+ Every `.md` file starts with:
71
144
 
72
145
  ```markdown
73
146
  # Title
@@ -76,37 +149,36 @@ Every `.md` file must start with:
76
149
  > Second optional description line.
77
150
  ```
78
151
 
79
- Everything after the description block is free-form — preserved unchanged.
152
+ Everything after the description block is preserved unchanged.
153
+
154
+ ### Lenient fallback
155
+
156
+ Files that don't follow the strict format are still bundled best-effort:
157
+
158
+ | Condition | Behavior |
159
+ |---|---|
160
+ | No `# Title` on line 1 | Title omitted from frontmatter |
161
+ | No `>` description block | Description derived from first 80 chars of body ("…" if truncated) |
162
+ | Root-level file without `--default-type` | Skip file, warn, continue |
80
163
 
81
164
  **Rules:**
82
165
 
83
166
  | Rule | Why |
84
167
  |---|---|
85
- | First line must be `# Title` | Tool reads title here |
86
- | Followed by `> Description` | Tool reads description here |
87
168
  | Folder name = concept type | `tables/orders.md` → `type: "tables"` |
88
169
  | Only `.md` files processed | Non-`.md` files ignored |
89
- | Reserved names skipped: `index.md`, `log.md`, `README.md` | OKF spec reserves these |
90
-
91
- **Violations:**
92
-
93
- | Condition | Behavior |
94
- |---|---|
95
- | Line 1 not `# Title` | Error, stop |
96
- | Empty title | Error, stop |
97
- | No `> description` block | Error, stop |
98
- | Root-level file without `--default-type` | Skip file, warn, continue |
170
+ | `index.md`, `log.md`, `README.md` skipped in `bundle` | Input may contain repo artifacts; these are not concepts. `bundle` warns when it skips them. Other commands (`list`, `show`, `validate`) operate on conformant OKF bundles where only `index.md` and `log.md` are reserved. |
99
171
 
100
172
  Root files need `--default-type`. Otherwise put files in named folders.
101
173
 
102
174
  See the [`example/`](example/) directory for a sample of how to structure files.
103
175
 
104
- ## How it works
176
+ ## How `bundle` works
105
177
 
106
178
  1. Walk `input-dir` for `.md` files (skip reserved names)
107
- 2. Extract `title` from `#`, `description` from `>`
179
+ 2. Extract `title` from `#` on line 1, `description` from `>` block. If strict format not met, falls back: title omitted if absent, description from first 80 chars of body
108
180
  3. Set `type` from parent dir name, `timestamp` from file mtime
109
- 4. Write concept files with YAML frontmatter
181
+ 4. Write concept files with YAML frontmatter (title field omitted if empty)
110
182
  5. Generate `index.md` per directory — `# Contents` for files, `# Directories` for subdirs (recursive)
111
183
 
112
184
  ## Output
@@ -148,9 +220,13 @@ Generated bundles conform to [OKF v0.1](OKF_SPEC.md) (§9):
148
220
 
149
221
  ```
150
222
  okf-cli
151
- ├── OKF_SPEC.md # OKF specification
152
- ├── pyproject.toml # uv-managed Python project
153
- ├── src/okf/cli.py # Single-file CLI
223
+ ├── .github/workflows/test.yml # CI
224
+ ├── OKF_SPEC.md # OKF specification
225
+ ├── pyproject.toml # uv-managed Python project
226
+ ├── src/okf/
227
+ │ ├── cli.py # Typer entrypoint
228
+ │ ├── core.py # Shared parsing/formatting
229
+ │ └── commands/ # bundle, list, show, validate
154
230
  ├── tests/test_cli.py # Tests
155
231
  └── example/ # Sample input markdown
156
232
  ```
@@ -0,0 +1,201 @@
1
+ # okf-cli — Open Knowledge Format tooling
2
+
3
+ Converts plain markdown into [OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)-conformant knowledge bundles. Domain experts write `# Title` then `> description` — `okf bundle` generates frontmatter, type, timestamps, and index files. Files without strict formatting are parsed leniently: title from line 0 if present, description from body text.
4
+
5
+ Also validates bundles and lists concept IDs for consumption by other tools.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ uv tool install okf-cli
11
+ ```
12
+
13
+ ### Dev quickstart
14
+
15
+ ```bash
16
+ uv sync
17
+ uv run okf --help
18
+ ```
19
+
20
+ ## Commands
21
+
22
+ ### `okf bundle` — convert plain markdown to OKF bundle
23
+
24
+ ```
25
+ okf bundle <input-dir> [output-dir] [--default-type <name>] [--force]
26
+ ```
27
+
28
+ | Argument | Description |
29
+ |---|---|
30
+ | `input-dir` | Directory of plain `.md` files |
31
+ | `output-dir` | Target directory (default: `bundled`) |
32
+ | `--default-type` | Type for root-level files (skip root files if omitted) |
33
+ | `--force`, `-f` | Overwrite output directory if it exists |
34
+
35
+ ```bash
36
+ okf bundle example --default-type reference # first run → bundled/
37
+ okf bundle example --default-type reference --force # re-run
38
+ cat bundled/tables/orders.md
39
+ ```
40
+
41
+ ### `okf list` — list concept IDs in a bundle
42
+
43
+ ```
44
+ okf list <directory>
45
+ ```
46
+
47
+ Prints concept IDs (bundle-relative path with `.md` stripped) for every
48
+ concept file in the bundle. Reserved filenames (`index.md`, `log.md`)
49
+ are excluded. `README.md` is not reserved by the OKF spec and is listed
50
+ as a concept if present.
51
+
52
+ Requires the directory to be a valid OKF bundle; fails if any
53
+ non-reserved `.md` is missing frontmatter or a non-empty `type`.
54
+
55
+ ```bash
56
+ okf list bundled/
57
+ # datasets/sales
58
+ # playbooks/incident-response
59
+ # tables/orders
60
+ # …
61
+ ```
62
+
63
+ Useful for piping OKF bundles into other tooling:
64
+
65
+ ```bash
66
+ okf list bundled/ | xargs -I{} okf show bundled/ {}
67
+ ```
68
+
69
+ ### `okf show` — read a concept by ID
70
+
71
+ ```
72
+ okf show <directory> <concept-id>
73
+ ```
74
+
75
+ Prints the full contents (frontmatter + body) of a concept file.
76
+ Concept IDs are the bundle-relative path with `.md` stripped — exactly
77
+ as printed by `okf list`.
78
+
79
+ Requires the directory to be a valid OKF bundle.
80
+
81
+ ```bash
82
+ okf show bundled/ tables/orders
83
+ # ---
84
+ # type: "tables"
85
+ # title: "Customer Orders"
86
+ # ...
87
+ ```
88
+
89
+ Guards against path traversal and rejects reserved filenames.
90
+
91
+ ### `okf validate` — check OKF conformance
92
+
93
+ ```
94
+ okf validate <directory>
95
+ ```
96
+
97
+ Checks OKF v0.1 §9 conformance:
98
+
99
+ - Every non-reserved `.md` has parseable YAML frontmatter with non-empty `type`.
100
+ - Reserved filenames (`index.md`, `log.md`) follow spec structure (§6/§7).
101
+ - Non-UTF-8 files are flagged.
102
+
103
+ ```bash
104
+ okf validate bundled/
105
+ # 16 files: 16 ok
106
+ ```
107
+
108
+ ## Writing input files (for `bundle`)
109
+
110
+ ### Strict format (recommended)
111
+
112
+ Every `.md` file starts with:
113
+
114
+ ```markdown
115
+ # Title
116
+
117
+ > Description
118
+ > Second optional description line.
119
+ ```
120
+
121
+ Everything after the description block is preserved unchanged.
122
+
123
+ ### Lenient fallback
124
+
125
+ Files that don't follow the strict format are still bundled best-effort:
126
+
127
+ | Condition | Behavior |
128
+ |---|---|
129
+ | No `# Title` on line 1 | Title omitted from frontmatter |
130
+ | No `>` description block | Description derived from first 80 chars of body ("…" if truncated) |
131
+ | Root-level file without `--default-type` | Skip file, warn, continue |
132
+
133
+ **Rules:**
134
+
135
+ | Rule | Why |
136
+ |---|---|
137
+ | Folder name = concept type | `tables/orders.md` → `type: "tables"` |
138
+ | Only `.md` files processed | Non-`.md` files ignored |
139
+ | `index.md`, `log.md`, `README.md` skipped in `bundle` | Input may contain repo artifacts; these are not concepts. `bundle` warns when it skips them. Other commands (`list`, `show`, `validate`) operate on conformant OKF bundles where only `index.md` and `log.md` are reserved. |
140
+
141
+ Root files need `--default-type`. Otherwise put files in named folders.
142
+
143
+ See the [`example/`](example/) directory for a sample of how to structure files.
144
+
145
+ ## How `bundle` works
146
+
147
+ 1. Walk `input-dir` for `.md` files (skip reserved names)
148
+ 2. Extract `title` from `#` on line 1, `description` from `>` block. If strict format not met, falls back: title omitted if absent, description from first 80 chars of body
149
+ 3. Set `type` from parent dir name, `timestamp` from file mtime
150
+ 4. Write concept files with YAML frontmatter (title field omitted if empty)
151
+ 5. Generate `index.md` per directory — `# Contents` for files, `# Directories` for subdirs (recursive)
152
+
153
+ ## Output
154
+
155
+ Each concept becomes a markdown file with YAML frontmatter:
156
+
157
+ ```yaml
158
+ ---
159
+ type: "tables"
160
+ title: "Customer Orders"
161
+ description: "One row per completed customer order across all channels."
162
+ timestamp: "2026-07-04T15:06:51+00:00"
163
+ ---
164
+
165
+ Original body preserved as-is.
166
+ ```
167
+
168
+ Every directory gets its own `index.md`:
169
+
170
+ ```markdown
171
+ # Contents
172
+
173
+ * [Customer Orders](orders.md) - One row per completed customer order across all channels.
174
+
175
+ # Directories
176
+
177
+ * [partitions](partitions/)
178
+ ```
179
+
180
+ ## OKF Conformance
181
+
182
+ Generated bundles conform to [OKF v0.1](OKF_SPEC.md) (§9):
183
+
184
+ - Every non-reserved `.md` has parseable YAML frontmatter with non-empty `type` ✓
185
+ - Reserved filenames follow spec structure ✓
186
+ - Consumers MUST tolerate missing optional fields, unknown types, broken links ✓
187
+
188
+ ## Project layout
189
+
190
+ ```
191
+ okf-cli
192
+ ├── .github/workflows/test.yml # CI
193
+ ├── OKF_SPEC.md # OKF specification
194
+ ├── pyproject.toml # uv-managed Python project
195
+ ├── src/okf/
196
+ │ ├── cli.py # Typer entrypoint
197
+ │ ├── core.py # Shared parsing/formatting
198
+ │ └── commands/ # bundle, list, show, validate
199
+ ├── tests/test_cli.py # Tests
200
+ └── example/ # Sample input markdown
201
+ ```
@@ -0,0 +1,4 @@
1
+ I am a file deep in a subdirectory with no heading at all.
2
+
3
+ Type comes from the directory name, title is omitted, and description is
4
+ derived from this body text.
@@ -0,0 +1,6 @@
1
+ # Has Title But No Desc
2
+
3
+ This file has a proper `# Title` but no `>` blockquote description.
4
+
5
+ It should still be bundled: title extracted normally, description taken
6
+ from the first 80 characters of the body as a fallback.
@@ -0,0 +1,5 @@
1
+ No heading at all — this file has no `# Title` line and no `>` description block.
2
+
3
+ It should still be bundled: title omitted from frontmatter, description
4
+ derived from the first 80 characters of this body text verbatim, with
5
+ newlines collapsed to spaces.
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "okf-cli"
3
- version = "0.1.2"
3
+ version = "0.3.1"
4
4
  description = "Open Knowledge Format tooling"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
- requires-python = ">=3.13"
8
- dependencies = ["typer>=0.15"]
7
+ requires-python = ">=3.11"
8
+ dependencies = ["typer>=0.15", "pyyaml>=6"]
9
9
 
10
10
  [project.scripts]
11
11
  okf = "okf.cli:app"
@@ -18,4 +18,10 @@ build-backend = "hatchling.build"
18
18
  packages = ["src/okf"]
19
19
 
20
20
  [dependency-groups]
21
- dev = ["pytest>=8"]
21
+ dev = ["pytest>=8", "ruff>=0.14"]
22
+
23
+ [tool.ruff]
24
+ line-length = 88
25
+
26
+ [tool.ruff.lint]
27
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,45 @@
1
+ """okf — Open Knowledge Format tooling."""
2
+
3
+ from importlib.metadata import version as _version
4
+
5
+ import typer
6
+
7
+ from okf.commands.bundle import bundle
8
+ from okf.commands.list import cmd_list
9
+ from okf.commands.show import cmd_show
10
+ from okf.commands.validate import validate
11
+
12
+ app = typer.Typer(
13
+ name="okf",
14
+ help="Open Knowledge Format tooling",
15
+ no_args_is_help=True,
16
+ )
17
+
18
+
19
+ def _version_callback(value: bool) -> None:
20
+ if value:
21
+ typer.echo(f"okf {_version('okf-cli')}")
22
+ raise typer.Exit()
23
+
24
+
25
+ @app.callback()
26
+ def main(
27
+ version: bool = typer.Option(
28
+ False,
29
+ "--version",
30
+ callback=_version_callback,
31
+ is_eager=True,
32
+ help="Show version and exit",
33
+ ),
34
+ ) -> None:
35
+ """Convert plain markdown into OKF-conformant knowledge bundles."""
36
+
37
+
38
+ app.command()(bundle)
39
+ app.command("list")(cmd_list)
40
+ app.command("show")(cmd_show)
41
+ app.command("validate")(validate)
42
+
43
+
44
+ if __name__ == "__main__":
45
+ app()
File without changes