From 1ed379ebc77913927a7318bf7dfec9049f979440 Mon Sep 17 00:00:00 2001 From: nullhack Date: Wed, 8 Jul 2026 04:24:04 -0400 Subject: [PATCH] feat(pipeline): refresh state, cache bootstrapping, rework triggers, branch discipline Cycle-close refresh state (deliver-flow 1.4 -> 1.5): - new `refresh` state after publish: regenerate docs/state.md living spec from the current tests, summarize carry-over cache files to ~1000 words, regenerate docstrings across the whole source surface then strip all .py - optional publish on stakeholder approval (approved -> publish, declined -> refresh); refresh always closes the cycle Cache bootstrapping (lazy, per-state): - new .templates/cache/interview-notes.md.template (funnel skeleton) - interview-general bootstraps .cache//interview-notes.md from the template if absent, mirroring the model-data-schema journal precedent Interview-implied rework as a second trigger source: - interview-building-blocks flags findings that modify existing blocks - author-test-stubs and write-test-py read both trigger sources (journal build-escalation + interview flags) -> @pytest.mark.pending Branch discipline mechanized end-to-end: - contract surface committed to dev at plan (simulate-contracts) - build cuts feature/ off dev at entry (select-build-target) - deliver squash-merges + deletes the branch (merge-to-dev); no dangling - feature/ threads the existing session id; one per build cycle AGENTS.md binding constraints sharpened: - dispatched agent never transitions / skips / holds the todo - todo-first as a hard constraint; two-hats model documented Docstring lifecycle simplified to conceptual bullets (script is the mechanical substitute; PYI021 covers stubs in lint). Bumps version 10.0.0 -> 10.1.0. --- .flowr/flows/deliver-flow.yaml | 59 +++++++++++++++++-- .flowr/flows/pipeline-flow.yaml | 5 +- .flowr/flows/tdd-flow.yaml | 12 ++-- .../methodology/separation-of-concerns.md | 9 +++ .../software-craft/docstring-lifecycle.md | 55 +++-------------- .../software-craft/git-conventions.md | 15 ++--- .opencode/knowledge/software-craft/tdd.md | 4 +- .../knowledge/workflow/flowr-operations.md | 1 + .opencode/skills/author-test-stubs/SKILL.md | 2 +- .../skills/interview-building-blocks/SKILL.md | 3 +- .opencode/skills/interview-general/SKILL.md | 2 +- .opencode/skills/merge-to-dev/SKILL.md | 2 +- .opencode/skills/publish-release/SKILL.md | 2 +- .opencode/skills/refresh-cycle/SKILL.md | 13 ++++ .opencode/skills/select-build-target/SKILL.md | 4 +- .opencode/skills/simulate-contracts/SKILL.md | 1 + .opencode/skills/write-test-py/SKILL.md | 5 +- .templates/cache/interview-notes.md.template | 35 +++++++++++ AGENTS.md | 43 +++++++++----- pyproject.toml | 2 +- scripts/strip_docstrings.py | 16 ++--- uv.lock | 2 +- 22 files changed, 185 insertions(+), 107 deletions(-) create mode 100644 .opencode/skills/refresh-cycle/SKILL.md create mode 100644 .templates/cache/interview-notes.md.template diff --git a/.flowr/flows/deliver-flow.yaml b/.flowr/flows/deliver-flow.yaml index 13b97fe..bca34fc 100644 --- a/.flowr/flows/deliver-flow.yaml +++ b/.flowr/flows/deliver-flow.yaml @@ -1,13 +1,14 @@ flow: deliver-flow -version: 1.3.0 +version: 1.5.0 exits: - delivered states: - id: merge attrs: description: > - Squash-merge the feature commits into dev with the full suite and - whole-suite stubtest green on dev. + Squash-merge the feature branch into dev with the full suite and + whole-suite stubtest green on dev, then delete the branch. Publishing + is optional and on stakeholder approval. dispatch_to: release-engineer git branch: dev skills: [merge-to-dev] @@ -20,9 +21,12 @@ states: tests-green-on-dev: "==true" stubtest-clean-on-dev: "==true" next: - merged: + approved: to: publish when: merged + declined: + to: refresh + when: merged - id: publish attrs: @@ -36,4 +40,49 @@ states: output artifacts: - "release" next: - published: delivered + published: refresh + + - id: refresh + attrs: + description: > + Close the cycle: regenerate the living specification (docs/state.md) + from the current tests, summarize the carry-over cache files to ~1000 + words so the next cycle starts compact, regenerate docstrings for the + full source surface, then strip all .py. + dispatch_to: system-architect + git branch: dev + skills: [refresh-cycle] + input artifacts: + - "dev" + - "/**/*.pyi" + - "/**/*.py" + - "tests/integration/**/*_test.pyi" + - "tests/integration/**/*_test.py" + - "tests/e2e/**/*_test.pyi" + - "tests/e2e/**/*_test.py" + - "tests/cassettes/**" + - "migrations/**" + - "docs/glossary.md" + - ".cache//interview-notes.md" + - ".cache//external-contracts.md" + - ".cache//data-model.md" + - ".cache//journal.md" + output artifacts: + - "docs/state.md" + - ".cache//interview-notes.md" + - ".cache//external-contracts.md" + - ".cache//data-model.md" + - ".cache//journal.md" + - "/**/*.py" + - "tests/integration/**/*_test.py" + - "tests/e2e/**/*_test.py" + conditions: + refreshed: + spec-regenerated: "==true" + cache-summarized: "==true" + docstrings-regenerated: "==true" + docstrings-stripped: "==true" + next: + refreshed: + to: delivered + when: refreshed diff --git a/.flowr/flows/pipeline-flow.yaml b/.flowr/flows/pipeline-flow.yaml index df76ff7..9fb012c 100644 --- a/.flowr/flows/pipeline-flow.yaml +++ b/.flowr/flows/pipeline-flow.yaml @@ -55,8 +55,9 @@ states: - id: deliver attrs: description: > - Squash-merge the feature commits into dev with the full suite green, - then publish (notes / PR / tag). + Squash-merge the feature branch into dev with the full suite green and + delete it; optionally publish (notes / PR / tag) on approval, then + refresh closes the cycle. flow: deliver-flow flow-version: "^1" next: diff --git a/.flowr/flows/tdd-flow.yaml b/.flowr/flows/tdd-flow.yaml index 130308c..ff58430 100644 --- a/.flowr/flows/tdd-flow.yaml +++ b/.flowr/flows/tdd-flow.yaml @@ -10,7 +10,7 @@ states: Pull the next source contract to build from the pending backlog, respecting outside-in layer order. dispatch_to: product-owner - git branch: feature + git branch: feature/ skills: [select-build-target] input artifacts: - "/**/*.pyi" @@ -28,7 +28,7 @@ states: Confirm the target contract's tests fail for the right reason (ImportError for new work, assertion failure for rework). dispatch_to: software-engineer - git branch: feature + git branch: feature/ skills: [confirm-red-failure] input artifacts: - ".cache//build-target.md" @@ -54,7 +54,7 @@ states: its fixed .pyi; emit migrations/fixtures as needed. dispatch_to: software-engineer specialists: [data-architect, integration-engineer, ux-engineer, security-engineer] - git branch: feature + git branch: feature/ skills: [implement-from-stub] input artifacts: - ".cache//build-target.md" @@ -80,7 +80,7 @@ states: Improve the source structure under green tests with the .pyi and tests fixed. dispatch_to: software-engineer - git branch: feature + git branch: feature/ skills: [refactor-green] input artifacts: - "/**/*.pyi" @@ -98,7 +98,7 @@ states: quality, drift, and green tests. dispatch_to: reviewer specialists: [security-engineer] - git branch: feature + git branch: feature/ skills: [review-implementation] input artifacts: - "/**/*.pyi" @@ -126,7 +126,7 @@ states: Commit the unit as one logical change with a descriptive imperative message. dispatch_to: software-engineer - git branch: feature + git branch: feature/ skills: [ship-unit] input artifacts: - "/**/*.py" diff --git a/.opencode/knowledge/methodology/separation-of-concerns.md b/.opencode/knowledge/methodology/separation-of-concerns.md index 26983c4..2a62ea7 100644 --- a/.opencode/knowledge/methodology/separation-of-concerns.md +++ b/.opencode/knowledge/methodology/separation-of-concerns.md @@ -65,6 +65,15 @@ A gate `conditions` key (e.g. `stubtest-clean=true`) is EVIDENCE the dispatched When a state exits on `reveals-gap` / `needs-capture` / `needs-elicitation`, its finding must reach the phase it re-enters. The finding rides two carriers: the discovering state **appends** it to `.cache//journal.md` (its `output artifacts`), and the re-dispatch prompt carries it verbatim. The receiving state **reads** `journal.md` on entry (its `input artifacts`) and re-derives the detail from the artifacts — the tests, source stubs, and cassettes that *are* the spec. The journal is a transient per-session safety net against context lost mid-escalation (e.g. a compress between the discovering state and re-entry); it is not a second spec and never duplicates what the tests express. One stable file absorbs every escalation — present and future — so new edges never spawn new artifact types. +### Rework trigger sources + +The `@pytest.mark.pending` backlog is fed by two distinct trigger sources, read at `author-test-stubs` and `write-test-py`: + +- **Build escalation** — a contract gap discovered mid-build is appended to `.cache//journal.md` (the carrier above) and re-enters plan as rework on the named contract. +- **Discovery flag** — a finding in `.cache//interview-notes.md` that implies *modifying* an existing block (flagged `rework — modifies existing ` at `interview-building-blocks`) re-enters plan as rework on that block's contracts. + +Both surface as `@pytest.mark.pending` so `select-build-target` pulls them from the queue. The journal carries build-phase escalations; the interview notes carry requirements-level modification flags — two carriers, one backlog, no third artifact type. A discovery finding that adds a *new* block is new work, not rework, and authors a fresh stub without the pending marker. + ### Loading model The orchestrator reads the flow once per state and dispatches; it does not load the methodology layer up front. An agent loads when its role is dispatched; a skill loads when the state's `skills` names it; knowledge loads when a wikilink resolves. No session pays for the whole layer. diff --git a/.opencode/knowledge/software-craft/docstring-lifecycle.md b/.opencode/knowledge/software-craft/docstring-lifecycle.md index f9e1ea7..85a634d 100644 --- a/.opencode/knowledge/software-craft/docstring-lifecycle.md +++ b/.opencode/knowledge/software-craft/docstring-lifecycle.md @@ -1,59 +1,18 @@ --- domain: software-craft -tags: [docstrings, lint, lifecycle, token-economy, format] -last-updated: 2026-07-02 +tags: [docstrings, lint, lifecycle] +last-updated: 2026-07-08 --- # Docstring and Lint Lifecycle ## Key Takeaways -- Docstrings are human-language duplicates of code; during active development they cost tokens on every read and drift on every edit, so source `.py` is kept **naked** across red/green/refactor/review/ship and regenerated at deliver/merge from the stable code. -- Tests and stubs carry no docstrings, ever — tests ARE the spec (their names and bodies document behaviour) and PEP 484 stubs are signature-only (a docstring in a `.pyi` is forbidden — PYI021). -- Models add docstrings by training instinct; instruction alone does not suppress it. The `select` state enforces nakedness **mechanically** by running `scripts/strip_docstrings.py` over the target source `.py` at cycle entry, removing any docstring carried over from the last merge — the script is the backstop, not the prompt. -- Lint splits the same way: **bug-catchers run throughout** (the dev `ruff` select); restructuring lint (`SIM`, `RUF`) and `ruff format` run **at merge** via `task lint-merge`, so a dev edit is not churned by readability reordering mid-cycle. -- ruff itself cannot ban or remove docstrings (no rule flags a docstring's *presence* in `.py`; `PYI021` is `.pyi`-only), which is why the strip is a script and the docstring ban is not encoded as a ruff rule. - -## Concepts - -**Naked during dev, dressed at merge.** A docstring restates the code beneath it in prose. While the code is being written and rewritten — the whole of red/green/refactor/review — that prose is a stale duplicate dragging on every read (more tokens) and every edit (a code change forces a duplicate change, or the duplicate drifts). The phased answer is to keep source `.py` docstring-free for the entire build cycle and generate the docstrings once, at deliver/merge, from code that has stabilised. The duplication tax is paid once per cycle at merge, not continuously through dev. Tests and stubs are excluded: a test's documentation IS its body (editing the body edits the spec), and a stub is a PEP 484 signature (`...` only; a docstring there is a PYI021 violation). - -**The instinct is real; the script is the backstop.** Models are trained on docstring-rich code and reach for them by reflex — strong enough that an instruction ("do not write docstrings during build") leaks. The mechanical defence runs at `select`: the strip script walks the target `.py`'s AST, finds every docstring node, and deletes its line range, surgically, without reformatting the surrounding code. Whatever the instinct slipped in is gone before red runs. The strip runs at cycle entry, not on every commit, because the source `.py` is re-read and re-edited through the cycle and a single entry-point strip keeps it naked for the duration. - -**Lint splits dev from merge.** The same churn argument applies to lint that restructures for readability — `SIM` collapsing a nested `with`, `ruff format` reordering imports and whitespace. Mid-cycle, a format pass inflates the diff with non-semantic changes and fights the edit in progress. The dev `ruff` select is bug-catchers only (`A`, `ASYNC`, `B`, `C9`, `DTZ`, `ERA`, `F`, `G`, `LOG`, `PYI`, `S`) — `PYI` stays because it scopes to `.pyi` and enforces the naked-stub rule without touching `.py`. The restructure set (`SIM`, `RUF`) plus `ruff format` move to `task lint-merge`, run by `merge-to-dev` after the docstrings are generated, so dev stays quiet and the merged result is clean. - -**Why ruff cannot do this.** ruff's `D`/pydocstyle rules either *require* docstrings (D100–D107) or police their *style* (D200+); none flags a docstring's *presence* as a violation, and ruff has no autofix that strips docstrings from `.py` (the project verified this at the start — `PYI021` strips them only in `.pyi`). So "no docstrings during dev" is not expressible as a ruff rule; the AST strip script is the substitute, and the `D` rules are not used at all (the merge step *generates* docstrings; it does not *enforce* their presence via lint). - -## Content - -### The lifecycle - -| Phase | Source `.py` docstrings | Tests / stubs | Lint | -|---|---|---|---| -| plan (author/review/write/derive/simulate) | n/a (no `.py` yet) | naked (permanent) | dev select, `.pyi` via `PYI` | -| build (select/red/green/refactor/review/ship) | **naked** — `select` strips the target | naked | dev select only | -| deliver (merge) | **generated** for the shipped surface | naked | `task lint-merge` (add `SIM`, `RUF`, `format`) | - -### The strip mechanic - -`scripts/strip_docstrings.py ` parses the file, collects the `[lineno, end_lineno]` of every module/class/function docstring, and deletes those line ranges from the bottom up (so earlier line numbers stay valid). Every other line — formatting, comments, blanks — is preserved; the script does not reformat. It assumes a body is not docstring-only (true for real rework `.py`); it runs at `select` over the chosen target source `.py`, so red/green/refactor operate on naked code for the whole cycle. - -### The merge generate step - -`merge-to-dev`, after the squash-merge, regenerates docstrings for the public surface of every contract shipped this cycle (modules, classes, public functions and methods). The prose is faithful to the code, never a mechanical restatement of the signature; the *why* of an architectural decision lives in an ADR, not a docstring. Then `task lint-merge` runs (`ruff check --extend-select SIM,RUF .` plus `ruff format .`) and the result is committed on dev. The next cycle's `select` strips these docstrings from whatever file it touches next — they are a derived, regenerable view, never a carried artifact. - -### The lint split - -| Set | Where | Rules | -|---|---|---| -| dev (always) | plan-simulate gate; build gates; `ruff check .` | `A`, `ASYNC`, `B`, `C9`, `DTZ`, `ERA`, `F`, `G`, `LOG`, `PYI`, `S` | -| merge (deliver only) | `task lint-merge` | add `SIM`, `RUF`; then `ruff format .` | - -`PYI` stays in the dev set because it applies only to `.pyi` and enforces the naked-stub rule (PYI021); it does not churn `.py`. `SIM` is deferred because it restructures (the nested-`with` collapse that once forced a mid-build test edit is the canonical case); `RUF` is mixed and deferred with it for simplicity. +- Source `.py` is kept **docstring-free** during dev; docstrings are **generated** at merge from stable code, then stripped. Source only — never tests (the body is the spec) or stubs (`PYI021`). +- Lint splits: **bug-catchers run throughout** — the dev `ruff` select is `A ASYNC B C9 DTZ ERA F G LOG PYI S`; restructure (`SIM`, `RUF`) and `ruff format` run at merge via `task lint-merge`. +- ruff cannot ban a docstring's *presence* in `.py` (no rule flags it; `PYI021` is `.pyi`-only); `scripts/strip_docstrings.py` is the mechanical substitute. ## Related -- [[software-craft/tdd]] — the red/green/refactor cycle that runs on naked code -- [[software-craft/code-review]] — review gates the cycle output; the docstring state at review is naked -- [[software-craft/source-stubs]] — stubs are signature-only (PYI021); the docstring ban there is permanent -- [[software-craft/git-conventions]] — the squash-merge at which docstrings are regenerated +- [[software-craft/tdd]] — the cycle runs on docstring-free code +- [[software-craft/git-conventions]] — the merge at which docstrings regenerate diff --git a/.opencode/knowledge/software-craft/git-conventions.md b/.opencode/knowledge/software-craft/git-conventions.md index 1cc8673..feb3b71 100644 --- a/.opencode/knowledge/software-craft/git-conventions.md +++ b/.opencode/knowledge/software-craft/git-conventions.md @@ -1,7 +1,7 @@ --- domain: software-craft tags: [git, commits, conventional-commits, branching, squash-merge] -last-updated: 2026-07-01 +last-updated: 2026-07-08 --- # Git Conventions @@ -11,7 +11,7 @@ last-updated: 2026-07-01 - Commits follow **Conventional Commits**: `(): ` — types `feat`, `fix`, `test`, `refactor`, `chore`, `docs`, `ci`; no `wip`, `temp`, or untyped commit (Conventional Commits 1.0.0). - **One logical change per commit** — `ship-unit` commits the built contract's `.py` plus the structural artifacts it required as one change, and the `.pyi` is unchanged (contracts are fixed during build). - **Refactor commits are separate from feature commits**; a structural change is never mixed with a behaviour addition, so history stays bisectable and every commit leaves the tests green. -- The branch model is **`feature` → `dev` → `release`/`main`**: the build cycle runs on `feature`, squash-merges accumulate on `dev`, and publish targets `release` or `main`. Feature branches are short-lived and deleted after their squash-merge. +- The branch model is **`feature/` → `dev` → `release`/`main`**: the build cycle runs on a session-specific feature branch, squash-merges accumulate on `dev`, and publish targets `release` or `main`. Feature branches are short-lived and deleted after their squash-merge. The contract surface (tests + source stubs) is committed to `dev` at plan, so the feature branch carries only the source implementation — contracts consolidated in dev don't diverge across branches. - `merge-to-dev` squash-merges the feature commits into `dev` as one commit, then verifies the whole suite and the whole-suite stubtest are clean — no pending markers remain, and no drift was smuggled in by the batch. ## Concepts @@ -22,9 +22,9 @@ last-updated: 2026-07-01 **Refactor separate from feature.** A behaviour addition and a structural cleanup are different changes even when they land together, so they are different commits. Mixing them forces a reader (or a `git bisect`) to attribute a test failure to two unrelated changes at once; keeping them apart lets each commit stand as one defensible step, and every commit along the way leaves the tests green. -**The three-branch model.** Work flows in one direction: a `feature` branch carries a build cycle (red through ship), `dev` accumulates squash-merged contracts and is where integration is verified, and `release` or `main` is the publish target. Feature branches are short-lived — created for a contract, deleted after the squash-merge — so only `dev` and the publish branch are long-lived. +**The three-branch model.** Work flows in one direction: a session-specific `feature/` branch carries one build cycle (red through ship), `dev` accumulates squash-merged contracts and is where integration is verified, and `release` or `main` is the publish target. The contract surface — test stubs, test bodies, and source stubs — is committed to `dev` at plan; the feature branch is cut from dev at build entry and carries only the source implementation. Keeping contracts in dev (not on feature branches) is deliberate: feature branches that hold their own contracts diverge, and reconciling them is cheaper to avoid than to do. Feature branches are short-lived — created at build entry off dev, deleted after the squash-merge — so only `dev` and the publish branch are long-lived. -**Squash-merge into dev.** The granular per-contract commits on `feature` collapse into one commit on `dev`, carrying the contract's summary in its message. The merge is the gate where the whole suite and the whole-suite stubtest run: by now every `.pyi` has its `.py`, every pending marker is gone, and any drift the per-cycle scope hid is exposed across the full set. +**Squash-merge into dev.** The granular per-contract commits on `feature/` collapse into one commit on `dev`, carrying the contract's summary in its message. The merge is the gate where the whole suite and the whole-suite stubtest run: by now every `.pyi` has its `.py`, every pending marker is gone, and any drift the per-cycle scope hid is exposed across the full set. The branch is deleted after the merge. ## Content @@ -32,8 +32,8 @@ last-updated: 2026-07-01 | Branch | Lives | Carries | |---|---|---| -| `feature` | short — one build cycle | the per-contract commits (red → green → refactor → ship) | -| `dev` | long | squash-merged contracts; integration truth | +| `feature/` | short — one build cycle | the source implementation commits (red → green → refactor → ship) | +| `dev` | long | the contract surface (committed at plan) + squash-merged source; integration truth | | `release` / `main` | long | the publish target | ### Commit format @@ -64,9 +64,10 @@ last-updated: 2026-07-01 | Step | Check | |---|---| -| squash `feature` → `dev` | one commit, contract summary in the message | +| squash `feature/` → `dev` | one commit, contract summary in the message | | run the full suite | every test green; no pending markers remain | | run whole-suite `stubtest tests` | every source and test pair agrees — no batch drift | +| delete `feature/` | no dangling branches | ## Related diff --git a/.opencode/knowledge/software-craft/tdd.md b/.opencode/knowledge/software-craft/tdd.md index 2f62c82..43187cd 100644 --- a/.opencode/knowledge/software-craft/tdd.md +++ b/.opencode/knowledge/software-craft/tdd.md @@ -13,7 +13,7 @@ last-updated: 2026-07-01 - Green writes the **minimum code** to turn the tests green, implementing the `.py` from its fixed `.pyi`. YAGNI and KISS override every other principle; a hard-coded value is correct when the test needs only that value (Beck & Jeffries, 1999; North, 2006). - Refactor restructures the `.py` while the `.pyi` and the tests stay **frozen and green**; it is design-only, never convention compliance — conventions run in CI. A change that needs the `.pyi` is a contract gap escalated at review. - The cycle runs **per contract** — a source module and the tests that exercise it — in outside-in dependency order; stubtest is scoped to the modules built this cycle. -- Source `.py` is kept **naked of docstrings** across the whole cycle — `select` strips any carried over from the last merge, and docstrings are regenerated at deliver/merge from the stable code per [[software-craft/docstring-lifecycle]]. Tests and stubs are naked permanently. +- Source `.py` stays **docstring-free** across the cycle; docstrings are regenerated at merge per [[software-craft/docstring-lifecycle]]. Stubs stay docstring-free via `PYI021`. ## Concepts @@ -78,4 +78,4 @@ Removing the `@pytest.mark.pending` decorator can orphan its `import pytest` whe - [[software-craft/source-stubs]] — the fixed `.pyi` green implements to - [[software-craft/refactoring-techniques]] — the moves refactor applies - [[software-craft/code-review]] — the review that gates the cycle's output -- [[software-craft/docstring-lifecycle]] — source `.py` stays naked through the cycle; docstrings regenerate at merge +- [[software-craft/docstring-lifecycle]] — source `.py` stays docstring-free through the cycle; docstrings regenerate at merge diff --git a/.opencode/knowledge/workflow/flowr-operations.md b/.opencode/knowledge/workflow/flowr-operations.md index 69cd069..7065604 100644 --- a/.opencode/knowledge/workflow/flowr-operations.md +++ b/.opencode/knowledge/workflow/flowr-operations.md @@ -106,6 +106,7 @@ The temple8 flows' guarded transitions and the evidence the orchestrator asserts | tdd / green | `test-green` | `test-status=green`, `stubtest-clean=true` | | tdd / review | `approved` | `impl-matches-contract`, `source-quality-clean`, `stubtest-clean`, `tests-green` | | deliver / merge | `merged` | `tests-green-on-dev`, `stubtest-clean-on-dev` | +| deliver / refresh | `refreshed` | `spec-regenerated`, `cache-summarized`, `docstrings-regenerated`, `docstrings-stripped` | Bare transitions (no `when:`) fire without evidence — e.g. the `select → red` advance, the escalate exits (`needs-contracts`, `needs-elicitation`, `needs-capture`), and `all-built → shipped`. diff --git a/.opencode/skills/author-test-stubs/SKILL.md b/.opencode/skills/author-test-stubs/SKILL.md index 56dee6c..2d9ff80 100644 --- a/.opencode/skills/author-test-stubs/SKILL.md +++ b/.opencode/skills/author-test-stubs/SKILL.md @@ -6,7 +6,7 @@ description: "Author test stub files (.pyi) whose signatures express the domain, # Author Test Stubs 1. Load [[software-craft/test-stubs]], [[software-craft/test-design]] — stub conventions and what-to-test patterns. -2. Read `.cache//journal.md` (always present — bootstrapped at plan entry by `model-data-schema`). IF it carries escalation findings from build (contract gaps) THEN rework the contracts it names first; otherwise skip. +2. Read `.cache//journal.md` (always present — bootstrapped at plan entry by `model-data-schema`) and `.cache//interview-notes.md`. Two rework trigger sources feed the backlog: (a) IF the journal carries escalation findings from build (contract gaps) THEN rework the contracts it names first; (b) IF the interview notes flag findings that MODIFY an existing block (`rework — modifies existing ` from `interview-building-blocks`) THEN rework those contracts' `.pyi` signatures to match the changed requirement. Either source marks the affected contracts as rework; `write-test-py` re-applies the `@pytest.mark.pending` marker to the affected tests so `select-build-target` pulls them from the backlog. A finding with no rework flag is new work — author a fresh stub. 3. Author integration and E2E test stubs only — no unit-test stubs. Author in build layer order so external-boundary contracts precede adapter, then internal-data, then internal contracts. 4. Express the requirement in class and method signatures with type annotations: entity relationships, compositions, and the behaviour each test asserts. External-layer stubs assert against the captured cassettes — real shapes, not guesses. 5. External-boundary replay rule. An HTTP (httpx) adapter test MUST use `with vcr.use_cassette(cassette_path(NAME)):` as a context manager around the adapter call — in-process replay against the captured exchange per [[software-craft/external-fixtures]]. `pytester` is reserved for CLI subprocess tests ONLY; never use it for vcrpy cassette replay (it spawns a child pytest process, breaks collection, and hides the cassette contract from the type surface). Exception: a library-boundary adapter vcrpy cannot intercept (e.g. ddgs/primp) uses `monkeypatch` on the adapter's transport, not `pytester`. The wrong-vs-right pattern and the kind-dispatch table are in [[software-craft/external-fixtures]]. diff --git a/.opencode/skills/interview-building-blocks/SKILL.md b/.opencode/skills/interview-building-blocks/SKILL.md index 6439284..db678a1 100644 --- a/.opencode/skills/interview-building-blocks/SKILL.md +++ b/.opencode/skills/interview-building-blocks/SKILL.md @@ -9,4 +9,5 @@ description: "Funnel level 3 — identify building-block names and rough boundar 2. Read the running interview-notes; append the building-block names and boundaries to it. 3. Capture building-block NAMES and rough boundaries ONLY — no detailed spec (rules, examples, criteria come later in planning). The purpose is to know WHAT building blocks exist, not to specify each. 4. Ask the simplicity question for each block: "Is this building block load-bearing, or could it collapse into a neighbour?" A block with no unique responsibility a cited need requires is speculative per [[methodology/simplicity-discipline]] — flag it for collapse into a neighbour or drop at consolidation. A block the cited need requires but no current block covers is a gap, flagged here (not silently filled). -5. Run gap analysis: IF a bounded context from cross-cutting maps to no block THEN flag the gap; IF a quality attribute maps to no block THEN flag the gap. Do not silently fill gaps per [[requirements/domain-decomposition]], [[requirements/aggregate-boundaries]]. +5. Flag rework: IF a finding implies MODIFYING an existing block's behaviour (not adding a new one) THEN mark that block in the decomposition table as `rework — modifies existing ` and record the stakeholder's CIT-grounded reason. This is a requirements-level observation only — discovery does not touch tests. Plan reads these flags at `author-test-stubs` and marks the matching existing tests `@pytest.mark.pending` so the build backlog picks them up as rework per [[software-craft/tdd]]. A finding with no such flag is new work. +6. Run gap analysis: IF a bounded context from cross-cutting maps to no block THEN flag the gap; IF a quality attribute maps to no block THEN flag the gap. Do not silently fill gaps per [[requirements/domain-decomposition]], [[requirements/aggregate-boundaries]]. diff --git a/.opencode/skills/interview-general/SKILL.md b/.opencode/skills/interview-general/SKILL.md index f07eada..c473436 100644 --- a/.opencode/skills/interview-general/SKILL.md +++ b/.opencode/skills/interview-general/SKILL.md @@ -6,7 +6,7 @@ description: "Funnel level 1 — establish the big picture with seven broad ques # Interview (General) 1. Load [[requirements/interview-techniques]], [[methodology/simplicity-discipline]] — CIT, Laddering, CI Perspective Change, the Funnel, the Active Listening protocol, and the simplicity discipline that runs alongside every funnel level. -2. Begin the running interview-notes artifact (scoped to this session) with the general findings. +2. Bootstrap the interview-notes. IF `.cache//interview-notes.md` does not exist THEN render it from `.templates/cache/interview-notes.md.template` (substitute ``). The skeleton carries the funnel level headers only; absence on a first pass is expected — never a deliberation point. IF it exists THEN it carries the prior cycle's summarized interview (a ~1000-word summary left by `refresh-cycle`) — read it for continuity and append the general findings beneath the Level 1 header. 3. Ask seven big-picture questions in any natural order: Who, What, Why, When/Where, Success, Failure, Out-of-scope. Do not introduce category or topic labels — let the stakeholder's own categories emerge first (priming bias). 4. Probe every answer with CIT (a concrete past incident) and Laddering (climb to a writable constraint). IF recall stalls THEN re-frame from another actor's seat (CI Perspective Change). 5. Ask the simplicity question: "What's the smallest surface that meets the need?" The stakeholder's answer names the minimum the contracts must honour; an answer that inflates scope beyond the cited need is a speculative structure, flagged here per [[methodology/simplicity-discipline]]. diff --git a/.opencode/skills/merge-to-dev/SKILL.md b/.opencode/skills/merge-to-dev/SKILL.md index aaa9891..43e1007 100644 --- a/.opencode/skills/merge-to-dev/SKILL.md +++ b/.opencode/skills/merge-to-dev/SKILL.md @@ -6,7 +6,7 @@ description: "Squash-merge the feature commits into dev, regenerate docstrings, # Merge To Dev 1. Load [[software-craft/git-conventions]], [[software-craft/docstring-lifecycle]] — squash-merge form and the docstring lifecycle. -2. Switch to dev and squash-merge the feature commits per [[software-craft/git-conventions]]; the green-suite and whole-suite stubtest gate is verified on dev after the merge. +2. Switch to dev and squash-merge the feature branch (`feature/`) per [[software-craft/git-conventions]]; the green-suite and whole-suite stubtest gate is verified on dev after the merge. Delete the merged branch (`git branch -d feature/`) — no dangling branches. 3. Verify the full suite is green on dev after the merge: no pending markers remain (every source .pyi has its sibling .py, so every test runs) and every test passes. 4. Verify whole-suite stubtest is clean across both source and tests: every source .pyi and every test .pyi agrees with its sibling .py — no drift smuggled in by the batch. 5. Regenerate docstrings for the public surface of every contract shipped this cycle (modules, classes, public functions and methods) — faithful prose from the stable code, never a mechanical restatement of the signature; the *why* of an architectural decision lives in an ADR, not a docstring. Per [[software-craft/docstring-lifecycle]]. diff --git a/.opencode/skills/publish-release/SKILL.md b/.opencode/skills/publish-release/SKILL.md index 3a5990b..0b6b374 100644 --- a/.opencode/skills/publish-release/SKILL.md +++ b/.opencode/skills/publish-release/SKILL.md @@ -6,4 +6,4 @@ description: "Publish the delivered batch as release notes, a PR to main, or a t # Publish Release 1. Load [[software-craft/versioning]], [[software-craft/git-conventions]] — release/tag policy and git form. -2. Publish the batch per the project's release policy: release notes, a PR to main, or a tagged release per [[software-craft/versioning]], [[software-craft/git-conventions]]. +2. This step fires only on stakeholder approval (the `approved` transition from merge); if publication is declined, deliver skips straight to refresh. Publish the batch per the project's release policy: release notes, a PR to main, or a tagged release per [[software-craft/versioning]], [[software-craft/git-conventions]]. diff --git a/.opencode/skills/refresh-cycle/SKILL.md b/.opencode/skills/refresh-cycle/SKILL.md new file mode 100644 index 0000000..a89f867 --- /dev/null +++ b/.opencode/skills/refresh-cycle/SKILL.md @@ -0,0 +1,13 @@ +--- +name: refresh-cycle +description: "Close the pipeline cycle: regenerate the living spec from current tests, summarize the carry-over cache files, regenerate source docstrings then strip all .py so the next cycle starts compact." +--- + +# Refresh Cycle + +1. Load [[methodology/simplicity-discipline]], [[software-craft/git-conventions]] — the derived-view rule and the commit form. You close the cycle; the next discovery pass inherits what you write. +2. **Regenerate the living specification.** Render `docs/state.md` from `.templates/docs/state.md.template`, substituting ``, `` (from `pyproject.toml`), ``, and ``. Derive every field from the truth — the current test `.pyi`/`.py`, pending marks (`uv run pytest --collect-only -m pending -q`), cassettes, migrations, and `docs/glossary.md` — never from prose or memory. The contract-index intent column is read from each test body; the composition and data-flow lines are read from the e2e path the tests express. This file is a derived view; when prose and a test disagree, the test wins. Never hand-edit it. +3. **Summarize the carry-over cache files.** Rewrite each of `.cache//interview-notes.md`, `.cache//external-contracts.md`, `.cache//data-model.md`, and `.cache//journal.md` as a ~1000-word summary of this cycle — the decisions made, the contracts built, the gaps closed, the escalations resolved. Preserve the headers each template carries so the next pass appends in the right place; replace the body with the distilled record. Transient per-pass files (`build-target.md`, `probe-target.md`, `probe-research.md`) are NOT summarized — they are regenerated by the next pass. +4. **Regenerate docstrings.** Author faithful prose for every module, class, and public function/method across `/**/*.py` — never a mechanical restatement of the signature; the *why* of an architectural decision lives in an ADR, not a docstring. Source only, never tests. Then run `task lint-merge` (`ruff check --extend-select SIM,RUF . && ruff format .`). Commit on dev as `docs: regenerate docstrings + format`. +5. **Strip all `.py` docstrings.** Run `uv run python scripts/strip_docstrings.py $(find tests -name '*.py' -type f)` (many files, in place, line-range only; the glob skips `.pyi`). Commit on dev as `chore: strip docstrings for next dev cycle`. +6. Assert the gate evidence: `spec-regenerated=true`, `cache-summarized=true`, `docstrings-regenerated=true`, `docstrings-stripped=true`. diff --git a/.opencode/skills/select-build-target/SKILL.md b/.opencode/skills/select-build-target/SKILL.md index 2e96300..4b060e7 100644 --- a/.opencode/skills/select-build-target/SKILL.md +++ b/.opencode/skills/select-build-target/SKILL.md @@ -5,10 +5,10 @@ description: "Pull the next source contract to build from the pending backlog, r # Select Build Target -1. Load [[software-craft/tdd]], [[software-craft/docstring-lifecycle]] — the cycle and the naked-source rule. +1. Load [[software-craft/tdd]], [[software-craft/docstring-lifecycle]], [[software-craft/git-conventions]] — the cycle, the docstring-free-source rule, and the branch model. Ensure the build runs on `feature/`: if absent, branch it off dev (`git checkout -b feature/ dev`); if it exists (resumed cycle), check it out. The contract surface was committed to dev at plan, so the branch starts from it. 2. Discover the backlog: collect tests carrying the pending marker (`uv run pytest --collect-only -m pending -q`). Map each pending test to the source module it exercises. 3. Pick the lowest-layer contract that still has pending tests — layer order is dependency order, so a module is built only after the modules it imports are built. 4. IF a shared or foundation module (shared data types with no dedicated external-boundary test of its own) is needed THEN pull it in and build it alongside the first contract that depends on it; record both. 5. Treat the pending marker — not the presence of the source .py — as what makes a contract selectable: a reworked contract re-enters the queue even when its .py already exists. IF no pending tests remain anywhere THEN the system is built. -6. Strip the target source .py at cycle entry: IF the target module's .py already exists (rework) THEN run `uv run python scripts/strip_docstrings.py /.py` to remove the docstrings carried over from the last merge — source .py stays naked across red/green/refactor/review/ship per [[software-craft/docstring-lifecycle]]. Skip if the .py is absent (new contract). +6. IF the target module's .py already exists (rework) THEN strip it: `uv run python scripts/strip_docstrings.py /.py`. Skip if absent (new contract). 7. Skim the target's .pyi and its tests for context, not the whole project. diff --git a/.opencode/skills/simulate-contracts/SKILL.md b/.opencode/skills/simulate-contracts/SKILL.md index 4de87eb..455d8b9 100644 --- a/.opencode/skills/simulate-contracts/SKILL.md +++ b/.opencode/skills/simulate-contracts/SKILL.md @@ -27,3 +27,4 @@ description: "Compile the contract set in your head — simulate how the real ap 11. Run the dev ruff check (`ruff check .`) on the whole project; the gate is zero violations. Restructure lint (`SIM`, `RUF`) and `ruff format` are merge-time per [[software-craft/docstring-lifecycle]] — a readability restructure is not a plan defect. Plan-authored tests and stubs must pass the bug-catcher set before build. 12. IF a test references an external exchange no captured cassette covers THEN append the finding to `.cache//journal.md` (service, the missing case) and fire `needs-capture`. This routes back to explore to record the missing reality, not forward to build. 13. **Emit the verdict.** `accepted` only when the walkthrough reached no broken hop, the value traces found no disagreement, the spec-diff found no named-but-not-enforced finding, no build-implied gap was named, and the tool floor is clean. Any other outcome is a named gap routed to its target with the specifics (which hop, which value, which finding, which ambiguity) recorded in the journal. +14. On `accepted`, commit the gated contract surface to dev as one change — `git add tests ` then `git commit -m 'test: author contract surface'` — so the build's feature branch can branch off it. The surface is the test stubs/bodies and source stubs (`tests/**/*_test.pyi`, `tests/**/*_test.py`, `/**/*.pyi`); `data-model.md` is gitignored under `.cache/` and is not committed. diff --git a/.opencode/skills/write-test-py/SKILL.md b/.opencode/skills/write-test-py/SKILL.md index c0c4e9f..71e539b 100644 --- a/.opencode/skills/write-test-py/SKILL.md +++ b/.opencode/skills/write-test-py/SKILL.md @@ -6,11 +6,12 @@ description: "Transform the reviewed test stubs into executable test bodies, wit # Write Test Py 1. Load [[software-craft/solid]], [[software-craft/object-calisthenics]], [[software-craft/smell-catalogue]], [[software-craft/test-design]] — the quality criteria and body patterns. -2. Read `.cache//journal.md` (always present — bootstrapped at plan entry by `model-data-schema`). IF it carries escalation findings from build THEN edit the bodies of the contracts it names to match the reworked stubs; otherwise skip. +2. Read `.cache//journal.md` (always present — bootstrapped at plan entry by `model-data-schema`) and `.cache//interview-notes.md`. Two rework trigger sources feed the backlog: (a) IF the journal carries escalation findings from build THEN edit the bodies of the contracts it names to match the reworked stubs; (b) IF the interview notes flag findings that MODIFY an existing block THEN edit the bodies of those contracts to match the reworked `.pyi`. Either source is rework — step 9 re-applies the pending marker. Skip both if neither carries rework. 3. Write the test bodies — no docstrings, no comments; the body is the spec. Bodies define how entities relate, compose, and collaborate: wire classes, data, and objects following best practices. 4. Apply the code-quality gate here: SOLID, DRY, KISS, YAGNI, Object Calisthenics per [[software-craft/solid]], [[software-craft/object-calisthenics]]. IF a smell is present THEN reject it per [[software-craft/smell-catalogue]]. 5. Mark each test with the pending marker; the conftest hook skips pending tests so the suite stays green-with-skips until source is built. 6. Defer the system-under-test import into each test body so an unbuilt module collects cleanly and its pending tests skip rather than error at collection. Keep third-party and test-only imports at module top. 7. Keep the .py's module-level names and method signatures in exact agreement with its .pyi — stubtest checks the pair strictly. 8. Run the dev ruff check (`ruff check .`) on the authored test files; fix every violation so build receives bug-clean tests. Restructure lint (`SIM`, `RUF`) and `ruff format` are merge-time per [[software-craft/docstring-lifecycle]] — do not format here. -9. IF reworking an existing contract THEN edit the body to match the changed .pyi and re-apply the pending marker to the affected tests so they skip until build re-selects them. +9. Strip docstrings from the test `.py` you just wrote: `uv run python scripts/strip_docstrings.py `. +10. IF reworking an existing contract THEN edit the body to match the changed .pyi and re-apply the pending marker to the affected tests so they skip until build re-selects them. diff --git a/.templates/cache/interview-notes.md.template b/.templates/cache/interview-notes.md.template new file mode 100644 index 0000000..3f2632a --- /dev/null +++ b/.templates/cache/interview-notes.md.template @@ -0,0 +1,35 @@ +# Interview Notes — + +> The running record of the discovery funnel (general → cross-cutting → +> building-blocks → consolidate). Each level appends to this file; consolidate +> replaces the running notes with the full synthesis. On a first pass the +> skeleton is EMPTY (expected, not a missing input) — the product-owner +> bootstraps this file from the template at `interview-general` if it does not +> exist; absence on a first pass is never a deliberation point. Findings are +> grounded in cited stakeholder needs (CIT incidents, laddered constraints); +> speculative items are dropped at consolidation per +> [[methodology/simplicity-discipline]]. + +## Level 1 — General + +> Seven big-picture questions (Who, What, Why, When/Where, Success, Failure, +> Out-of-scope), probed with CIT and Laddering. No category or topic labels +> imposed — the stakeholder's own categories emerge first. + +## Level 2 — Cross-cutting + +> Behaviour groups, bounded contexts, integration points, lifecycle, and +> quality attributes — the structure of the domain. + +## Level 3 — Building blocks (tactical decomposition) + +> Block names and rough boundaries only — no spec. Gap analysis: every +> bounded context and quality attribute maps to ≥1 block. Findings that +> MODIFY an existing block are flagged here so plan can mark the matching +> tests `@pytest.mark.pending` as rework backlog. + +## Consolidation + +> The full synthesis across all three levels, presented for stakeholder +> approval; the accuracy gate for plan. Speculative items dropped, each drop +> flagged. diff --git a/AGENTS.md b/AGENTS.md index 35e4f72..37ccd92 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,24 +6,26 @@ through flowr. ## Binding constraints -1. **flowr is the single router.** Every state change runs through flowr — no improvised routing, no skipping states. -2. **The dispatched agent does the work.** Each state names one agent in `dispatch_to`; the orchestrator dispatches it, and that agent produces the artifacts and asserts the evidence. The orchestrator never authors the work. +1. **flowr is the single router.** Every state change runs through flowr — no improvised routing, no skipping, no pre-empting a later state's work. One state's work is exactly its `output artifacts`; the next begins only when the orchestrator transitions. +2. **The orchestrator dispatches; the dispatched agent does the work.** The orchestrator MUST invoke the state's `dispatch_to` agent — it never authors the work. The handoff gives the agent: `skills` paths, `input artifacts`, `output artifacts` (write only here), and the evidence keys to assert. The agent produces the artifacts and asserts evidence; it does not transition. 3. **The state's contract is binding.** Read every `input artifact` before starting — missing means stop, not assume. Write only to `output artifacts`. 4. **Assert only verified evidence; CI is the backstop.** On a **guarded** transition flowr fires only on the dispatched agent's asserted evidence (`--evidence k=v`); assert nothing you did not check — CI catches the lie (`ruff` / `pyright` / `mypy.stubtest` / `pytest`). **Unguarded** transitions (discovery, explore) carry no flowr gate — the orchestrator verifies the output artifacts and the stakeholder's approval IS the gate. -5. **Branch discipline.** Match the state's `git branch`; merge `feature` → `dev` only under the whole-suite gates. No dangling branches. +5. **Branch discipline.** Match the state's `git branch`: discovery/explore/plan/deliver run on `dev`; build runs on `feature/`, cut from dev at build entry. The contract surface is committed to `dev` at plan; the feature branch carries only the source implementation. Squash-merge `feature/` → `dev` only under the whole-suite gates, then delete the branch. No dangling branches. 6. **Every requirement traced.** Each interview finding maps to a test or an explicit deferral; an untraced requirement is a gap the simulate gate rejects. +7. **Only the orchestrator transitions.** Producing artifacts is not finishing — the orchestrator's verified `flowr transition` is. A dispatched agent never runs `flowr transition` / `flowr session`, never assumes the next state, never declares the flow done. +8. **Todo-first.** The first action on entering a state is to create its one-state todo (the loop below). No todo = no work. Only the orchestrator holds the todo; regenerate it after every transition. ## Driving a state -One state at a time. The orchestrator keeps **one todo per state, regenerated at -state entry** — the todo *is* this five-phase loop. "No todo = no work": work -outside the loop is untracked. One state per todo; regenerate on every -transition. +**Two hats.** AGENTS.md addresses the **orchestrator** — the agent holding the flowr session that drives state to state. A **dispatched agent** (invoked via a state's `dispatch_to`) does one state's work and returns: load the skill, read the inputs, write only the outputs, assert the evidence, and never transition, skip, or hold the todo. If you were invoked to do a state's work, you are the dispatched agent — produce, return, stop. +One state at a time. The orchestrator keeps **one todo per state** — the todo *is* this loop: + +0. **Create the todo** — `todowrite` the phases below before any other action (8). 1. **Read** — `flowr check --session `; parse `dispatch_to`, `skills`, `input artifacts`, `output artifacts`, `git branch`, `conditions`. -2. **Verify inputs** — every `input artifact` exists on disk. Missing = stop (binding constraint 3). -3. **Dispatch** — call the agent named in `dispatch_to` with the `skills` paths (`.opencode/skills//SKILL.md`) and the input artifacts. The dispatched agent writes only to `output artifacts` and returns asserted evidence. -4. **Verify outputs + evidence** — the `output artifacts` were produced; if the transition is guarded, its `conditions` evidence is real (binding constraint 4). +2. **Verify inputs** — every `input artifact` exists on disk. Missing = stop (3). +3. **Dispatch** — invoke `dispatch_to` with the full handoff: skill paths, input artifacts, output artifacts (write only here), evidence keys, and the boundary — *do only this state's work; do not transition, do not skip; the orchestrator moves the flow* (2, 7). It returns the output artifacts and asserted evidence. +4. **Verify outputs + evidence** — the `output artifacts` were produced; if the transition is guarded, its `conditions` evidence is real (4). 5. **Transition** — `flowr transition --session --evidence k=v …`, then regenerate the todo from the next state's `check`. Routing is one flow with five subflows: `pipeline-flow` → discovery → explore → @@ -59,11 +61,19 @@ then builds it: **discover** elicits requirements (interview funnel → glossary external contract); **plan** writes tests up front (`*_test.pyi` → `*_test.py` `@pytest.mark.pending` → source `.pyi` → simulate); **build** implements each source `.py` from its fixed `.pyi` one contract per cycle (red → green → -refactor → review → ship); **deliver** squash-merges to dev and publishes. -`@pytest.mark.pending` is the backlog signal (new work + rework); an empty -backlog is done. flowr's gates collect EVIDENCE the agent asserts; CI is the -enforcement backstop (ruff / pyright / `mypy.stubtest` / pytest). When prose and -a test disagree, the test wins. +refactor → review → ship); **deliver** squash-merges the feature branch to dev +(then deletes it), optionally publishes on approval, and **refresh** closes the +cycle: +regenerates `docs/state.md` from the current tests, summarizes the carry-over +cache files to ~1000 words so the next cycle starts compact, and closes the +docstring lifecycle (regenerate the public source surface then strip all `.py` +— source and test — docstring-free for the next cycle). Rework enters the backlog from two trigger sources: build-escalation +findings in the journal, and discovery findings in the interview that flag a +modification to an existing block; both surface as `@pytest.mark.pending` at +plan so `select-build-target` pulls them. An empty backlog is done. flowr's +gates collect EVIDENCE the agent asserts; CI is the enforcement backstop (ruff / +pyright / `mypy.stubtest` / pytest). When prose and a test disagree, the test +wins. Authoring detail (staged contracts, evidence vs enforcement, the docstring/lint lifecycle, separation-of-concerns, secrets/config) lives in the knowledge layer @@ -80,6 +90,7 @@ Committed (the source of truth): | `tests/cassettes/`, `tests/fixtures/` | recorded vcrpy cassettes; fixtures | | `migrations/` | Alembic migrations — the schema spec | | `docs/glossary.md` | ubiquitous language | +| `docs/state.md` | living specification — regenerated each cycle by refresh from the tests | | `.flowr/flows/` | flow definitions | | `.opencode/`, `.templates/`, `.github/` | methodology, templates, CI | @@ -87,7 +98,7 @@ Gitignored (local working state, regenerated on demand): | Path | Holds | |------|-------| -| `.cache//` | interview notes, external contracts, probe research, build target | +| `.cache//` | interview notes, external contracts, data model, journal (carry-over, summarized to ~1000 words at cycle close); build target, probe target/research (transient per-pass) | | `.cache/explore/` | throwaway probe scripts (run once; never imported) | | `.cache/sessions/` | flowr session state | | `.env` | non-secret local config (12-factor) | diff --git a/pyproject.toml b/pyproject.toml index 35c13f8..6063b87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "temple8" -version = "10.0.0" +version = "10.1.0" description = "Test-driven agent orchestration template with YAML flow definitions, staged test contracts, and BDD traceability" readme = "README.md" requires-python = ">=3.13" diff --git a/scripts/strip_docstrings.py b/scripts/strip_docstrings.py index 1a62521..c9a2497 100644 --- a/scripts/strip_docstrings.py +++ b/scripts/strip_docstrings.py @@ -1,14 +1,10 @@ -"""Strip docstrings from Python source files (surgical, line-range based). +"""Strip docstrings from Python source files. -Used by the tdd `select` state to naked a target source `.py` at cycle entry, -removing docstrings carried over from the last merge's generation pass. Tests -and stubs are never passed through here — they stay naked permanently. - -The edit is line-range only: each docstring's `[lineno, end_lineno]` is removed -and every other line (formatting, comments, blank lines) is preserved, so the -file does not get reformatted as a side-effect. Assumes bodies are not -docstring-only (true for real rework `.py`); a docstring-only body would need a -`pass` substituted, which this script does not do. +Deletes each docstring's `[lineno, end_lineno]` from the bottom up; every other +line (formatting, comments, blanks) is preserved — no reformat. Accepts many +files, edits each in place. Stubs (`.pyi`) are skipped by the caller's +`-name '*.py'` glob; `PYI021` handles them in lint. Assumes bodies are not +docstring-only. """ import ast diff --git a/uv.lock b/uv.lock index c985e2d..b24b74c 100644 --- a/uv.lock +++ b/uv.lock @@ -1103,7 +1103,7 @@ wheels = [ [[package]] name = "temple8" -version = "10.0.0" +version = "10.1.0" source = { editable = "." } [package.optional-dependencies]