feat(readme,skill,cli): the first screen an agent reads, three modes, and one flag that made two builds
`okf project` built a bundle two rules behind `okf build`. `cli.build`'s signature defaulted `keep_table_heading` and `sheet_section_rows` to `False` while argparse defaulted both to `True`, and `project.create` calls `build()` as a function. Measured on a five-document folder: 15 concepts / 30 files where `okf build` wrote 26 / 52, the whole difference in the priced sheet -- the document a question about price has to reach. The invariant test could not see it: it compared `project.create` against the same function, and its two fixture documents had neither a table nor a sheet. Both gaps are tests now, and the two paths are byte-equal on that folder (`diff -rq`, 0 differences). README opens with what / one install line / two commands / the three shapes of request; the phase-status paragraph moved down, nothing deleted. One tag is pinned everywhere: README pinned v0.4.0 on its install lines and v0.6.0 below, llms.txt pinned v0.4.0, so an agent reading from the top installed a tag without `okf project`. The skill states three modes -- question, hypothesis (per premise, `confirmed` / `refuted` / `undecidable-from-bundle`), and a task producing a document (source per claim in the artefact, an ungrounded paragraph written and marked rather than dropped, the cut declared inside the document). The five markings are untouched. Generated skills state relative paths in the project layout: `okf consume .okf/<id>` and `okf check --skill .claude/skills/<id>-consume/SKILL.md`, runnable from where `okf project` tells the reader to start `claude`. Two absolute paths to zero, measured with a query shown capable of finding first -- O5's published "4 -> 0" used `grep -c "^/"` against paths indented by two spaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
956714594d
commit
1260fac154
12 changed files with 532 additions and 43 deletions
|
|
@ -76,7 +76,7 @@ from .manifest import (
|
|||
)
|
||||
from .materialize import IngestResult, materialize_bundle
|
||||
|
||||
__version__ = "0.6.0"
|
||||
__version__ = "0.7.0"
|
||||
|
||||
__all__ = [
|
||||
"BlockedFile",
|
||||
|
|
|
|||
|
|
@ -230,8 +230,8 @@ def _propose_plans(
|
|||
outline_run: int = 0,
|
||||
table_grid: bool = False,
|
||||
unit_fold: bool = False,
|
||||
keep_table_heading: bool = False,
|
||||
sheet_section_rows: bool = False,
|
||||
keep_table_heading: bool = DEFAULT_KEEP_TABLE_HEADING,
|
||||
sheet_section_rows: bool = DEFAULT_SHEET_SECTION_ROWS,
|
||||
drop_wrapped_outline: bool = False,
|
||||
outline_gate: bool = False,
|
||||
first_span_from_zero: bool = False,
|
||||
|
|
@ -301,8 +301,8 @@ def build(
|
|||
outline_run: int = DEFAULT_OUTLINE_RUN,
|
||||
table_grid: bool = DEFAULT_TABLE_GRID,
|
||||
unit_fold: bool = DEFAULT_UNIT_FOLD,
|
||||
keep_table_heading: bool = False,
|
||||
sheet_section_rows: bool = False,
|
||||
keep_table_heading: bool = DEFAULT_KEEP_TABLE_HEADING,
|
||||
sheet_section_rows: bool = DEFAULT_SHEET_SECTION_ROWS,
|
||||
drop_wrapped_outline: bool = DEFAULT_DROP_WRAPPED_OUTLINE,
|
||||
outline_gate: bool = DEFAULT_OUTLINE_GATE,
|
||||
first_span_from_zero: bool = DEFAULT_FIRST_SPAN_FROM_ZERO,
|
||||
|
|
|
|||
|
|
@ -273,6 +273,45 @@ def directory_control(bundle_root: Path, *, profile: BundleProfile) -> tuple[int
|
|||
return walked, on_disk
|
||||
|
||||
|
||||
# --- Where the reader runs the commands (O6) -----------------------------------
|
||||
|
||||
#: `okf project` writes the skill to `<root>/.claude/skills/<id>-consume` and
|
||||
#: the bundle to `<root>/.okf/<id>`, then tells the reader to start `claude` in
|
||||
#: `<root>`. So `<root>` is where the skill's commands are read from, and a path
|
||||
#: written relative to it is the one a reader can paste.
|
||||
SKILLS_LAYOUT = (".claude", "skills")
|
||||
|
||||
|
||||
def project_root_of(out: Path) -> Path | None:
|
||||
"""`<root>/.claude/skills/<name>` -> `<root>`; anything else -> None.
|
||||
|
||||
Read off the path rather than passed in, so `okf skill` and `okf project`
|
||||
reach the same answer without a second parameter that could disagree with
|
||||
the layout on disk.
|
||||
"""
|
||||
resolved = out.resolve()
|
||||
if resolved.parts[-3:-1] == SKILLS_LAYOUT:
|
||||
return resolved.parents[2]
|
||||
return None
|
||||
|
||||
|
||||
def as_written(path: Path, *, base: Path | None) -> str:
|
||||
"""The path as the skill states it: relative to `base` when it is under it.
|
||||
|
||||
A path outside `base` stays absolute on purpose. `../../..` is not more
|
||||
portable than `/Users/...`, it is only harder to read, and a skill that
|
||||
states a path its reader cannot resolve is worse than one that states an
|
||||
honest absolute.
|
||||
"""
|
||||
resolved = path.resolve()
|
||||
if base is None:
|
||||
return str(resolved)
|
||||
try:
|
||||
return resolved.relative_to(base).as_posix()
|
||||
except ValueError:
|
||||
return str(resolved)
|
||||
|
||||
|
||||
# --- The instantiation --------------------------------------------------------
|
||||
|
||||
|
||||
|
|
@ -321,10 +360,11 @@ def render(
|
|||
name = f"{slug(bundle_id)}-consume"
|
||||
text = template_path().read_text(encoding="utf-8")
|
||||
text = text.split("---\n", 2)[2]
|
||||
base = project_root_of(out)
|
||||
text = _rewrite(
|
||||
text,
|
||||
bundle_root=bundle_root,
|
||||
skill_path=out.resolve() / "SKILL.md",
|
||||
bundle_root=Path(as_written(bundle_root, base=base)),
|
||||
skill_path=Path(as_written(out / "SKILL.md", base=base)),
|
||||
bundle_id=bundle_id,
|
||||
ref=ref,
|
||||
name=name,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue