From 424f258d90e11841b809ed1b6f243b6ee2094c59 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Thu, 17 Sep 2026 00:10:45 -0700 Subject: [PATCH 1/6] Docs: reconcile published v1.4.2 guidance, roadmap, and qualification v1.4.2 is published from 55339bf1acf76d33be5937e80bdaad772e0b2bf5 under the annotated v1.4.2 tag, and release #952 is closed, but current-facing docs on main still described it as an unpublished source candidate pending #952. - State v1.4.2 as the published package-index baseline across README, install, quickstart, try-in-10-minutes, the public-release and OSS checklists, the early-adopter runbooks, sessions, github-setup, builders-grok-cursor, the PyPI runbook, and release history. README, current-state, and the rollout plan now use versioning.public_baseline_sentence() verbatim. - Convert docs/v142-release-notes.md and docs/v142-qualification.md into final records. Only proven outcomes are PASS, each bound to its evidence: the independent Codex audit of PR #1006 head 32706cf5, CI run 35188065537, publish run 35189302150, the wheel/sdist digests, and both verified local Board restarts. Run 35189721623's skipped publish jobs are recorded as the intended posture. Metadata upload is marked not required; no cloud aggregate check is claimed. Both pages note that the immutable v1.4.2 tag still carries the prepublication snapshots; the tag is not touched. - Record v1.4.0/v1.4.1/v1.4.2 as shipped in the roadmap, Board complete at 55339bf, #951 as the separately pending hosted canary, and v1.5.0 Slack as the active phase. Historical PR mapping moves to past tense and merged PRs now use /pull/ URLs while issues use /issues/. - Surface Graphify as shipped optional functionality: README navigation to setup/lifecycle/queries, a ramp-up flow in graphify-setup.md, the excluded inputs and the absence of a working-tree watcher, and a dated historical banner on graphify-evaluation.md that preserves its benchmark record. - Surface the persistent Board service and its macOS/launchd boundary from README and docs navigation, fix the board-demo browser wording, and move the stranded delayed_health_failed row into the delayed-health section where its post-apply semantics belong. - Add official pipx/uv installation links and command -v preflight checks without any curl-pipe-shell, and record the install boundaries adoption feedback keeps returning to. - Sync templates/lanes/README.md with the packaged copy so both document the supported `never` token expiry. - Make README links absolute so the built PyPI long description resolves, and teach release_readiness to accept either link spelling. - Canonicalize the installed-lineage harness temp root and both sides of every provenance assertion so the documented replay is portable on macOS, where temporary directories arrive through the /var -> /private/var symlink. - Move CHANGELOG Unreleased to the top and empty it; its entries shipped in v1.4.1 and are recorded there. - Rewrite tests/test_release_v142.py away from candidate-state enforcement onto the published identity, README link behavior, packaged-template consistency, issue-vs-pull URLs, and the repaired lifecycle table. Refs #1008. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 89 ++--- README.md | 212 ++++++++---- docs/board-service-lifecycle.md | 10 +- docs/builders-grok-cursor.md | 7 +- docs/cloud-benchmarking.md | 2 +- docs/context-delivery.md | 7 +- docs/context-provider-contract.md | 19 +- docs/context-setup.md | 9 +- docs/current-state-and-roadmap.md | 188 ++++++----- docs/early-adopter-invite-runbook.md | 4 +- docs/early-adopter-v05.md | 4 +- docs/first-run-transcript.md | 2 +- docs/first-user-demo-transcript.md | 2 +- docs/first-user-install-rehearsal.md | 8 +- docs/friendly-user-rollout-v05.md | 11 +- docs/github-setup.md | 2 +- docs/graphify-evaluation.md | 37 ++- docs/graphify-setup.md | 118 ++++++- docs/install.md | 79 ++++- docs/jira-cloud-setup.md | 5 +- docs/oss-v1-checklist.md | 2 +- docs/provider-matrix.md | 7 +- docs/public-release-checklist.md | 23 +- docs/pypi-release.md | 5 +- docs/quickstart.md | 29 +- docs/release-history.md | 6 +- docs/sessions.md | 6 +- docs/try-in-10-minutes.md | 10 +- docs/v141-qualification.md | 11 +- docs/v141-release-notes.md | 17 +- docs/v142-qualification.md | 112 ++++--- docs/v142-release-notes.md | 145 ++++---- examples/board-demo/README.md | 13 +- src/code_mower/release_readiness.py | 29 +- templates/lanes/README.md | 3 +- tests/test_lineage_producer_artifacts.py | 14 +- tests/test_release_hygiene.py | 46 ++- tests/test_release_v142.py | 402 +++++++++++++++++++---- 38 files changed, 1207 insertions(+), 488 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 915235e8..e6fe65a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,25 @@ project used alpha/beta prerelease tags while the first-user setup path, provider posture, and optional cloud sharing loop were hardening; v1.0 and later entries are regular releases. -## 1.4.2 — source candidate (publication pending) +## Unreleased + +Accepted on `main` and not in any published package. The published `v1.4.2` +package on the package index contains the originally shipped optional Graphify +integration; the entries below are intended for the next appropriate release. +The lineage-contract and Coworker-citation entries that were previously listed +here shipped in the published `v1.4.1` artifact and are recorded under that +release below. + +### Fixed + +- Graphify inventories larger than 256 KiB now use a separate bounded 16 MiB + provider-manifest reader without relaxing file coverage or hash checks. + Oversized provider manifests explicitly refuse publication. The accepted + provider's `doc_ref` nodes are recognized as non-code exclusions, and related + test queries recognize JavaScript/TypeScript `.test`/`.spec` and `__tests__` + conventions and import relationships without claiming execution coverage. + +## 1.4.2 — published - `code-mower board service` manages a persistent local Board: render a reviewable definition, install, inspect, restart, and remove it. macOS uses @@ -25,19 +43,22 @@ later entries are regular releases. - Board's work-first Now/Timeline/Releases/Health views, provider-neutral remote lifecycle observations, exact local work observations, and the qualified independent head-bound evidence and session-visibility - composition are accepted on `main` (#999, #1000, #1001, #1002, #1003 / #951). - This source PR adds no new cloud event fields; Slack-specific and hosted - cloud mappings remain #921. + composition ship in this release (PRs #999, #1000, #1001, #1002, #1003, + covering issue #951's merged local-evidence code). No new cloud event field + was added; Slack-specific and hosted cloud mappings remain #921. - Version, changelog, release notes, current docs, and package/release qualification contracts updated for v1.4.2, distinguishing local Board visibility from future Slack/cloud mappings. - See [candidate notes](docs/v142-release-notes.md) and the + See [release notes](docs/v142-release-notes.md) and the [evidence matrix](docs/v142-qualification.md) for inclusion and limitations. - Publication, installed-package acceptance, restart verification against the - two observed local Board services (port 5332, `codemower-ai/code-mower`, +- Published from release commit `55339bf1acf76d33be5937e80bdaad772e0b2bf5` + under the annotated `v1.4.2` tag. Production PyPI publication, canonical + cold install, the 1.4.1-to-1.4.2 upgrade, and restart verification against + both observed local Board services (port 5332, `codemower-ai/code-mower`, plus one additional private-repository port, each restarted by its - classified managed-or-transient posture), and the #951 bounded hosted - Devin canary remain pending #952. + classified managed-or-transient posture) passed and closed #952. The #951 + bounded hosted Devin canary is a separate open boundary and is not claimed + by this release. ## 1.4.1 — published @@ -50,40 +71,22 @@ later entries are regular releases. See [release notes](docs/v141-release-notes.md) for inclusion and limitations. Publication, installed-package acceptance and the release-specific scorecard, campaign, Board and cloud evidence completed the #915 closeout. - -## Unreleased - -### Added - -- Staged trusted lineage producer primitives for supervised takeover and same-writer - delivery persistence, authenticated semantic publication, explicit builder-label - reconciliation, and transport-preserving attribution. Independent workflow and - runner assets are available for explicit materialization; normal init and - automatic adoption remain unchanged until #992. - -- A pure typed builder-lineage contract with immutable exact targets, validated - contribution chains, explicit comment history and authority accounts, and - contributor-aware reviewer admission. Includes a standalone init support - module; live consumers will adopt the contract in a later stage. - -### Fixed - -- Graphify inventories larger than 256 KiB now use a separate bounded 16 MiB - provider-manifest reader without relaxing file coverage or hash checks. - Oversized provider manifests explicitly refuse publication. The accepted - provider's `doc_ref` nodes are recognized as non-code exclusions, and related - test queries recognize JavaScript/TypeScript `.test`/`.spec` and `__tests__` - conventions and import relationships without claiming execution coverage. -- Coworker fast-search responses with `source_id`, missing titles, or `Text` - records now retain cited evidence with explicit uncertainty. Records without - provenance are never given invented citations. Response-format failures, - including nested SDK task-group errors, no longer masquerade as access - failures; fetch and guided prepare return closed, redacted failure reasons - and preserve the explicit-refresh requirement. -- Coworker's `no_data` response now yields explicitly incomplete empty evidence - instead of a format or access failure. -- Local packet/storage failures and text budgets too small to retain evidence - have separate redacted diagnostics instead of blaming provider access. +- Shipped the typed builder-lineage contract -- immutable exact targets, + validated contribution chains, explicit comment history and authority + accounts, and contributor-aware reviewer admission -- together with the + trusted lineage producer primitives for supervised takeover, same-writer + delivery persistence, authenticated semantic publication, explicit + builder-label reconciliation, and transport-preserving attribution. The + independent workflow and runner assets stay opt-in materialization; normal + init and automatic adoption are unchanged. +- Fixed Coworker fast-search responses carrying `source_id`, missing titles, or + `Text` records so they retain cited evidence with explicit uncertainty. + Records without provenance are never given invented citations, and + response-format failures -- including nested SDK task-group errors -- no + longer masquerade as access failures. A `no_data` response yields explicitly + incomplete empty evidence, and local packet/storage failures and + too-small text budgets report separate redacted diagnostics instead of + blaming provider access. ## v1.4.0 diff --git a/README.md b/README.md index c267a6ec..3694644b 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,25 @@ The current release is supervised-pilot, bring-your-own-agent-loop software. It is not a drop-in unattended merge gate. Humans still own credentials, repository policy, reviewer promotion, and exceptional decisions. -The current source candidate is `v1.4.2`, with target install spec -`code-mower==1.4.2`. Publication and installed-package qualification are pending -[#952](https://github.com/codemower-ai/code-mower/issues/952). The published -`v1.4.0` and `v1.4.1` artifacts remain unchanged. Install commands below target -v1.4.2 after publication; candidate rehearsals use the exact verified artifact. - -Documentation on `main` follows the source on `main`. After v1.4.2 publication, start with the -[`v1.4.2` guide](https://github.com/codemower-ai/code-mower/blob/v1.4.2/docs/try-in-10-minutes.md). +The current package-index release baseline is `v1.4.2`, with pinned package +install spec `code-mower==1.4.2`. Release evidence is recorded on the GitHub +release and in the first-user install rehearsal. v1.4.2 was published from +release commit `55339bf1acf76d33be5937e80bdaad772e0b2bf5` under the annotated +`v1.4.2` tag; see the +[v1.4.2 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.2) +and the [v1.4.2 qualification record](https://github.com/codemower-ai/code-mower/blob/main/docs/v142-qualification.md). The published +`v1.4.0` and `v1.4.1` artifacts remain unchanged. Every install command below +targets the published release. + +One qualification boundary stays open and is not claimed by v1.4.2: the bounded +hosted Devin canary tracked by +[#951](https://github.com/codemower-ai/code-mower/issues/951), which needs an +explicit owner authorization before it can run. + +Documentation on `main` follows the source on `main`. To read the guide exactly as +v1.4.2 shipped it, use the +[`v1.4.2` guide](https://github.com/codemower-ai/code-mower/blob/v1.4.2/docs/try-in-10-minutes.md); +the pages on `main` are the maintained current versions. ## What Code Mower Adds @@ -49,10 +60,16 @@ command -v code-mower code-mower --version ``` -Hosted agents and CI machines can use `uv tool install`; contributors should -use `scripts/dev-python` and an editable virtual environment. The -[Install And Bootstrap](docs/install.md) guide gives the exact cold-install, -upgrade, optional Coworker, and contributor commands. +`command -v code-mower` should print the path you expect and `code-mower +--version` should print `code-mower 1.4.2` before you point Code Mower at a +repository. If you do not have pipx, install it from the +[official pipx installation guide](https://pipx.pypa.io/stable/installation/). + +Hosted agents and CI machines can use `uv tool install` after installing uv +from the [official uv installation guide](https://docs.astral.sh/uv/getting-started/installation/); +contributors should use `scripts/dev-python` and an editable virtual +environment. The [Install And Bootstrap](https://github.com/codemower-ai/code-mower/blob/main/docs/install.md) guide gives the exact +cold-install, upgrade, optional Coworker, and contributor commands. From the repository you want to pilot: @@ -69,16 +86,16 @@ repository, start a provider, enable auto-merge, or upload data. Review and edit the generated configuration before opening the setup PR. To inspect representative output first, use the -[synthetic calibration example](examples/demo-calibration/README.md) and -[Board demo](examples/board-demo/README.md). +[synthetic calibration example](https://github.com/codemower-ai/code-mower/blob/main/examples/demo-calibration/README.md) and +[Board demo](https://github.com/codemower-ai/code-mower/blob/main/examples/board-demo/README.md). -Follow [Try Code Mower In 10 Minutes](docs/try-in-10-minutes.md) to run Codex +Follow [Try Code Mower In 10 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/try-in-10-minutes.md) to run Codex and Claude manually against that first PR. Automation tokens, recurring dispatch, branch-protection changes, and auto-merge are not prerequisites for the manual reviewer-gate pilot. After the first peer-review loop works, follow -[Build Loop In 30 Minutes](docs/build-loop-in-30-minutes.md) to add automated +[Build Loop In 30 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/build-loop-in-30-minutes.md) to add automated builder dispatch and the stricter promoted-pilot repository settings. ## Participants And Sessions @@ -117,8 +134,8 @@ Codex, Claude Code, and Cursor are qualified for the shared session, telemetry, lease, and Jira-authority contract in v1.4.2. Devin, Grok Bot, Antigravity, Muse, and custom hosts are recognized for briefs and provenance, while their execution remains an explicit handoff or provider-specific transport. See -[Participants And Sessions](docs/sessions.md) and the -[Provider Matrix](docs/provider-matrix.md). +[Participants And Sessions](https://github.com/codemower-ai/code-mower/blob/main/docs/sessions.md) and the +[Provider Matrix](https://github.com/codemower-ai/code-mower/blob/main/docs/provider-matrix.md). ## Optional Organizational Context @@ -142,7 +159,7 @@ code-mower session context feedback .code-mower/sessions/SESSION.json \ Every delivery and feedback read reauthorizes online. Private evidence and private review findings must not be copied into tracked files or public logs. -Start with [Optional Organizational Context Setup](docs/context-setup.md). +Start with [Optional Organizational Context Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/context-setup.md). ## Roles @@ -177,7 +194,7 @@ code-mower calibration auto-discover \ Review every proposed disposition. Promote a lane only after known-clean and known-blocked evidence satisfies the -[lane promotion policy](docs/lane-promotion-policy.md). +[lane promotion policy](https://github.com/codemower-ai/code-mower/blob/main/docs/lane-promotion-policy.md). ## Local Status And Board @@ -202,8 +219,27 @@ code-mower board reset --repo OWNER/REPO --yes ``` The Board includes the owner queue, reviewer verdict history and spend/latency -when those local inputs exist. Local paths are redacted by default. The Board -does not upload data. +when those local inputs exist. It binds to loopback only. Local paths are +redacted by default. The Board does not upload data. + +To keep one Board running across logout and reboot, install it as a persistent +local service. macOS is the supported platform, through launchd; every other +platform refuses rather than calling a transient process a service: + +```bash +code-mower board service render --repo OWNER/REPO --repo-path . --port 5332 +code-mower board service install --repo OWNER/REPO --repo-path . --port 5332 +code-mower board service status --json +code-mower board service restart --repo OWNER/REPO --repo-path . --port 5332 +code-mower board service remove --repo OWNER/REPO --yes +``` + +`render` prints the exact definition before anything is applied. `board stop +--repo OWNER/REPO` resolves one exact known binding and refuses an ambiguous, +duplicate, or contradicting selection, and it refuses a port a keepalive-managed +service would immediately reclaim. See +[Board Service Lifecycle](https://github.com/codemower-ai/code-mower/blob/main/docs/board-service-lifecycle.md) for the serving gate, +delayed health, and the fail-closed refusals. ## Optional Cloud Sharing @@ -217,8 +253,8 @@ code-mower cloud board-snapshot --repo-slug OWNER/REPO --json Neither command uploads without `--yes`. Default bundles exclude source code, raw diffs, model transcripts, raw stdout/stderr, auth output, issue body text, -local secret values, and secrets. See [Cloud Sharing](docs/cloud-sharing.md) -and the [Cloud Data Contract](docs/cloud-data-contract.md). +local secret values, and secrets. See [Cloud Sharing](https://github.com/codemower-ai/code-mower/blob/main/docs/cloud-sharing.md) +and the [Cloud Data Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/cloud-data-contract.md). ## Current Capabilities And Limits @@ -236,68 +272,100 @@ and the [Cloud Data Contract](docs/cloud-data-contract.md). GitLab, Bitbucket, broad unattended rollout, uncalibrated merge gates, Devin peer-orchestrator/reviewer parity, a hosted work-order CLI, a required Graphify -dependency, and Slack worker delivery are outside the v1.4.2 candidate. The current priorities and -boundaries are recorded in -[Current State And Roadmap](docs/current-state-and-roadmap.md). +dependency, and Slack worker delivery are outside v1.4.2. The current priorities +and boundaries are recorded in +[Current State And Roadmap](https://github.com/codemower-ai/code-mower/blob/main/docs/current-state-and-roadmap.md). + +## Optional Repository Context Graph + +Graphify shipped in v1.4.1 as an optional local repository-graph provider and +remains available in v1.4.2. It is separately installed into an operator-owned +environment, explicitly activated, and outside the base dependency set: a +default Claude + Codex install adds no Graphify dependency, no indexer, no +background service, and no watcher. + +```bash +code-mower init --graphify +code-mower context-graph doctor +code-mower context-graph status --json +``` + +`init --graphify` only renders acquisition and pin guidance; it installs and +indexes nothing. Start with +[Optional Graphify Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/graphify-setup.md) for the acquisition and +ramp-up flow, [Local Repository Graph Lifecycle](https://github.com/codemower-ai/code-mower/blob/main/docs/context-graph-lifecycle.md) +for what a build is allowed to see and where its state lives, and +[Bounded Queries And Context Packets](https://github.com/codemower-ai/code-mower/blob/main/docs/context-graph-queries.md) for the +four questions and the packet contract. ## Documentation ### Install And First Use -- [Install And Bootstrap](docs/install.md) -- [Try Code Mower In 10 Minutes](docs/try-in-10-minutes.md) -- [Upgrade An Existing Repository](docs/upgrade-existing-repo.md) -- [Quickstart Reference](docs/quickstart.md) -- [Troubleshooting](docs/troubleshooting.md) -- [First Run Transcript](docs/first-run-transcript.md) (v1.4.0 illustrative shape, not the current v1.4.2 pin) +- [Install And Bootstrap](https://github.com/codemower-ai/code-mower/blob/main/docs/install.md) +- [Try Code Mower In 10 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/try-in-10-minutes.md) +- [Upgrade An Existing Repository](https://github.com/codemower-ai/code-mower/blob/main/docs/upgrade-existing-repo.md) +- [Quickstart Reference](https://github.com/codemower-ai/code-mower/blob/main/docs/quickstart.md) +- [Troubleshooting](https://github.com/codemower-ai/code-mower/blob/main/docs/troubleshooting.md) +- [First Run Transcript](https://github.com/codemower-ai/code-mower/blob/main/docs/first-run-transcript.md) (v1.4.0 illustrative shape, not the current v1.4.2 pin) + +### Local Board And Repository Context + +- [Board Data Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/board-data-contract.md) +- [Board Service Lifecycle](https://github.com/codemower-ai/code-mower/blob/main/docs/board-service-lifecycle.md) +- [Board Demo Rehearsal](https://github.com/codemower-ai/code-mower/blob/main/examples/board-demo/README.md) +- [Optional Graphify Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/graphify-setup.md) +- [Local Repository Graph Lifecycle](https://github.com/codemower-ai/code-mower/blob/main/docs/context-graph-lifecycle.md) +- [Bounded Queries And Context Packets](https://github.com/codemower-ai/code-mower/blob/main/docs/context-graph-queries.md) +- [Graphify Evaluation Record](https://github.com/codemower-ai/code-mower/blob/main/docs/graphify-evaluation.md) (historical decision, 2026-09-12) ### Sessions, Builders, And Reviewers -- [Participants And Sessions](docs/sessions.md) -- [Build Loop In 30 Minutes](docs/build-loop-in-30-minutes.md) -- [Build Loop Operations](docs/build-loop.md) -- [Planning And Work Orders](docs/planning-work-orders.md) -- [Builder Experiments](docs/builder-experiments.md) -- [Orchestrator Prompt Pack](docs/orchestrator-prompt-pack.md) -- [Provider Matrix](docs/provider-matrix.md) -- [Provider Calibration Scorecard](docs/provider-calibration-scorecard.md) -- [Devin Peer-Support Qualification](docs/devin-peer-support-qualification.md) -- [Lane Standing Instructions](docs/lanes/README.md) -- [Codex Lane](docs/lanes/codex.md) -- [Claude Lane](docs/lanes/claude.md) -- [Cursor Lane](docs/lanes/cursor.md) -- [Devin Lane](docs/lanes/devin.md) -- [Self-Hosted Mac Runner](docs/self-hosted-mac-runner.md) -- [Local Audit Runner](docs/local-audit-runner.md) +- [Participants And Sessions](https://github.com/codemower-ai/code-mower/blob/main/docs/sessions.md) +- [Build Loop In 30 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/build-loop-in-30-minutes.md) +- [Build Loop Operations](https://github.com/codemower-ai/code-mower/blob/main/docs/build-loop.md) +- [Planning And Work Orders](https://github.com/codemower-ai/code-mower/blob/main/docs/planning-work-orders.md) +- [Builder Experiments](https://github.com/codemower-ai/code-mower/blob/main/docs/builder-experiments.md) +- [Orchestrator Prompt Pack](https://github.com/codemower-ai/code-mower/blob/main/docs/orchestrator-prompt-pack.md) +- [Provider Matrix](https://github.com/codemower-ai/code-mower/blob/main/docs/provider-matrix.md) +- [Provider Calibration Scorecard](https://github.com/codemower-ai/code-mower/blob/main/docs/provider-calibration-scorecard.md) +- [Devin Peer-Support Qualification](https://github.com/codemower-ai/code-mower/blob/main/docs/devin-peer-support-qualification.md) +- [Lane Standing Instructions](https://github.com/codemower-ai/code-mower/blob/main/docs/lanes/README.md) +- [Codex Lane](https://github.com/codemower-ai/code-mower/blob/main/docs/lanes/codex.md) +- [Claude Lane](https://github.com/codemower-ai/code-mower/blob/main/docs/lanes/claude.md) +- [Cursor Lane](https://github.com/codemower-ai/code-mower/blob/main/docs/lanes/cursor.md) +- [Devin Lane](https://github.com/codemower-ai/code-mower/blob/main/docs/lanes/devin.md) +- [Self-Hosted Mac Runner](https://github.com/codemower-ai/code-mower/blob/main/docs/self-hosted-mac-runner.md) +- [Local Audit Runner](https://github.com/codemower-ai/code-mower/blob/main/docs/local-audit-runner.md) ### Context And Trackers -- [Optional Organizational Context Setup](docs/context-setup.md) -- [Coworker Connections](docs/context-connections.md) -- [Context Delivery And Private Review](docs/context-delivery.md) -- [Context Provider Contract](docs/context-provider-contract.md) -- [Context Packet Schema](docs/context-packet-schema.md) -- [Jira Cloud Setup](docs/jira-cloud-setup.md) -- [Jira Adoption Rehearsal](docs/jira-adoption-rehearsal.md) -- [Work Tracker Data Contract](docs/tracker-data-contract.md) +- [Optional Organizational Context Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/context-setup.md) +- [Coworker Connections](https://github.com/codemower-ai/code-mower/blob/main/docs/context-connections.md) +- [Context Delivery And Private Review](https://github.com/codemower-ai/code-mower/blob/main/docs/context-delivery.md) +- [Context Provider Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/context-provider-contract.md) +- [Context Packet Schema](https://github.com/codemower-ai/code-mower/blob/main/docs/context-packet-schema.md) +- [Jira Cloud Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/jira-cloud-setup.md) +- [Jira Adoption Rehearsal](https://github.com/codemower-ai/code-mower/blob/main/docs/jira-adoption-rehearsal.md) +- [Work Tracker Data Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/tracker-data-contract.md) ### Trust, Operations, And Project Records -- [Architecture](docs/architecture.md) -- [Current State And Roadmap](docs/current-state-and-roadmap.md) -- [Lane Promotion Policy](docs/lane-promotion-policy.md) -- [Privacy And Threat Model](docs/privacy-threat-model.md) -- [Board Data Contract](docs/board-data-contract.md) -- [Cloud Data Contract](docs/cloud-data-contract.md) -- [Release Qualification](docs/release-qualification.md) -- [Public Release Checklist](docs/public-release-checklist.md) -- [v1.4.2 Release Notes](docs/v142-release-notes.md) -- [Release History And Archived Plans](docs/release-history.md) -- [Changelog](CHANGELOG.md) -- [Contributing](CONTRIBUTING.md) -- [Support](SUPPORT.md) -- [Security Policy](SECURITY.md) -- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Architecture](https://github.com/codemower-ai/code-mower/blob/main/docs/architecture.md) +- [Current State And Roadmap](https://github.com/codemower-ai/code-mower/blob/main/docs/current-state-and-roadmap.md) +- [Lane Promotion Policy](https://github.com/codemower-ai/code-mower/blob/main/docs/lane-promotion-policy.md) +- [Privacy And Threat Model](https://github.com/codemower-ai/code-mower/blob/main/docs/privacy-threat-model.md) +- [Cloud Data Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/cloud-data-contract.md) +- [Release Qualification](https://github.com/codemower-ai/code-mower/blob/main/docs/release-qualification.md) +- [Public Release Checklist](https://github.com/codemower-ai/code-mower/blob/main/docs/public-release-checklist.md) +- [v1.4.2 Release Notes](https://github.com/codemower-ai/code-mower/blob/main/docs/v142-release-notes.md) +- [v1.4.2 Qualification Record](https://github.com/codemower-ai/code-mower/blob/main/docs/v142-qualification.md) +- [Release History And Archived Plans](https://github.com/codemower-ai/code-mower/blob/main/docs/release-history.md) +- [Changelog](https://github.com/codemower-ai/code-mower/blob/main/CHANGELOG.md) +- [Contributing](https://github.com/codemower-ai/code-mower/blob/main/CONTRIBUTING.md) +- [Support](https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md) +- [Security Policy](https://github.com/codemower-ai/code-mower/blob/main/SECURITY.md) +- [Code of Conduct](https://github.com/codemower-ai/code-mower/blob/main/CODE_OF_CONDUCT.md) ## License diff --git a/docs/board-service-lifecycle.md b/docs/board-service-lifecycle.md index 63fbe856..8acb0594 100644 --- a/docs/board-service-lifecycle.md +++ b/docs/board-service-lifecycle.md @@ -60,6 +60,15 @@ comes back with stale arguments, fails the gate instead of passing on a single early probe. The payload reports `delayed_health` with its state, the settle and refresh windows, and how many refreshes it took. +When that window closes without a validating binding, the operation reports +`delayed_health_failed`: the service applied but its binding never validated. +This is the one failure that is *not* in the fail-closed table below, and the +difference matters. The refusals below are decided before anything is applied +and leave no local state behind; `delayed_health_failed` is decided after +launchd already holds the job, so the definition stays installed and +`board service status` keeps reporting the failing binding until you repair or +remove it. + ## Fail-closed refusals None of these change any local state: @@ -131,7 +140,6 @@ services are discovered by scanning definition files, so deleting one whose job survived would strand a running, self-restarting service where `status`, `remove` and the `board stop` keepalive guard could no longer see it. That case reports `remove_incomplete` and leaves the definition in place. -| `delayed_health_failed` | the service applied but its binding never validated | `--replace` is the only way to take over an existing definition for a port, and the replacement is atomic: the definition file is swapped with `os.replace`, and diff --git a/docs/builders-grok-cursor.md b/docs/builders-grok-cursor.md index 1200fcb0..790fad2c 100644 --- a/docs/builders-grok-cursor.md +++ b/docs/builders-grok-cursor.md @@ -67,9 +67,10 @@ no builder sidecar. Use the separately staged and its required `target_json`, `policy_json`, `authority_json`, and `transport_json` inputs for reviewed integration by a trusted caller. That producer owns the explicit-input contract; do not derive these inputs from PR -metadata or execute PR code/configuration to obtain them. The 1.4.2 source -candidate remains unqualified and unpublished; activation against the released -package remains gated by #952. +metadata or execute PR code/configuration to obtain them. These producer assets +ship in the published 1.4.2 package but are not materialized by normal init; +activating them against a repository is still an explicit, separately reviewed +integration by a trusted caller. Treat each PR branch as single-writer. The owning `builder:` identity is the only lane that should push commits to that branch; other builders and audit diff --git a/docs/cloud-benchmarking.md b/docs/cloud-benchmarking.md index 4703ea5b..1a7dac98 100644 --- a/docs/cloud-benchmarking.md +++ b/docs/cloud-benchmarking.md @@ -5,7 +5,7 @@ reports, and the local Board without a hosted account. CodeMower.com is an optional destination for longitudinal team reporting and future aggregate benchmarks. -## Current v1.4.2 Source Surface +## Current v1.4.2 Release Surface The current client can: diff --git a/docs/context-delivery.md b/docs/context-delivery.md index a01416c3..eb940ab8 100644 --- a/docs/context-delivery.md +++ b/docs/context-delivery.md @@ -299,6 +299,7 @@ was not returned stays unknown; replay performs authorization, not another organization search. The synthetic local-repository graph fixture uses the common packet validation -and evidence renderer for all six roles without OAuth identity fields. This -preserves an extension point for later Graphify evaluation; it does not install -or qualify Graphify in v1.4.0. +and evidence renderer for all six roles without OAuth identity fields. It +exercises the extension point that the shipped optional Graphify provider uses; +a passing fixture neither installs Graphify nor qualifies a provider build. See +[Optional Graphify Setup](graphify-setup.md). diff --git a/docs/context-provider-contract.md b/docs/context-provider-contract.md index a76b6879..b4b9a3b0 100644 --- a/docs/context-provider-contract.md +++ b/docs/context-provider-contract.md @@ -199,7 +199,7 @@ they are not live provider authorization evidence. The [v1.3 qualification score live delivery evidence from frozen reference assessment. Public artifacts contain no private account identities, source text, source IDs, or credentials. -## Later Graphify candidate +## Local repository provider (Graphify) Use a discriminated connection kind: remote organization or local repository. The shared evidence contract preserves extracted/inferred/unknown confidence, @@ -217,13 +217,16 @@ claims still resolve. Scope is a gate rather than a score: one out-of-scope citation rejects the packet. Stale or unknown revision binding fails that quality gate even when every citation resolves. -The [Graphify candidate](https://github.com/codemower-ai/code-mower/issues/876) -is **adopted as an optional, bounded local provider** behind this contract; see -the [evaluation record](graphify-evaluation.md) for the decision, the pinned -package record, and the conditions an implementing change must meet. Nothing is -installed or required yet. Synthetic graph fixtures prove only the extension -point; they do not establish Graphify compatibility or make it a v1.4.0 -dependency. +Graphify was **adopted as an optional, bounded local provider** behind this +contract under +[#876](https://github.com/codemower-ai/code-mower/issues/876), and it shipped in +`v1.4.1`. It remains optional: separately installed into an operator-owned +environment, explicitly activated, and outside the base dependency set. Nothing +is installed or required by a default Claude + Codex install. See +[Optional Graphify Setup](graphify-setup.md) for the current path and the +[evaluation record](graphify-evaluation.md) for the dated decision and pinned +package record. Synthetic graph fixtures in the suite prove only the extension +point; they are not provider compatibility evidence. The lifecycle around such a provider — exact pin, immutable tracked-file materialization, scrubbed environment, an OS sandbox that denies the provider diff --git a/docs/context-setup.md b/docs/context-setup.md index c57e68c9..71c13e2a 100644 --- a/docs/context-setup.md +++ b/docs/context-setup.md @@ -2,9 +2,12 @@ Start with the ordinary Claude + Codex installation. Coworker is optional and is never a participant or an implicit account inherited from your host tool. A -future repository-context provider, such as Graphify, can use the same policy, -packet and readiness contracts with its own capability and authorization checks. -Graphify is not installed or required by this feature. +repository-context provider uses the same policy, packet and readiness contracts +with its own capability and authorization checks. Graphify is the shipped +example: it is optional functionality, separately installed into an +operator-owned environment and explicitly activated, and it is neither installed +nor required by this Coworker feature. See +[Optional Graphify Setup](graphify-setup.md). ## Select a connection diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index 7f8eaee8..126ecd7f 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -20,13 +20,20 @@ provider differences: The product is useful without CodeMower.com. Cloud sharing is optional and dry-run-first. -## Current Source Candidate And Published Baseline +## Current Published Baseline -The current source candidate is `v1.4.2`, with target install spec -`code-mower==1.4.2`. Publication and installed-package qualification are pending -[#952](https://github.com/codemower-ai/code-mower/issues/952). +The current package-index release baseline is `v1.4.2`, with pinned package +install spec `code-mower==1.4.2`. Release evidence is recorded on the GitHub +release and in the first-user install rehearsal. It was published from release +commit `55339bf1acf76d33be5937e80bdaad772e0b2bf5` under the annotated `v1.4.2` +tag; release [#952](https://github.com/codemower-ai/code-mower/issues/952) is +closed. See the +[v1.4.2 release notes](v142-release-notes.md) and the +[v1.4.2 qualification record](v142-qualification.md). -The published v1.4.0 and v1.4.1 baselines require Python 3.12 or newer. Each provides: +`v1.4.0`, `v1.4.1` and `v1.4.2` have all shipped, and the v1.4.0 and v1.4.1 +artifacts remain unchanged. The published baselines require Python 3.12 or +newer. Each provides: - pipx, uv tool, and contributor installation paths; - safe setup previews and selectable participants; @@ -117,9 +124,10 @@ future hosted-service work. multi-provider process launcher. - Devin has stronger builder support than reviewer or orchestrator support. - Private Coworker delivery is limited to explicitly approved Claude, Codex, and - Devin roles in v1.4.0. -- Graphify remains an optional bounded provider with no default dependency, and - Slack is an ingress foundation only: v1.4.0 delivers no Slack worker results. + Devin roles. +- Graphify is a shipped optional bounded provider with no default dependency, + and Slack is an ingress foundation only: v1.4.2 delivers no Slack worker + results. - Provider cost fields remain unknown when the provider does not return them. - A successful release campaign proves installation and operational transport, not builder quality or reviewer promotion readiness. @@ -130,22 +138,29 @@ future hosted-service work. ## Near-Term Roadmap -`v1.4.0` is released and its published artifacts are immutable. The agreed -sequence from the stabilization epic is: - -1. Accepted on main: all seven `v1.4.0` stabilization implementation children - plus [#974](https://github.com/codemower-ai/code-mower/issues/974) evidence - verification. #963 is accepted through the #990/#991/#992 replacement stages - and final #997 integration, not the unaccepted #989 draft. -2. Graphify shipped together with those fixes as `v1.4.1` - ([#915](https://github.com/codemower-ai/code-mower/issues/915)). -3. Ship Board as `v1.4.2`. Board's code is accepted on `main`, including - [#935](https://github.com/codemower-ai/code-mower/issues/935), - #956/#957/#999/#1000/#1002, #961 via PR #1001, and #951's PR #1003; #951 - stays open only for its bounded hosted Devin canary, and #952 is the - release PR. -4. Supervised Slack remains planned for `v1.5.0`; its runtime work is deferred - until the sequence above is complete. +`v1.4.0`, `v1.4.1` and `v1.4.2` are released and their published artifacts are +immutable. The agreed sequence from the stabilization epic ran as follows: + +1. **Done.** All seven `v1.4.0` stabilization implementation children were + accepted on main, plus + [#974](https://github.com/codemower-ai/code-mower/issues/974) evidence + verification. #963 was accepted through the #990/#991/#992 replacement stages + and final PR #997 integration, not the unaccepted #989 draft. +2. **Done.** Graphify shipped together with those fixes as `v1.4.1`, closing + release [#915](https://github.com/codemower-ai/code-mower/issues/915). +3. **Done.** Board shipped as `v1.4.2` from release commit `55339bf`, closing + release [#952](https://github.com/codemower-ai/code-mower/issues/952). The + included work is + [#935](https://github.com/codemower-ai/code-mower/issues/935) and + [#961](https://github.com/codemower-ai/code-mower/issues/961), delivered by + PRs #956, #957, #999, #1000, #1001, #1002 and #1003. Issue + [#951](https://github.com/codemower-ai/code-mower/issues/951) stays open + only for its bounded hosted Devin canary, which needs an explicit owner + authorization and is not claimed by this release. +4. **Active phase.** Supervised Slack is the `v1.5.0` work + ([#903](https://github.com/codemower-ai/code-mower/issues/903) / + [#923](https://github.com/codemower-ai/code-mower/issues/923)). The + preceding sequence is complete, so its runtime work is no longer deferred. Devin support is a bounded builder qualification only: a maintained local CLI builder lane and the exact PR-bound hosted work-order library seam. Hosted @@ -159,16 +174,18 @@ It rejects an unqualified Devin orchestrator before lease or session writes and checks bounded hosted builder admission before new work. See [Participant Qualification](participant-qualification.md). These are main-line stabilization changes that shipped in `v1.4.1`. Effective-authority rendering -([#955](https://github.com/codemower-ai/code-mower/issues/955)) is accepted through #988; +([#955](https://github.com/codemower-ai/code-mower/issues/955)) was accepted +through PR #988; neither is part of the immutable `v1.4.0` artifact, and both are part of the published `v1.4.1` artifact. -Each step below is an independently gated epic rather than one cross-cutting -implementation PR. +Each phase below was an independently gated epic rather than one cross-cutting +implementation PR. Phases 1 to 3 are history; phase 4 is the active one. -### 1. Accepted `v1.4.0` Stabilization ([#979](https://github.com/codemower-ai/code-mower/issues/979)) +### 1. Accepted `v1.4.0` Stabilization -- shipped in `v1.4.1` ([#979](https://github.com/codemower-ai/code-mower/issues/979)) -Seven accepted main-only implementation units plus one evidence verification: +Seven accepted implementation units plus one evidence verification, all merged +and carried into the published `v1.4.1` artifact: - accurate advertised commands and live roadmap docs ([#965](https://github.com/codemower-ai/code-mower/issues/965)); @@ -186,67 +203,78 @@ Seven accepted main-only implementation units plus one evidence verification: - independent operational acceptance evidence ([#976](https://github.com/codemower-ai/code-mower/issues/976)). -#962 runs before #963 where handoff and provenance files overlap. #974 is +#962 ran before #963 where handoff and provenance files overlapped. #974 was evidence-only verification of existing hosted aggregate freshness; a confirmed -hosted defect becomes a separately recorded implementation child and its own -hosted PR rather than an assumed fix. +hosted defect would have become a separately recorded implementation child and +its own hosted PR rather than an assumed fix. -### 2. Graphify Repository Context — `v1.4.1` ([#902](https://github.com/codemower-ai/code-mower/issues/902) / release [#915](https://github.com/codemower-ai/code-mower/issues/915)) +### 2. Graphify Repository Context -- shipped in `v1.4.1` ([#902](https://github.com/codemower-ai/code-mower/issues/902) / release [#915](https://github.com/codemower-ai/code-mower/issues/915)) Graphify is a repository-context provider beside Coworker, not a participant. -Its runtime source is accepted on main: the -[evaluation record](graphify-evaluation.md) closed +The [evaluation record](graphify-evaluation.md) closed [#876](https://github.com/codemower-ai/code-mower/issues/876) with an adopt decision, and `code-mower context-graph`, described in the [lifecycle record](context-graph-lifecycle.md), closed -[#913](https://github.com/codemower-ai/code-mower/issues/913). The query/packet implementation -([#914](https://github.com/codemower-ai/code-mower/issues/914), accepted through -#982) consumes +[#913](https://github.com/codemower-ai/code-mower/issues/913). The query/packet +implementation ([#914](https://github.com/codemower-ai/code-mower/issues/914), +accepted through PR #982) consumes a pinned structured JSON contract and generates bounded impact, dependency, symbol, and related-test packets in one shape for Claude, Codex, and Devin. Release #915 shipped that accepted source and stabilization baseline as `v1.4.1`, completing the release-specific comparative scorecard, campaign, Board, and fresh aggregate evidence as part of that closeout. -Installation stays opt-in, no command requires an index to exist, and Code -Mower owns refresh policy rather than parsing human-oriented MCP prose. - -### 3. Board Clarity And Session Visibility — `v1.4.2` ([#945](https://github.com/codemower-ai/code-mower/issues/945) / release [#952](https://github.com/codemower-ai/code-mower/issues/952)) - -Board implementation is accepted on `main`, not underway. -[#935](https://github.com/codemower-ai/code-mower/issues/935) is complete and -merged with [#973](https://github.com/codemower-ai/code-mower/issues/973); -[#956](https://github.com/codemower-ai/code-mower/issues/956) and -[#957](https://github.com/codemower-ai/code-mower/issues/957) are merged, as -are the work-first Now/Timeline/Releases/Health views -([#1000](https://github.com/codemower-ai/code-mower/issues/1000)), exact -local work observations -([#999](https://github.com/codemower-ai/code-mower/issues/999)), -provider-neutral remote lifecycle observations -([#1002](https://github.com/codemower-ai/code-mower/issues/1002)), and -persistent Board services with stale-keepalive rejection during release -restart ([#961](https://github.com/codemower-ai/code-mower/issues/961), via -[PR #1001](https://github.com/codemower-ai/code-mower/pull/1001)). -#951's integrated qualification code (independent head-bound evidence and -session-visibility composition, via -[PR #1003](https://github.com/codemower-ai/code-mower/pull/1003)) is also -merged; #951 itself stays open only for its bounded hosted Devin canary, -which is tracked separately from this merged code evidence. Remaining work -is the release PR #952, which does not add cloud fields and does not claim -#951's pending hosted canary. +Graphify remains available in `v1.4.2`, unchanged. Installation stays opt-in and +outside the base dependency set, no command requires an index to exist, and Code +Mower owns refresh policy rather than parsing human-oriented MCP prose. See +[Optional Graphify Setup](graphify-setup.md) for the current ramp-up flow. + +### 3. Board Clarity And Session Visibility -- shipped in `v1.4.2` ([#945](https://github.com/codemower-ai/code-mower/issues/945) / release [#952](https://github.com/codemower-ai/code-mower/issues/952)) + +Board is complete at release commit `55339bf`. +[#935](https://github.com/codemower-ai/code-mower/issues/935) was completed by +[PR #973](https://github.com/codemower-ai/code-mower/pull/973). The work-first +Now/Timeline/Releases/Health views arrived in +[PR #1000](https://github.com/codemower-ai/code-mower/pull/1000), the frozen +provider-neutral observation contract and existing-data hierarchy in +[PR #956](https://github.com/codemower-ai/code-mower/pull/956) and +[PR #957](https://github.com/codemower-ai/code-mower/pull/957), exact local +work observations in +[PR #999](https://github.com/codemower-ai/code-mower/pull/999), +provider-neutral remote lifecycle observations in +[PR #1002](https://github.com/codemower-ai/code-mower/pull/1002), and +persistent Board services with stale-keepalive rejection during release restart +in [PR #1001](https://github.com/codemower-ai/code-mower/pull/1001), closing +issue [#961](https://github.com/codemower-ai/code-mower/issues/961). +Issue [#951](https://github.com/codemower-ai/code-mower/issues/951)'s +integrated qualification code -- independent head-bound evidence and +session-visibility composition -- landed in +[PR #1003](https://github.com/codemower-ai/code-mower/pull/1003). +Release [#952](https://github.com/codemower-ai/code-mower/issues/952) shipped +that baseline through +[PR #1006](https://github.com/codemower-ai/code-mower/pull/1006) and is closed. +It added no cloud field. + +#951 itself stays open only for its bounded hosted Devin canary, which requires +an explicit owner authorization, has not run, and is not claimed by v1.4.2. Board is a read model over one closed local observation model. Missing or stale evidence stays explicitly unknown or last-observed; Board never infers runtime activity from a label, provider name, PID, PR author, lease, or command-line -prose. +prose. `code-mower board service` keeps one Board running as a supervised +launchd service on macOS and refuses every other platform; see +[Board Service Lifecycle](board-service-lifecycle.md). -### 4. Supervised Slack Task And Status Interaction — `v1.5.0` ([#903](https://github.com/codemower-ai/code-mower/issues/903) / release [#923](https://github.com/codemower-ai/code-mower/issues/923)) +### 4. Supervised Slack Task And Status Interaction -- active, `v1.5.0` ([#903](https://github.com/codemower-ai/code-mower/issues/903) / release [#923](https://github.com/codemower-ai/code-mower/issues/923)) + +This is the current roadmap phase. Slack is an interaction channel, not an orchestrator. A real qualified Codex or Claude supervisor controls bounded hosted work: missing supervisor readiness blocks dispatch, and selecting a provider never promotes its role. Ingress foundations [#916](https://github.com/codemower-ai/code-mower/issues/916) and -[#917](https://github.com/codemower-ai/code-mower/issues/917) are merged. +[#917](https://github.com/codemower-ai/code-mower/issues/917) are merged and +shipped in `v1.4.0`. Remaining work is OAuth, the qualified-supervisor adapter ([#977](https://github.com/codemower-ai/code-mower/issues/977)), durable interactions, the bridge, paired telemetry, setup @@ -255,24 +283,26 @@ acceptance #923. Slack consumes the durable session lifecycle and event surface rather than scraping terminal or Board output, and carries no raw private context or private reviewer findings. -This runtime work is deferred until the sequence above is complete. Board -readiness gates only Slack's end-to-end canary and final acceptance in #923; it -does not block independent Slack OAuth, inbox, interaction, bridge, setup, or -documentation work. +The preceding phases are complete, so this runtime work is no longer deferred. +Board readiness gates only Slack's end-to-end canary and final acceptance in +#923; it does not block independent Slack OAuth, inbox, interaction, bridge, +setup, or documentation work. ## Delivery Order -1. Complete `v1.4.0` stabilization on main: the seven #979 implementation PRs - plus the #974 evidence verification. -2. Graphify shipped together with those main-only fixes as `v1.4.1` through +1. **Done.** `v1.4.0` stabilization completed on main: the seven #979 + implementation PRs plus the #974 evidence verification. +2. **Done.** Graphify shipped together with those fixes as `v1.4.1` through #915, after #914. -3. Ship Board as `v1.4.2` through #952, after #961 and #951. -4. Merge the supervised Slack runtime last, accepted in #923 for `v1.5.0`. +3. **Done.** Board shipped as `v1.4.2` through #952, after #961 and #951's + merged local-evidence code. +4. **Active.** Merge the supervised Slack runtime last, to be accepted in #923 + for `v1.5.0`. Elapsed time, implementation difficulty, or an open draft PR never changes this -release order. Graphify's #915/#902 dependency is satisfied: it shipped as -`v1.4.1`. Merged post-`v1.4.0` fixes, including #935/#973, count as on main -until a later published package is verified to contain them. +release order. Merged fixes count as on main until a later published package is +verified to contain them; #935/#973 and the phase-3 Board PRs are now verified +in the published `v1.4.2` artifact. Each child issue should produce one reviewable PR with one branch writer, independent current-head review, the normal gate, and package-level validation. diff --git a/docs/early-adopter-invite-runbook.md b/docs/early-adopter-invite-runbook.md index 01a3e72c..ce23a597 100644 --- a/docs/early-adopter-invite-runbook.md +++ b/docs/early-adopter-invite-runbook.md @@ -1,7 +1,7 @@ # Early Adopter Invite Runbook -Current source target: v1.4.2. Publication and installed qualification remain -pending #952; release invitations and pinned index installs follow that acceptance. +Current release: v1.4.2, published and qualified. Release invitations and +pinned index installs target `code-mower==1.4.2`. Use this runbook for the first 5-10 friendly users before widening Code Mower to 20-50 early OSS users. diff --git a/docs/early-adopter-v05.md b/docs/early-adopter-v05.md index faa7fa39..8d784dc2 100644 --- a/docs/early-adopter-v05.md +++ b/docs/early-adopter-v05.md @@ -1,7 +1,7 @@ # Code Mower Early Adopter Guide -Current source target: v1.4.2. Publication and installed qualification remain -pending #952; release invitations and pinned index installs follow that acceptance. +Current release: v1.4.2, published and qualified. Release invitations and +pinned index installs target `code-mower==1.4.2`. This document records the historical v0.5 early-adopter product plan. The current public install path is the v1.4.2 supervised-pilot release; use diff --git a/docs/first-run-transcript.md b/docs/first-run-transcript.md index a7265d84..9fcbc0be 100644 --- a/docs/first-run-transcript.md +++ b/docs/first-run-transcript.md @@ -1,7 +1,7 @@ # First Run Transcript Historical: this transcript is pinned to the published `v1.4.0` shape and is -not updated for the current `v1.4.2` candidate. Use [Install And +not updated for the current published `v1.4.2` release. Use [Install And Bootstrap](install.md) and [Try Code Mower In 10 Minutes](try-in-10-minutes.md) for current guidance. diff --git a/docs/first-user-demo-transcript.md b/docs/first-user-demo-transcript.md index a221aab9..0b303e94 100644 --- a/docs/first-user-demo-transcript.md +++ b/docs/first-user-demo-transcript.md @@ -1,7 +1,7 @@ # First-User Demo Transcript Historical: this transcript is pinned to the published `v1.4.0` shape and is -not updated for the current `v1.4.2` candidate. Use [Quickstart](quickstart.md) +not updated for the current published `v1.4.2` release. Use [Quickstart](quickstart.md) and [First-User Install Rehearsal](first-user-install-rehearsal.md) for current guidance. diff --git a/docs/first-user-install-rehearsal.md b/docs/first-user-install-rehearsal.md index e4854ad3..99c73b13 100644 --- a/docs/first-user-install-rehearsal.md +++ b/docs/first-user-install-rehearsal.md @@ -1,8 +1,10 @@ # First-User Install Rehearsal -The v1.4.2 source candidate is not yet published or qualified. Pinned index -commands below apply after publication; candidate checks use the verified -artifact. Track acceptance in [#952](https://github.com/codemower-ai/code-mower/issues/952). +v1.4.2 is published. The pinned index commands below install the current +release; verify the exact command path and version after installing. Release +evidence is on the +[v1.4.2 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.2) +and in the [v1.4.2 qualification record](v142-qualification.md). This is the release-gate rehearsal for Code Mower's early-adopter path. It installs Code Mower into a clean virtual environment, creates a fresh toy Git diff --git a/docs/friendly-user-rollout-v05.md b/docs/friendly-user-rollout-v05.md index 0f55486c..812752d6 100644 --- a/docs/friendly-user-rollout-v05.md +++ b/docs/friendly-user-rollout-v05.md @@ -1,7 +1,7 @@ # Friendly-User Rollout Plan -Current source target: v1.4.2. Publication and installed qualification remain -pending #952; release invitations and pinned index installs follow that acceptance. +Current release: v1.4.2, published and qualified. Release invitations and +pinned index installs target `code-mower==1.4.2`. This is the operating plan for the first 5-10 friendly users before Code Mower widens to 20-50 early adopters. @@ -34,9 +34,10 @@ export CODE_MOWER_PYTHON="$(command -v python3.12)" pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.4.2 ``` -The current source candidate is `v1.4.2`, with target install spec -`code-mower==1.4.2`. Publication and installed-package qualification are pending -[#952](https://github.com/codemower-ai/code-mower/issues/952). +The current package-index release baseline is `v1.4.2`, with pinned package +install spec `code-mower==1.4.2`. Release evidence is recorded on the GitHub +release and in the first-user install rehearsal. See the +[v1.4.2 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.2). ## Invite Criteria diff --git a/docs/github-setup.md b/docs/github-setup.md index cf09a3e8..1ae88505 100644 --- a/docs/github-setup.md +++ b/docs/github-setup.md @@ -839,7 +839,7 @@ or bootstrap job should fail on warnings. ## Non-GitHub Systems -The Code Mower 1.4.2 source candidate remains GitHub-first. +The published Code Mower 1.4.2 release remains GitHub-first. GitLab is the best next source-control target because merge requests, discussions, labels, approval rules, pipelines, and API concepts map closely to diff --git a/docs/graphify-evaluation.md b/docs/graphify-evaluation.md index f046edcd..4224fd64 100644 --- a/docs/graphify-evaluation.md +++ b/docs/graphify-evaluation.md @@ -6,11 +6,26 @@ Status: **adopt, as an optional and bounded local provider**. Recorded [epic #868](https://github.com/codemower-ai/code-mower/issues/868), and carried forward to [epic #902](https://github.com/codemower-ai/code-mower/issues/902). -This document does not announce a shipped Graphify integration. Code Mower still -has no Graphify dependency, no indexer, no graph cache, and no graph provider in -any default install path. The decision records that a bounded local provider is -worth building behind the repository-context contract; it changes nothing a user -installs or runs today. +> **Historical record. Graphify has since shipped.** This page is the dated +> evaluation that produced the adopt decision on 2026-09-12, and its +> present-tense statements describe the repository as it stood *then*. The +> optional local Graphify provider shipped in `v1.4.1` and is available in the +> published `v1.4.2` release. For what exists today, read +> [Optional Graphify Setup](graphify-setup.md), +> [the revision-bound lifecycle](context-graph-lifecycle.md), and +> [bounded queries and context packets](context-graph-queries.md). The +> benchmark evidence and thresholds below are preserved as recorded and are not +> rewritten. + +As of the date above, this document did not announce a shipped Graphify +integration. Code Mower then had no Graphify dependency, no indexer, no graph +cache, and no graph provider in any default install path. The decision records +that a bounded local provider was worth building behind the repository-context +contract; it changed nothing a user installed or ran on that date. + +What is still true after shipping: Graphify remains **optional**, separately +installed into an operator-owned environment, explicitly activated, and outside +the base dependency set. ## Decision @@ -184,12 +199,14 @@ depth and node budgets, truncation reported as `truncated` plus a are validated against the bound commit's tracked tree before any of it reaches a recipient. -## Boundary +## Boundary as recorded -Graphify stays out of v1.3.1 and does not block Coworker's 1.3.0 or 1.3.1 -completion. This change adds no dependency, no background service, no provider -subscription, and no mandatory indexing step; the runtime dependency arrives, if -at all, with the implementing change under epic #902. +Graphify stayed out of v1.3.1 and did not block Coworker's 1.3.0 or 1.3.1 +completion. The change recorded here added no dependency, no background service, +no provider subscription, and no mandatory indexing step; the runtime dependency +would arrive, if at all, with the implementing change under epic #902. It did: +the optional provider shipped in `v1.4.1` and is still opt-in, with no base +dependency and no mandatory indexing step. The fixture in `tests/fixtures/local_graph_contract.json` is invented content against a generic public example tree. It proves the extension point only; it is diff --git a/docs/graphify-setup.md b/docs/graphify-setup.md index 60fcde59..39d9bb75 100644 --- a/docs/graphify-setup.md +++ b/docs/graphify-setup.md @@ -1,8 +1,25 @@ # Optional Graphify setup -Graphify is opt-in local code context. Default Claude + Codex installation adds -no Graphify dependency, indexing step, hook or background service. To inspect -the guidance from a fresh directory or your selected repository configuration: +Graphify shipped in v1.4.1 and is available in the published v1.4.2 release as +an **optional** local repository-graph provider. It is separately installed into +an operator-owned environment, explicitly activated, and outside the base +dependency set: a default Claude + Codex installation adds no Graphify +dependency, indexing step, hook or background service. + +What a graph covers, and what it does not: + +- Only the **tracked** tree of one immutable commit is indexed. Untracked and + ignored files are never written into the build, so they have no path into a + graph. +- Symlinks and submodules are skipped and recorded as skipped. +- Committed private state -- `.git`, `.graph`, `.graphify`, `graphify-out`, + `.code-mower` -- is skipped at any depth. +- Nothing **watches the working tree**. There is no hook, watcher, or background + service; a graph becomes stale the moment `HEAD` moves, and you refresh it + explicitly. + +To inspect the guidance from a fresh directory or your selected repository +configuration: ```bash code-mower init --graphify @@ -106,3 +123,98 @@ results to the consuming revision. Stale, incomplete, oversized or unresolved citations do not establish fresh context. Without a provider, ordinary setup and review remain available. Do not upload graph, query, path, citation or source content; the release scorecard contains sanitized measurements only. + +## Ramp-up in order + +Once the provider environment exists and the pin file is saved, this is the +whole path from nothing to a first answer. Each step is explicit; none of it +runs on its own. + +**1. Check the posture before building anything.** + +```bash +code-mower context-graph doctor --pin-file "$GRAPHIFY_ROOT/pin.json" +``` + +`doctor` builds nothing. It reports `skip` rather than `fail` when nothing is +pinned or built, because an operator who never opted in has nothing wrong with +their installation. + +**2. Build one generation, bound to an immutable tracked commit.** + +```bash +GRAPHIFY_REVISION="$(git rev-parse HEAD)" +code-mower context-graph build --pin-file "$GRAPHIFY_ROOT/pin.json" --indexer "$GRAPHIFY_INDEXER" --revision "$GRAPHIFY_REVISION" +``` + +The census comes from `git ls-tree -r` against that **commit** -- not the +working tree and not the index. Uncommitted edits are invisible to the build by +construction. + +**3. Confirm the published generation is usable.** + +```bash +code-mower context-graph status --json +``` + +`status` exits non-zero when the graph is not usable, so a script can branch on +it. A provider run that admitted an incomplete census publishes a generation +`status` calls `partial` and refuses, rather than describing it as `current`. + +**4. Register the graph as a local context connection.** + +```bash +code-mower context-graph connect --connection local-graph --repository owner/repo --recipient claude:builder --recipient codex:reviewer +code-mower context-graph connection-status --connection local-graph +``` + +A local connection has no principal, no workspace and no credential. Nothing is +written to the OS credential vault, no browser opens, and no endpoint is +contacted. Which repositories and recipients are approved is an authorization +decision that belongs to the connection, not to any individual query. + +**5. Ask one bounded question.** + +```bash +code-mower context-graph query --question impact --target parse_config --authorization AUTH.json --packet-out /tmp/packet.json --json +``` + +`--question` is one of `impact`, `dependency`, `symbol`, or `related_tests`. +`--target` is a symbol name or a repository-relative path. `AUTH.json` is a file +you name, carrying exactly `connection`, `policy`, `repository` and `work_item`; +see [bounded queries and context packets](context-graph-queries.md) for its +contents and for what each question traverses. Standard output is metadata only +-- counts, states, the bound revision and generation, and omission codes. The +evidence goes to the `--packet-out` file, created `0600`, or nowhere at all. + +**6. Refresh explicitly after the revision changes.** + +```bash +GRAPHIFY_REVISION="$(git rev-parse HEAD)" +code-mower context-graph refresh --pin-file "$GRAPHIFY_ROOT/pin.json" --indexer "$GRAPHIFY_INDEXER" --revision "$GRAPHIFY_REVISION" +``` + +Nothing watches the working tree, so nothing refreshes on your behalf. When +`HEAD` moves, the published generation is stale for the new revision and +authorization fails outright rather than answering today's question with +yesterday's code. `refresh` rebuilds and atomically publishes a new generation. +A packet bound to the previous generation is refused at load, which is the +intended outcome, not a regression. + +**7. Tear down when you are finished.** + +```bash +code-mower context-graph disconnect --connection local-graph +code-mower context-graph remove +``` + +`disconnect` disables the connection and drops the packets it authorized. +`remove` deletes this checkout's private graph state. The operator-owned +provider environment from +[Separate acquisition environment](#separate-acquisition-environment) is yours +to keep or delete separately; Code Mower never touches it. + +A separate open pull request, +[#1007](https://github.com/codemower-ai/code-mower/pull/1007), is working on +further Graphify real-pilot compatibility. It is not merged and not released; +nothing on this page depends on it. diff --git a/docs/install.md b/docs/install.md index 6616db2a..62cbb4cd 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,8 +1,10 @@ # Install And Bootstrap -The v1.4.2 source candidate is not yet published or qualified. Pinned index -commands below apply after publication; candidate checks use the verified -artifact. Track acceptance in [#952](https://github.com/codemower-ai/code-mower/issues/952). +v1.4.2 is published. The pinned index commands below install the current +release; verify the exact command path and version after installing. Release +evidence is on the +[v1.4.2 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.2) +and in the [v1.4.2 qualification record](v142-qualification.md). Code Mower requires Python 3.12 or newer. Use one install path per machine or agent, then verify the installed command before touching a repository. @@ -21,6 +23,27 @@ add `--apply` to write reviewable setup files. See | Hosted agent, CI box, or minimal Linux VM | `uv tool install` | The machine already uses uv, lacks pipx, or should avoid changing shell startup files. | | Code Mower contributor checkout | editable venv | You are changing Code Mower itself and need tests against this checkout. | +pipx and uv are the recommended paths because each keeps Code Mower and its +dependencies in their own isolated environment, separate from system Python and +from any other tool. Install the installer itself from its official +documentation rather than from a copied shell snippet: + +- pipx: +- uv: + +Code Mower does not publish, and you should not use, a +`curl ... | sh` bootstrap for either installer. + +Confirm the installer is on `PATH` before installing Code Mower with it: + +```bash +command -v pipx +command -v uv +``` + +An empty result means that installer is not installed or not on `PATH` for this +shell; fix that first rather than falling back to an ambient `pip install`. + ## Cold Install Vs Upgrade A cold install means this machine does not already have the `code-mower` @@ -32,6 +55,12 @@ command -v code-mower code-mower --version ``` +`command -v code-mower` must print the path belonging to the installer you +chose, and `code-mower --version` must print `code-mower 1.4.2`. A version that +does not match, or a path from a different installer, means an older command is +still winning on `PATH`; resolve that before running anything against a +repository. + An upgrade means `code-mower` already exists. Before changing it, record the current command path and version, then choose whether this machine should keep using the same installer or switch installers: @@ -205,7 +234,8 @@ uv tool install --python 3.12 --reinstall --refresh-package code-mower code-mowe code-mower --version ``` -Before PyPI has the candidate, rehearse the local wheel from a source checkout: +To rehearse an unpublished build -- a release candidate, or a local source +change -- install the local wheel from a source checkout instead of the index: ```bash scripts/dev-python -m build @@ -347,3 +377,44 @@ wrappers or unattended dispatch. Then follow [Try Code Mower In 10 Minutes](try-in-10-minutes.md) for the first audited PR or [Build Loop In 30 Minutes](build-loop-in-30-minutes.md) after the reviewer gate is working. + +## What The Install Does And Does Not Change + +Five boundaries survive install, upgrade, and reinstall. Adoption feedback keeps +returning to them, so they are stated here rather than only in the reference: + +- **`init --easy` previews and changes nothing.** `--apply` writes a reviewable + generated tree under `.code-mower.generated/`; it does not copy files into + your repository, start a provider, enable auto-merge, or upload anything. In a + checkout with no `code-mower.yml`, init falls back to the packaged starter + configuration and says so (`Config source: packaged starter ...`), rather than + failing. +- **A session lease is local, explicit, and releasable.** An eligible + `code-mower session start` takes a mutating single-orchestrator lease with a + 12-hour default. `code-mower session show --current` and `code-mower session + lease show` are read-only. A later process renews or releases the same lease + by passing the session ID: `code-mower session lease renew --session-id + SESSION_ID`, then `code-mower session lease release --session-id SESSION_ID` + after its writers stop. Use `--dry-run` for a preview or `--no-lease` for a + saved read-only brief. +- **Headless authorization has real limits.** Codex's isolated campaign home is + keyring-only. On a Linux host with no desktop session, doctor reports that as + a fact and does not recommend a keyring login there; it offers the supported + routes instead -- dispatch from a host with a desktop-session keyring, run + `doctor --hosted-builders` or `--orchestrator-only`, or set + `CODE_MOWER_CAMPAIGN_AUTH_PROBE=0` to leave the lane capability-only. Isolated + non-keyring Codex campaign authentication is tracked separately in + [#983](https://github.com/codemower-ai/code-mower/issues/983). +- **The Board is loopback-only and does not upload.** `code-mower board serve + --repo OWNER/REPO` binds a loopback host, redacts local paths by default, and + prints a URL that is local to that machine unless you build your own tunnel. + `code-mower board stop --repo OWNER/REPO` resolves exactly one known binding: + an ambiguous, duplicate, or contradicting repository/port/PID selection stops + nothing, and a port that a keepalive-managed service would immediately reclaim + is refused rather than reported as stopped. For a Board that outlives the + shell, see [Board Service Lifecycle](board-service-lifecycle.md); macOS is the + supported platform and every other platform refuses. +- **Unselected integrations stay quiet.** Ordinary no-campaign adoption asks for + no campaign-auth owner action. Optional surfaces -- Coworker, Jira Cloud, + Graphify, cloud sharing -- are opt-in and add nothing to a default + Claude + Codex install until you select them. diff --git a/docs/jira-cloud-setup.md b/docs/jira-cloud-setup.md index 693aadf7..f506061a 100644 --- a/docs/jira-cloud-setup.md +++ b/docs/jira-cloud-setup.md @@ -213,8 +213,9 @@ GitHub state only. Controller dry-run never dispatches, merges, or writes Jira. ## 4. Shared Orchestrator Contract This shared session-brief contract, the Jira REST commands, and the double -write guard described elsewhere in this guide are available in -`code-mower==1.4.0`. +write guard described elsewhere in this guide have been available since +`code-mower==1.4.0` and are present in the current `code-mower==1.4.2` +release. `code-mower session start` adds a `tracker` section to the operating brief whenever `tracker.kind` is `jira_cloud`. Codex, Claude, and every other diff --git a/docs/oss-v1-checklist.md b/docs/oss-v1-checklist.md index 310e758d..c501f6d0 100644 --- a/docs/oss-v1-checklist.md +++ b/docs/oss-v1-checklist.md @@ -46,7 +46,7 @@ history opens the repository. They should be able to confirm: ## Current v1.0 Baseline -The target public-release baseline is `v1.4.2` (publication pending #952) of the standalone +The public-release baseline is the published `v1.4.2` of the standalone package. Before widening the release, record: - non-editable package-install rehearsal in a clean venv; diff --git a/docs/provider-matrix.md b/docs/provider-matrix.md index a904c016..139c8e2f 100644 --- a/docs/provider-matrix.md +++ b/docs/provider-matrix.md @@ -28,9 +28,10 @@ agent hosting a session is the default orchestrator. | Cursor | Qualified for the same session, telemetry, and lease contract | Code Mower REST is authoritative; Atlassian MCP is optional read/context only and noninteractive reads need approved tool access such as `--auto-review` | | Devin, Grok Bot, Antigravity, Muse, custom hosts | Recognized host identities for briefs and telemetry; execution remains an explicit handoff or provider-specific transport until separately qualified | No implicit Jira authority; use the guarded Code Mower tracker commands for writes | -The lease, shared Jira brief, and explicit Cursor qualification are available -in `code-mower==1.4.0`; see [Participants And Sessions](sessions.md) for the -operating contract. +The lease, shared Jira brief, and explicit Cursor qualification have been +available since `code-mower==1.4.0` and are present in the current +`code-mower==1.4.2` release; see [Participants And Sessions](sessions.md) for +the operating contract. ## Provider Classes diff --git a/docs/public-release-checklist.md b/docs/public-release-checklist.md index 02fdf510..dc1cc1c1 100644 --- a/docs/public-release-checklist.md +++ b/docs/public-release-checklist.md @@ -1,8 +1,10 @@ # Code Mower Public Release Checklist -The v1.4.2 source candidate is not yet published or qualified. Pinned index -commands below apply after publication; candidate checks use the verified -artifact. Track acceptance in [#952](https://github.com/codemower-ai/code-mower/issues/952). +v1.4.2 is published. The pinned index commands below install the current +release; verify the exact command path and version after installing. Release +evidence is on the +[v1.4.2 release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.2) +and in the [v1.4.2 qualification record](v142-qualification.md). Use this checklist for public OSS readiness and 1.x hardening. The standalone `code-mower` repository is public; the remaining work is to make the first @@ -16,12 +18,12 @@ not know the original reference repos. - The package has public releases and reports its version with `code-mower --version`. - The current published package-index release entrypoint is - `code-mower==1.4.1` (GitHub tag `v1.4.1`), with + `code-mower==1.4.2` (GitHub tag `v1.4.2`), with `code-mower doctor --adoption --repo OWNER/REPO` as the human-facing first-run setup diagnostic and `code-mower lanes status --repo OWNER/REPO` - as the operator snapshot. The target package-index entrypoint after - v1.4.2's acceptance is `code-mower==1.4.2` (GitHub tag `v1.4.2`), tracked - by [#952](https://github.com/codemower-ai/code-mower/issues/952); + as the operator snapshot. v1.4.2 superseded `code-mower==1.4.1` (GitHub tag + `v1.4.1`) when release + [#952](https://github.com/codemower-ai/code-mower/issues/952) closed; `doctor --preflight` and `doctor --v05` remain compatibility presets for scripts. - The current supervised-pilot release includes Python 3.12+ install hardening, @@ -33,7 +35,12 @@ not know the original reference repos. multi-instance handling, setup drift reporting, explicit package-index rehearsal opt-ins, the public Board demo rehearsal, local release qualification, resumable provider campaigns, Board campaign visibility, and - opt-in metadata-only `adoption_run` cloud reporting. + opt-in metadata-only `adoption_run` cloud reporting. v1.4.2 adds the + work-first Board views, provider-neutral remote lifecycle observations, exact + local work observations, repository-aware `board stop --repo`, and the + macOS-only persistent Board service (`code-mower board service`); v1.4.1's + optional local Graphify repository-graph provider remains available and + outside the base dependency set. - The README now shows a shortened `doctor --adoption --repo OWNER/REPO` example so fresh users can see the payoff before installing. - The first-run transcript, architecture overview, cloud data contract, and diff --git a/docs/pypi-release.md b/docs/pypi-release.md index 4dcc083e..ae0c1b26 100644 --- a/docs/pypi-release.md +++ b/docs/pypi-release.md @@ -9,8 +9,9 @@ CODE_MOWER_PYTHON="$(command -v python3.12)" pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.4.2 ``` -The v1.4.2 source candidate is not yet published or qualified. All mutating -steps below require the supervisor and the recorded owner release decision. +v1.4.2 is published; the steps below are the executed record of that release +and the shape the next release follows. All mutating steps require the +supervisor and the recorded owner release decision. diff --git a/docs/quickstart.md b/docs/quickstart.md index 8cb7aea3..596e63e0 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -37,9 +37,8 @@ pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.4.2 code-mower --version ``` -`1.4.2` is the supervised-pilot source candidate; publication and installed -qualification remain pending #952. These pinned install commands apply after -publication. If you want a future prerelease instead +`1.4.2` is the published supervised-pilot release. These pinned install +commands install it today. If you want a future prerelease instead of this exact release target, use: ```bash @@ -79,8 +78,12 @@ after writers stop, release with `code-mower session lease release --session-id SESSION_ID`. Use `session start --dry-run` for a preview or `--no-lease` for a saved read-only brief. Devin orchestration is unqualified and acquires no lease. -Optional local Graphify guidance is available with `code-mower init --graphify`. -It adds no dependency, indexing or default context; see [setup](graphify-setup.md). +Graphify is a shipped optional local repository-graph provider. It is +separately installed into an operator-owned environment, explicitly activated, +and outside the base dependency set. `code-mower init --graphify` renders the +acquisition and pin guidance only: it adds no dependency, no indexing step and +no default context. See [Optional Graphify Setup](graphify-setup.md) for the +ramp-up flow. ## 2. Authenticate GitHub @@ -450,10 +453,18 @@ an explicit `--port` fails with a friendly conflict instead. The printed URL is local to that machine or VM unless you create your own tunnel. `lanes status` discovers local Board listeners best-effort across common macOS and Linux tools; if listener inventory is restricted, GitHub PR/check status still reports. -Use `code-mower board list` to see local Board listeners with repo/version and -restart hints. Use `code-mower board stop --port PORT --yes` or -`code-mower board stop --pid PID --yes` only when you want to stop a listener -that the inventory identified as a high-confidence Code Mower Board process. +Use `code-mower board list` to see local Board listeners with repo/version, +restart hints, and whether each one is managed or transient. Use +`code-mower board stop --repo OWNER/REPO --yes`, `code-mower board stop --port +PORT --yes`, or `code-mower board stop --pid PID --yes` only when you want to +stop a listener that the inventory identified as a high-confidence Code Mower +Board process. Selectors must agree: an ambiguous, duplicate, or contradicting +repository/port/PID selection stops nothing, and a port that a keepalive-managed +service would immediately reclaim is refused instead of being reported as +stopped. To keep one Board running across logout and reboot, install it as a +persistent service -- macOS only, through launchd -- with +`code-mower board service`; see +[Board Service Lifecycle](board-service-lifecycle.md). Visible Board timestamps render in the browser's local timezone and keep the original UTC value in hover text for precise handoffs. The Board header shows the Code Mower version currently serving the page and diff --git a/docs/release-history.md b/docs/release-history.md index f02878ac..78f7a4b7 100644 --- a/docs/release-history.md +++ b/docs/release-history.md @@ -11,8 +11,10 @@ guidance; use [Install And Bootstrap](install.md) instead. ## Current Release Line -- [v1.4.2 source candidate notes](v142-release-notes.md) (publication/qualification pending #952) -- [v1.4.1 source candidate notes](v141-release-notes.md) (published) +- [v1.4.2 release notes](v142-release-notes.md) (published; current baseline) +- [v1.4.2 qualification record](v142-qualification.md) +- [v1.4.1 release notes](v141-release-notes.md) (published) +- [v1.4.1 qualification record](v141-qualification.md) - [v1.4.0 release notes](v140-release-notes.md) - [v1.3.1 release notes](v131-release-notes.md) - [v1.3.1 guided context qualification](v131-guided-context-qualification.md) diff --git a/docs/sessions.md b/docs/sessions.md index 3e73eb99..971fdec7 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -6,10 +6,10 @@ your conversation is the default orchestrator when its role is eligible. The participant picker, host-led session brief, single-orchestrator lease, shared Jira tracker brief, controller host telemetry, and explicit Cursor -qualification documented below are included in the `code-mower==1.4.2` source candidate. -Install from the matching tag when following release documentation, or use a +qualification documented below are included in the published `code-mower==1.4.2` release. +Install that pin when following release documentation, or use a contributor checkout when testing later source changes. Role-specific admission -and startup lease commands described here are included in this candidate; the published `v1.4.0` artifacts remain unchanged. +and startup lease commands described here are included in the same release; the published `v1.4.0` artifacts remain unchanged. ## Choose During Setup diff --git a/docs/try-in-10-minutes.md b/docs/try-in-10-minutes.md index 755466d1..f05cc7d4 100644 --- a/docs/try-in-10-minutes.md +++ b/docs/try-in-10-minutes.md @@ -48,9 +48,8 @@ For a repository that already has generated Code Mower support, follow [Upgrade An Existing Repository](upgrade-existing-repo.md) before copying a new `.code-mower.generated` tree. -`1.4.2` is the supervised-pilot source candidate; publication and installed -qualification remain pending #952. These pinned install commands apply after -publication. To follow a future prerelease line +`1.4.2` is the published supervised-pilot release. These pinned install +commands install it today. To follow a future prerelease line instead of pinning this exact build: ```bash @@ -91,8 +90,9 @@ with `code-mower session lease renew --session-id SESSION_ID` if work continues; for a preview or `--no-lease` for a saved read-only brief. Participant selection alone does not qualify an orchestrator; Devin requires a qualified supervisor. Role admission and exact startup lease commands shipped in the published -`v1.4.1` artifact; the v1.4.2 candidate inherits them unchanged and adds only -the Board clarity work described in [candidate notes](v142-release-notes.md). +`v1.4.1` artifact; the published `v1.4.2` release inherits them unchanged and +adds only the Board clarity work described in +[the v1.4.2 release notes](v142-release-notes.md). These commands do not alter the published `v1.4.0` artifact. Run this from a clean checkout of the repository you want to pilot: diff --git a/docs/v141-qualification.md b/docs/v141-qualification.md index 71ef490c..811a0ae2 100644 --- a/docs/v141-qualification.md +++ b/docs/v141-qualification.md @@ -1,7 +1,14 @@ # v1.4.1 qualification and evidence matrix -This is an unexecuted release procedure. The Codex source PR owns source changes -only. The supervisor runs the canonical full suite once, the independent exact +> **v1.4.1 is a completed release.** This page preserves the prepublication +> procedure written before release #915 ran, including its then-`Pending` +> outcome column. It is kept as the historical record rather than rewritten. +> Final v1.4.1 evidence is on the +> [v1.4.1 GitHub release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.1); +> the current release is [v1.4.2](v142-qualification.md). + +This was an unexecuted release procedure when written. The Codex source PR owns +source changes only. The supervisor runs the canonical full suite once, the independent exact current-head Claude review and CI/`code-mower/gate`, then serializes merge and owner-authorized release operations. Preserve v1.4.0 artifacts and historical campaign truth. Do not create paid hosted Devin sessions from an old runbook. diff --git a/docs/v141-release-notes.md b/docs/v141-release-notes.md index 0d963022..5b4f6900 100644 --- a/docs/v141-release-notes.md +++ b/docs/v141-release-notes.md @@ -1,8 +1,19 @@ # Code Mower v1.4.1 Release Notes -Status: source candidate prepared for #915. This document is not a publication, -installed-package qualification, comparative scorecard or freshness claim. -v1.4.0 tags, assets, release notes and its historical runbook remain immutable. +> **v1.4.1 is a completed release.** This page is preserved as the +> source-candidate note it was when it was written for release #915, which has +> since closed. The prepublication procedure and boundaries below are kept +> verbatim as the historical record; they are not current instructions. For the +> current release see the [v1.4.2 release notes](v142-release-notes.md) and the +> [v1.4.2 qualification record](v142-qualification.md), and for what v1.4.1 +> actually proved see the +> [v1.4.1 qualification record](v141-qualification.md) and the +> [v1.4.1 GitHub release](https://github.com/codemower-ai/code-mower/releases/tag/v1.4.1). + +Status as written: source candidate prepared for #915. At that time this +document was not a publication, installed-package qualification, comparative +scorecard or freshness claim. v1.4.0 tags, assets, release notes and its +historical runbook remain immutable. ## Optional local Graphify context diff --git a/docs/v142-qualification.md b/docs/v142-qualification.md index 9d10431a..5739dd62 100644 --- a/docs/v142-qualification.md +++ b/docs/v142-qualification.md @@ -1,56 +1,69 @@ # v1.4.2 qualification and evidence matrix -This is an unexecuted release procedure. The Claude source PR owns source -changes only. The orchestrator runs the canonical full suite once, an -independent exact current-head Codex audit and CI/`code-mower/gate`, then -serializes merge and owner-authorized release operations. Preserve v1.4.0 and -v1.4.1 artifacts and historical campaign truth. Do not create paid hosted -Devin sessions from an old runbook. +This is the **final** qualification record for the published v1.4.2 release. +It replaces the unexecuted procedure that this page carried before the release +ran. The v1.4.0 and v1.4.1 artifacts and their historical campaign truth are +preserved and unchanged. -Record these as separate states on #952 and parent #945/#900: +> The `v1.4.2` tag carries the prepublication snapshot of this page. The tag is +> immutable and is not rewritten. This copy on `main` is the record of what +> actually happened. -| Boundary | Required evidence | Current source-writer outcome | +Release identity: + +| Item | Value | +| --- | --- | +| Release commit | `55339bf1acf76d33be5937e80bdaad772e0b2bf5` | +| Tag | annotated `v1.4.2`, resolving to that exact commit | +| Release PR | #1006, audited at head `32706cf5a01af5863d6c713b83dfb196efde6d4c` | +| Wheel SHA-256 | `f8bf24dd8a982ed5ab28302e837cd5d2aeece6d984ed1c44fcb4688c3fb7a522` | +| Sdist SHA-256 | `aff202eea9748ab3734ea6b90ba3b48ea5aea1e21ae87fba03b77b64d5cdec42` | + +## Outcomes + +Only outcomes with observed evidence are marked PASS. Everything else says what +was not run, or was not required for publication, rather than being softened +into a pass. + +| Boundary | Required evidence | Outcome | | --- | --- | --- | -| Source | PR/head, sole Claude writer, independent qualified Codex excluded from diff contributors, focused/full tests and exact-head CI/gate | Focused results in PR; remaining orchestrator gates pending | -| Candidate | Fresh clone at reviewed merge commit; package matrix, privacy, release readiness, no-publish workflow, wheel/sdist names and SHA-256 digests | Pending | -| Required inclusion | Inspect actual artifacts for #999/#1000/#1001/#1002/#1003, and confirm no new cloud event fields | Merged baseline is not artifact evidence | -| #951 boundary | Merged local-evidence code accepted; bounded hosted Devin canary still pending; this release does not claim the hosted result or close #951 | Pending; record separately from source acceptance | -| Headless candidate | Cold install and upgrade from v1.4.1 on Linux with supported uv/Python 3.12; CLI/wrapper/pin/serving Board version agreement after restart of the Board processes named in #952 | Pending; record OS/architecture, credential posture, and the exact reconciled Board inventory | -| Publication | Exact reviewed merge SHA, annotated tag, workflow/run identity and expected head, canonical PyPI names/digests matching approved artifacts | Pending; no source-only publication claim | -| Installed published | Repeat cold/upgrade, Board doctor and restart checks for every reconciled Board process using downloaded canonical package | Pending; no checkout substitution | -| Metadata upload | Dry-run-first preview of already-allowlisted campaign/Board metadata; CodeMower.com accepts only allowlisted fields; no new field required | Pending; preview only, no apply from this source PR | -| Cloud | Metadata-only preview, stored receipt, then fresh authenticated aggregate visibility observed separately (#974/#976) | Pending; stale view requires follow-up, not a pass | +| Source | PR/head, sole Claude writer, independent qualified Codex excluded from diff contributors, focused/full tests and exact-head CI/gate | **PASS** -- independent Codex audit of PR #1006 head `32706cf5a01af5863d6c713b83dfb196efde6d4c` returned P0=0, P1=0, P2=0 ([evidence](https://github.com/codemower-ai/code-mower/pull/1006#issuecomment-5709758094)); full PR CI [run 35188065537](https://github.com/codemower-ai/code-mower/actions/runs/35188065537) passed | +| Candidate | Fresh clone at reviewed merge commit; package matrix, privacy, release readiness, no-publish workflow, wheel/sdist names and SHA-256 digests | **PASS** -- fresh-clone build, release-readiness and `twine check` passed; digests above | +| Required inclusion | Inspect actual artifacts for PRs #999/#1000/#1001/#1002/#1003, and confirm no new cloud event field | **PASS** -- inspected in the built artifacts; no new cloud event field | +| #951 boundary | Merged local-evidence code accepted; bounded hosted Devin canary separately observed | **PARTIAL, as designed** -- merged local-evidence code is included and accepted. The hosted Devin canary was **not run**: it requires an explicit 1-ACU owner authorization. This release claims no hosted result and does not close #951 | +| Headless candidate | Cold install and upgrade from v1.4.1 with supported uv/Python 3.12; CLI/wrapper/pin/serving Board version agreement after restart | **PASS** -- cold install and an isolated 1.4.1-to-1.4.2 upgrade both passed with preserved local configuration | +| Publication | Exact reviewed merge SHA, annotated tag, workflow/run identity and expected head, canonical PyPI names/digests matching approved artifacts | **PASS** -- production publish [run 35189302150](https://github.com/codemower-ai/code-mower/actions/runs/35189302150); tag resolves to the release commit; canonical PyPI digests match the approved artifacts | +| Release-triggered verification | Release-event workflow identity and job posture | **PASS** -- [run 35189721623](https://github.com/codemower-ai/code-mower/actions/runs/35189721623) passed. Its publication jobs were **intentionally skipped** by repository variables because the prior explicit publish workflow had already completed; that skip is the expected posture, not a failure | +| Installed published | Repeat cold/upgrade and Board checks for every reconciled Board process using the downloaded canonical package | **PASS** -- TestPyPI and production PyPI installs passed; both known local Board listeners were restarted and verified serving installed 1.4.2 | +| Metadata upload | Dry-run-first preview of already-allowlisted campaign/Board metadata | **Not required for publication.** No new field was needed and none was added | +| Cloud aggregate freshness | Fresh authenticated aggregate visibility observed separately | **Not claimed.** No cloud metadata or aggregate check is claimed as evidence for this release | -For the candidate and canonical published packages, exercise fresh and -explicit repositories exactly as documented for prior releases; ordinary -no-campaign adoption adds no campaign-auth owner action, and unselected -integrations stay quiet. +## Board restart verification -## Board doctor and multi-service restart verification +A read-only `code-mower board list --json` observed +two live local Board services before the release: port 5332 +(`codemower-ai/code-mower`) and one additional private-repository port. Each +port's exact posture -- launchd-managed via #961, or a transient process -- was +classified from `code-mower board service status --json` rather than assumed, +and a managed port was restarted with `code-mower board service restart +--replace` rather than stop/serve so its supervision was never downgraded. After +the restart, every port reported `serving == installed == 1.4.2` with preserved +repositories and stores. #961's managed persistent-service semantics and stale +keepalive rejection applied unchanged. -`code-mower board doctor` must pass against each Board process named in -#952's target inventory. A read-only `code-mower board list --json` observed -two live local Board services pre-release: port 5332 (`codemower-ai/code-mower`) -and one additional private-repository port. Each port's exact posture -- -launchd-managed via #961, or a transient process -- is classified from -`code-mower board service status --json`, never assumed, and a managed port -is restarted with `code-mower board service restart --replace` rather than -stop/serve so its supervision is never downgraded. Every port must report -`serving == installed == 1.4.2` with preserved repositories/stores after -this restart. #961's managed persistent-service semantics and stale -keepalive rejection apply unchanged; this source PR prepares the -verification guidance but does not restart installed services. +Exact local paths and the private repository's name stay local. They are not +recorded here and must not be added. ## Installed lineage replay without source substitution -`tests/test_lineage_producer_artifacts.py` retains the installed lineage -hooks exercised for prior releases. Set `CODE_MOWER_QUALIFICATION_WHEEL` to -the absolute path of the exact verified downloaded candidate or canonical -published wheel to bypass local building. Run from the reviewed test harness -with its dependencies available and temporary state outside every Git -repository (not an alternate product source): +`tests/test_lineage_producer_artifacts.py` retains the installed lineage hooks +exercised for prior releases. Set `CODE_MOWER_QUALIFICATION_WHEEL` to the +absolute path of the exact verified canonical published wheel to bypass local +building. Run from the reviewed test harness with its dependencies available and +temporary state outside every Git repository (not an alternate product source): ```bash +VERIFIED_WHEEL="$(cd "$(dirname "$VERIFIED_WHEEL")" && pwd -P)/$(basename "$VERIFIED_WHEEL")" CODE_MOWER_QUALIFICATION_WHEEL="$VERIFIED_WHEEL" PYTHONPATH=tests \ python -m unittest \ test_lineage_producer_artifacts.ArtifactTests.test_real_rendered_workflow_and_runner_failure_rows \ @@ -58,10 +71,17 @@ CODE_MOWER_QUALIFICATION_WHEEL="$VERIFIED_WHEEL" PYTHONPATH=tests \ test_release_v142.InstalledPromptPackTests ``` +The first line canonicalizes the wheel path. On macOS the system temporary +directory is reached through the `/var` -> `/private/var` symlink, so an +uncanonicalized path and a resolved one name the same file under two spellings. +The harness itself now canonicalizes its own temporary root and both sides of +every installed-module provenance assertion, so the replay is portable there; +canonicalizing the wheel path keeps the recorded artifact identity unambiguous +too. + Bind the tested artifact's name/digest and harness revision to the release -record. Never replace product modules with checkout files, use editable -installs as published-package evidence, replay old receipts, or treat zero -observed usage as settled billing. Record failed preliminary runs and -corrected causes honestly. Raw logs and account/provider bindings stay in -authorized local evidence; publish only sanitized counts/outcomes and -allowlisted metadata. +record. Never replace product modules with checkout files, use editable installs +as published-package evidence, replay old receipts, or treat zero observed usage +as settled billing. Record failed preliminary runs and corrected causes +honestly. Raw logs and account/provider bindings stay in authorized local +evidence; publish only sanitized counts/outcomes and allowlisted metadata. diff --git a/docs/v142-release-notes.md b/docs/v142-release-notes.md index a2e71704..1963ed43 100644 --- a/docs/v142-release-notes.md +++ b/docs/v142-release-notes.md @@ -1,64 +1,91 @@ # Code Mower v1.4.2 Release Notes -Status: source candidate prepared for #952. This document is not a publication, -installed-package qualification, comparative scorecard or freshness claim. -v1.4.0 and v1.4.1 tags, assets, release notes and their historical runbooks -remain immutable. - -## Board clarity included in the source candidate - -This candidate packages the accepted #945/#900 Board clarity work already on -`main`: the work-first Now/Timeline/Releases/Health views (#1000), the -provider-neutral remote lifecycle observations (#1002), managed persistent -Board services with stale-keepalive rejection during release restart (#1001), -exact local work observations (#999) and the qualified independent head-bound -evidence and session-visibility composition (#951, merged via #1003). It adds -no cloud event fields; Slack-specific and hosted-cloud mappings remain #921. - -## What this source PR prepares - -- Version, changelog, release notes and current docs updated for v1.4.2. -- Package/release qualification contracts and the post-merge runbook moved - forward to bind the exact v1.4.2 release commit, tag and artifacts. -- Cold-install and 1.4.1-to-1.4.2 upgrade rehearsal coverage, installed- - version/Board doctor and multi-service restart verification guidance for - the Board processes named in #952, pending exact-inventory reconciliation. -- Dry-run-first allowlisted metadata upload guidance; no new field is - required for release, and no upload is applied by this source change. - -## Remaining boundaries carried over from v1.4.1 - -Repository-aware `board stop --repo` landed via #961 and is exercised by -#951's local qualification; installed-version agreement across the two -observed local Board processes named in #952 (port 5332, -`codemower-ai/code-mower`, plus one additional private-repository port) is -verified as part of the post-merge runbook, not by this -source PR, and each port's managed-versus-transient posture is classified -from `code-mower board service status` rather than assumed. Isolated -non-keyring Codex campaign authentication remains #983. -No new paid hosted Devin session is authorized by this release procedure. +Status: **published**. v1.4.2 is the current package-index release baseline, +with pinned package install spec `code-mower==1.4.2`. It was published from +release commit `55339bf1acf76d33be5937e80bdaad772e0b2bf5` under the annotated +`v1.4.2` tag. The v1.4.0 and v1.4.1 tags, assets, release notes and their +historical runbooks remain immutable and unchanged by this release. + +> The copies of this page and of the +> [qualification record](v142-qualification.md) that the immutable `v1.4.2` +> tag carries are the prepublication source-candidate snapshots, written +> before the release ran. The tag is not rewritten. These pages on `main` are +> the final release and qualification record; read them rather than the +> tag-pinned copies when you want the outcome. + +## What shipped in v1.4.2 + +This release delivers the accepted #945/#900 Board clarity work: + +- the work-first Now/Timeline/Releases/Health views (PR #1000); +- provider-neutral remote lifecycle observations (PR #1002); +- managed persistent Board services with stale-keepalive rejection during a + release restart, closing issue #961 (PR #1001); +- exact local work observations (PR #999); and +- the qualified independent head-bound evidence and session-visibility + composition from issue #951's merged local-evidence code (PR #1003). + +No new cloud event field was added. Slack-specific and hosted-cloud mappings +remain #921. Graphify, shipped in v1.4.1, is unchanged here: still separately +installed, explicitly activated, and outside the base dependency set. + +## Release evidence + +| Item | Value | +| --- | --- | +| Release commit | `55339bf1acf76d33be5937e80bdaad772e0b2bf5` | +| Tag | annotated `v1.4.2`, resolving to that exact commit | +| GitHub release | | +| Independent audit | Codex audit of PR #1006 head `32706cf5a01af5863d6c713b83dfb196efde6d4c`, PASS with P0=0, P1=0, P2=0 ([evidence](https://github.com/codemower-ai/code-mower/pull/1006#issuecomment-5709758094)) | +| Full PR CI | [run 35188065537](https://github.com/codemower-ai/code-mower/actions/runs/35188065537) | +| Production publish | [run 35189302150](https://github.com/codemower-ai/code-mower/actions/runs/35189302150) | +| Release-triggered verification | [run 35189721623](https://github.com/codemower-ai/code-mower/actions/runs/35189721623) | +| Wheel SHA-256 | `f8bf24dd8a982ed5ab28302e837cd5d2aeece6d984ed1c44fcb4688c3fb7a522` | +| Sdist SHA-256 | `aff202eea9748ab3734ea6b90ba3b48ea5aea1e21ae87fba03b77b64d5cdec42` | + +The publication jobs inside the release-triggered verification run were +skipped on purpose: the repository publish variables were off, because the +prior explicit publish workflow had already completed. A skipped publish job +there is the expected outcome, not a failed one. + +## What was verified after publication + +- Fresh-clone build, release-readiness and `twine check`. +- TestPyPI and production PyPI installs of the canonical artifacts. +- Cold install, and an isolated 1.4.1-to-1.4.2 upgrade that preserved existing + local configuration. +- Both known local Board listeners were restarted from the release and verified + serving the installed 1.4.2: port 5332 (`codemower-ai/code-mower`) and one + additional private-repository Board. Each port was restarted according to its + own classified managed-or-transient posture rather than a blind stop/serve. + +The [v1.4.2 qualification record](v142-qualification.md) lists each boundary, +what evidence was required, and which outcomes were proven versus not run. + +## Boundaries that stay open + +- **#951's bounded hosted Devin canary.** #951's merged code evidence is in + this release. Its hosted canary requires an explicit 1-ACU owner + authorization, has not been run, and is not claimed here. #951 stays open for + that canary alone. +- **Isolated non-keyring Codex campaign authentication** remains #983. +- **Slack worker delivery** remains an ingress foundation only; the supervised + Slack runtime is the `v1.5.0` phase tracked by #903 / #923. +- No new paid hosted Devin session was authorized by this release. The privacy boundary is unchanged. Upload only the maintained metadata allowlist, never credentials, source, diffs, prompts, transcripts, private -paths, task prose, graphs, queries, citations or raw provider output. Stored -receipts and fresh authenticated aggregate visibility require separate -release-specific evidence. - -## #951 status carried into this release - -#951's merged code evidence (head-bound evidence composition, deterministic -regression cases, sanitized qualification scorecard) is accepted on `main`. -Its bounded hosted Devin canary is still pending; this release PR prepares -and checks the canary contract but does not claim the hosted result or close -#951. - -## Acceptance still required - -The orchestrator owns the canonical full suite, independent exact-head Codex -audit, CI/gate, merge, annotated tag, no-publish build, publication and all -installed-package/campaign/Board/cloud acceptance. Bind actual wheel/sdist -filenames, digests, inspected contents and installed behavior to the reviewed -release commit. Source inclusion alone does not prove published inclusion. -Follow the [v1.4.2 evidence matrix](v142-qualification.md) and -[current runbook](pypi-release.md); keep #952 open until every criterion -passes, and keep #951 open until its hosted Devin canary is observed. +paths, task prose, graphs, queries, citations or raw provider output. + +## Installing this release + +```bash +python3.12 --version +export CODE_MOWER_PYTHON="$(command -v python3.12)" +pipx install --python "$CODE_MOWER_PYTHON" code-mower==1.4.2 +command -v code-mower +code-mower --version +``` + +See [Install And Bootstrap](install.md) for the uv, contributor, upgrade and +optional Coworker paths. diff --git a/examples/board-demo/README.md b/examples/board-demo/README.md index 1ec7bd5a..1972b111 100644 --- a/examples/board-demo/README.md +++ b/examples/board-demo/README.md @@ -21,7 +21,7 @@ From the repository root: code-mower board events --store-path examples/board-demo/board-events.jsonl ``` -To open the local browser Board with the same sample history and spend data: +To serve the local browser Board with the same sample history and spend data: ```bash code-mower board serve \ @@ -30,6 +30,17 @@ code-mower board serve \ --spend-path examples/board-demo/reviewer-spend.json ``` +This starts a loopback server and prints the URL. It does not open a browser. +Add `--open` if you want Code Mower to launch your browser at that URL: + +```bash +code-mower board serve \ + --repo example/widget-service \ + --store-path examples/board-demo/board-events.jsonl \ + --spend-path examples/board-demo/reviewer-spend.json \ + --open +``` + The live GitHub panel may say the demo repository is unavailable. That is fine: the Recent Local History, Reviewer Verdict Timeline, and Spend And Latency panels still show the sample local artifacts. diff --git a/src/code_mower/release_readiness.py b/src/code_mower/release_readiness.py index 95acb7a5..30c9d164 100644 --- a/src/code_mower/release_readiness.py +++ b/src/code_mower/release_readiness.py @@ -104,6 +104,24 @@ def _read_text_if_exists(path: Path) -> str: return "" +def _links_to_repository_doc(markdown: str, label: str, relative_path: str) -> bool: + """Whether ``markdown`` links ``label`` at ``relative_path``. + + README.md is also the built package's long description, where a relative + destination resolves against the package index rather than the repository, + so repository links there are absolute GitHub URLs. Both spellings satisfy + this check; only the label and the file it lands on are required. + """ + + pattern = re.compile( + r"\[" + re.escape(label) + r"\]\(\s*]+)>?[^)]*\)" + ) + for destination in pattern.findall(markdown): + if destination.partition("#")[0].rstrip("/").endswith(relative_path): + return True + return False + + def _python_package_version(repo_path: Path) -> str: init_text = _read_text_if_exists(repo_path / "src" / "code_mower" / "__init__.py") match = re.search(r"__version__\s*=\s*[\"']([^\"']+)[\"']", init_text) @@ -1762,10 +1780,13 @@ def render_release_readiness(repo_path: Path) -> dict[str, Any]: title="Public support, security, and conduct docs are linked from README", status=( "pass" - if ( - "[Support](SUPPORT.md)" in docs.get("README.md", "") - and "[Security Policy](SECURITY.md)" in docs.get("README.md", "") - and "[Code of Conduct](CODE_OF_CONDUCT.md)" in docs.get("README.md", "") + if all( + _links_to_repository_doc(docs.get("README.md", ""), label, relative_path) + for label, relative_path in ( + ("Support", "SUPPORT.md"), + ("Security Policy", "SECURITY.md"), + ("Code of Conduct", "CODE_OF_CONDUCT.md"), + ) ) else "fail" ), diff --git a/templates/lanes/README.md b/templates/lanes/README.md index 061f5a14..7f28e50b 100644 --- a/templates/lanes/README.md +++ b/templates/lanes/README.md @@ -33,7 +33,8 @@ Dispatcher rules: Operational checks: - `__DISPATCH_TOKEN_ENV__` should be a human-owned fine-grained PAT or delegated machine user token with Issues read/write and Pull requests read/write. -- `__DISPATCH_TOKEN_EXPIRES_VAR__` should hold the token expiry date as `YYYY-MM-DD`. +- `__DISPATCH_TOKEN_EXPIRES_VAR__` should hold the token expiry date as + `YYYY-MM-DD`, or `never` for a non-expiring token. - Set `CODE_MOWER_MAX_WIP` to override the default WIP cap. - Set `__LANE_MAC_RUNNER_ENABLED_VAR__=true` only after the self-hosted Mac runner has the configured labels and the lane CLIs are authenticated for the runner user. diff --git a/tests/test_lineage_producer_artifacts.py b/tests/test_lineage_producer_artifacts.py index e47f41b4..3f97977f 100644 --- a/tests/test_lineage_producer_artifacts.py +++ b/tests/test_lineage_producer_artifacts.py @@ -90,7 +90,11 @@ def module_source_hashes(self, path): def setUpClass(cls): cls.tmp = tempfile.TemporaryDirectory() cls.addClassCleanup(cls.tmp.cleanup) - cls.root = Path(cls.tmp.name) + # Canonicalize once. The subprocess provenance assertions below compare a + # resolved module path against this root, and macOS hands out temporary + # directories under the /var -> /private/var symlink, so an unresolved + # root would fail those assertions for the wrong reason. + cls.root = Path(cls.tmp.name).resolve() cls.wheels = cls.root / "wheels" cls.installed = cls.root / "installed" supplied_wheel = os.environ.get("CODE_MOWER_QUALIFICATION_WHEEL") @@ -117,7 +121,7 @@ def setUpClass(cls): from unittest.mock import patch sys.path.insert(0, {str(cls.installed)!r}) import code_mower.builder_lineage_producer as producer -assert Path(producer.__file__).resolve().is_relative_to(Path({str(cls.installed)!r})) +assert Path(producer.__file__).resolve().is_relative_to(Path({str(cls.installed)!r}).resolve()) assert {str(ROOT / 'src')!r} not in sys.path class Clock(datetime.datetime): @classmethod @@ -182,7 +186,7 @@ def materialized(self, relative): sys.meta_path = [f for f in sys.meta_path if '__editable__' not in str(f)] sys.path.insert(0, sys.argv[1]) from code_mower import init -assert Path(init.__file__).resolve().is_relative_to(Path(sys.argv[1])) +assert Path(init.__file__).resolve().is_relative_to(Path(sys.argv[1]).resolve()) source = Path(sys.argv[1]) / 'code_mower/templates' / sys.argv[2] print(init._render_workflow_template(source.read_text(), {}), end='') """ @@ -335,7 +339,7 @@ def test_published_140_refuses_emitted_artifact_before_any_effect(self): from pathlib import Path sys.path.insert(0, {str(legacy)!r}) import code_mower -assert Path(code_mower.__file__).resolve().is_relative_to(Path({str(legacy)!r})) +assert Path(code_mower.__file__).resolve().is_relative_to(Path({str(legacy)!r}).resolve()) assert {str(ROOT/'src')!r} not in sys.path exec(compile(sys.stdin.read(), '', 'exec')) ''' @@ -380,7 +384,7 @@ def test_installed_candidate_supervisor_and_public_readback_business(self): sys.path.insert(0, {str(self.installed)!r}) sys.path.insert(1, {str(ROOT/'tests')!r}) import code_mower.lane_delivery as delivery -assert Path(delivery.__file__).resolve().is_relative_to(Path({str(self.installed)!r})) +assert Path(delivery.__file__).resolve().is_relative_to(Path({str(self.installed)!r}).resolve()) from test_lineage_consumer_activation import ProducerActivation suite = unittest.TestSuite(ProducerActivation(name) for name in ( 'test_actual_entrypoint_takeover_continuation_then_third_writer', diff --git a/tests/test_release_hygiene.py b/tests/test_release_hygiene.py index 1874bc6b..e3f28baf 100644 --- a/tests/test_release_hygiene.py +++ b/tests/test_release_hygiene.py @@ -10877,9 +10877,9 @@ def test_public_support_docs_are_packaged_and_privacy_forward(self) -> None: self.assertIn("include CODE_OF_CONDUCT.md", manifest) self.assertIn("include SUPPORT.md", manifest) - self.assertIn("[Support](SUPPORT.md)", readme) - self.assertIn("[Security Policy](SECURITY.md)", readme) - self.assertIn("[Code of Conduct](CODE_OF_CONDUCT.md)", readme) + self.assertIn("[Support](https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md)", readme) + self.assertIn("[Security Policy](https://github.com/codemower-ai/code-mower/blob/main/SECURITY.md)", readme) + self.assertIn("[Code of Conduct](https://github.com/codemower-ai/code-mower/blob/main/CODE_OF_CONDUCT.md)", readme) for text in (support, conduct): lowered = text.lower() self.assertIn("private source", lowered) @@ -10997,7 +10997,10 @@ def test_public_release_baseline_helpers_derive_announcement_links(self) -> None ) def test_public_announcement_docs_use_current_release_helpers(self) -> None: - baseline_sentence = "The current source candidate is `v1.4.2`, with target install spec `code-mower==1.4.2`." + # v1.4.2 is published, so the shared published-baseline helper is the + # sentence these pages carry. Deriving it here keeps the docs and the + # helper from drifting apart at the next release. + baseline_sentence = code_mower_versioning.public_baseline_sentence(__version__) package_spec = code_mower_versioning.public_package_spec(__version__) announcement_url = code_mower_versioning.tagged_doc_url(__version__) @@ -11049,14 +11052,14 @@ def test_public_docs_match_current_commands_and_privacy_boundary(self) -> None: encoding="utf-8" ) self.assertIn("Documentation on `main` follows the source on `main`", readme) - self.assertIn("included in the `code-mower==1.4.2` source candidate", sessions) + self.assertIn("included in the published `code-mower==1.4.2` release", sessions) self.assertIn("# Code Mower v1.4.2 Release Notes", release_notes) self.assertIn("The privacy boundary is unchanged.", release_notes) release_history = (ROOT / "docs" / "release-history.md").read_text( encoding="utf-8" ) self.assertIn( - "[Release History And Archived Plans](docs/release-history.md)", + "[Release History And Archived Plans](https://github.com/codemower-ai/code-mower/blob/main/docs/release-history.md)", readme, ) self.assertLess( @@ -11072,11 +11075,11 @@ def test_public_docs_match_current_commands_and_privacy_boundary(self) -> None: release_history.index("[v1.2.0 release notes](v12-release-notes.md)"), ) self.assertLess( - release_history.index("[v1.4.2 source candidate notes](v142-release-notes.md)"), - release_history.index("[v1.4.1 source candidate notes](v141-release-notes.md)"), + release_history.index("[v1.4.2 release notes](v142-release-notes.md)"), + release_history.index("[v1.4.1 release notes](v141-release-notes.md)"), ) self.assertLess( - release_history.index("[v1.4.1 source candidate notes](v141-release-notes.md)"), + release_history.index("[v1.4.1 release notes](v141-release-notes.md)"), release_history.index("[v1.3.1 release notes](v131-release-notes.md)"), ) @@ -11099,17 +11102,12 @@ def test_current_release_docs_record_package_index_procedure(self) -> None: encoding="utf-8", ) - current_status = "The current source candidate is `v1.4.2`, with target install spec `code-mower==1.4.2`." + current_status = code_mower_versioning.public_baseline_sentence(__version__) for text in (readme, current_state, rollout): self.assertIn(current_status, " ".join(text.split())) self.assertIn( "The current published package-index release entrypoint is\n" - " `code-mower==1.4.1` (GitHub tag `v1.4.1`)", - public_release, - ) - self.assertIn( - "The target package-index entrypoint after\n" - " v1.4.2's acceptance is `code-mower==1.4.2` (GitHub tag `v1.4.2`)", + " `code-mower==1.4.2` (GitHub tag `v1.4.2`)", public_release, ) self.assertIn("The current supervised-pilot release includes", public_release) @@ -11119,7 +11117,7 @@ def test_current_release_docs_record_package_index_procedure(self) -> None: ) self.assertIn( - "The target public-release baseline is `v1.4.2`", + "The public-release baseline is the published `v1.4.2`", oss_checklist, ) self.assertIn( @@ -11258,7 +11256,7 @@ def test_readme_describes_calibration_limits_and_roles(self) -> None: "It does not prove that a reviewer should gate merges.", normalized_readme, ) - self.assertIn("[lane promotion policy](docs/lane-promotion-policy.md)", readme) + self.assertIn("[lane promotion policy](https://github.com/codemower-ai/code-mower/blob/main/docs/lane-promotion-policy.md)", readme) self.assertIn("## Start Here", readme) self.assertIn("supervised-pilot, bring-your-own-agent-loop software", normalized_readme) self.assertIn("not a drop-in unattended merge gate", normalized_readme) @@ -11266,7 +11264,7 @@ def test_readme_describes_calibration_limits_and_roles(self) -> None: self.assertIn("Claude Code, Codex, Cursor-style", readme) self.assertIn("supervised issue-to-merge loop end to end", normalized_readme) self.assertIn( - "[Release History And Archived Plans](docs/release-history.md)", + "[Release History And Archived Plans](https://github.com/codemower-ai/code-mower/blob/main/docs/release-history.md)", readme, ) @@ -11301,8 +11299,8 @@ def test_start_here_docs_cover_reviewer_gate_and_build_loop_routes(self) -> None encoding="utf-8", ) - self.assertIn("[Try Code Mower In 10 Minutes](docs/try-in-10-minutes.md)", readme) - self.assertIn("[Build Loop In 30 Minutes](docs/build-loop-in-30-minutes.md)", readme) + self.assertIn("[Try Code Mower In 10 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/try-in-10-minutes.md)", readme) + self.assertIn("[Build Loop In 30 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/build-loop-in-30-minutes.md)", readme) self.assertEqual(len(re.findall(r"^## 8\.", try_in_10, re.MULTILINE)), 1) self.assertTrue(try_in_10.rstrip().endswith("](build-loop-in-30-minutes.md).")) self.assertIn("[Quickstart](quickstart.md)", try_in_10) @@ -11390,7 +11388,7 @@ def test_board_data_contract_preserves_local_only_cloud_boundary(self) -> None: readme_flat = " ".join(readme.split()) quickstart_flat = " ".join(quickstart.split()) - self.assertIn("[Board Data Contract](docs/board-data-contract.md)", readme) + self.assertIn("[Board Data Contract](https://github.com/codemower-ai/code-mower/blob/main/docs/board-data-contract.md)", readme) self.assertIn("code-mower board record --repo OWNER/REPO", readme) self.assertIn("code-mower board serve --repo OWNER/REPO --record-events", readme) self.assertIn("code-mower board serve --repo OWNER/REPO --record-events", launch_surface) @@ -11639,9 +11637,9 @@ def test_current_adoption_docs_cover_cold_start_and_history(self) -> None: encoding="utf-8", ) - self.assertIn("[Try Code Mower In 10 Minutes](docs/try-in-10-minutes.md)", readme) + self.assertIn("[Try Code Mower In 10 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/try-in-10-minutes.md)", readme) self.assertIn( - "[Build Loop In 30 Minutes](docs/build-loop-in-30-minutes.md)", + "[Build Loop In 30 Minutes](https://github.com/codemower-ai/code-mower/blob/main/docs/build-loop-in-30-minutes.md)", readme, ) self.assertIn("code-mower builder-experiment run", builder_experiments) diff --git a/tests/test_release_v142.py b/tests/test_release_v142.py index 1f34fe59..c0eb8799 100644 --- a/tests/test_release_v142.py +++ b/tests/test_release_v142.py @@ -1,39 +1,205 @@ -"""Version-identity and Board-boundary regressions for the v1.4.2 release, #952.""" +"""Published-identity and Board-boundary regressions for the v1.4.2 release. + +Release #952 is closed: v1.4.2 was published from release commit +``55339bf1acf76d33be5937e80bdaad772e0b2bf5`` under the annotated ``v1.4.2`` tag. +These tests protect the *published* identity in current-facing documentation. +They deliberately assert on facts -- version pins, evidence identifiers, link +shape, packaged-template agreement -- rather than on sentence wording, so +ordinary editorial passes do not break them. +""" import os from pathlib import Path +import re import subprocess import sys import tempfile import unittest -from code_mower import __version__, release_readiness +from code_mower import __version__, release_readiness, versioning from code_mower import package as package_module ROOT = Path(__file__).resolve().parents[1] +RELEASE_COMMIT = "55339bf1acf76d33be5937e80bdaad772e0b2bf5" +AUDITED_HEAD = "32706cf5a01af5863d6c713b83dfb196efde6d4c" +WHEEL_SHA256 = "f8bf24dd8a982ed5ab28302e837cd5d2aeece6d984ed1c44fcb4688c3fb7a522" +SDIST_SHA256 = "aff202eea9748ab3734ea6b90ba3b48ea5aea1e21ae87fba03b77b64d5cdec42" + +#: Current-facing pages a new user or agent may follow. Historical release +#: notes, qualification records for earlier versions, and archived transcripts +#: are deliberately excluded: they are preserved, not reconciled. +CURRENT_FACING_DOCS = ( + "README.md", + "docs/install.md", + "docs/quickstart.md", + "docs/try-in-10-minutes.md", + "docs/current-state-and-roadmap.md", + "docs/release-history.md", + "docs/public-release-checklist.md", + "docs/oss-v1-checklist.md", + "docs/early-adopter-invite-runbook.md", + "docs/early-adopter-v05.md", + "docs/friendly-user-rollout-v05.md", + "docs/first-user-install-rehearsal.md", + "docs/sessions.md", + "docs/github-setup.md", + "docs/builders-grok-cursor.md", + "docs/graphify-setup.md", + "docs/v142-release-notes.md", + "docs/v142-qualification.md", +) +#: Wording that describes v1.4.2 as unpublished. Any of these in a +#: current-facing page is a stale-candidate regression. +STALE_CANDIDATE_PHRASES = ( + "source candidate", + "not yet published", + "publication pending", + "pending #952", + "pending [#952]", + "apply after publication", +) -class PublicReleaseChecklistCandidateStatusTests(unittest.TestCase): - def test_current_entrypoint_is_141_and_142_is_the_target_not_current(self): - checklist = (ROOT / "docs/public-release-checklist.md").read_text(encoding="utf-8") - self.assertIn( - "The current published package-index release entrypoint is\n" - " `code-mower==1.4.1` (GitHub tag `v1.4.1`)", - checklist, - ) + +def _read(relative): + return (ROOT / relative).read_text(encoding="utf-8") + + +class PublishedIdentityTests(unittest.TestCase): + def test_current_docs_do_not_describe_v142_as_an_unpublished_candidate(self): + offenders = [] + for relative in CURRENT_FACING_DOCS: + lowered = _read(relative).lower() + for phrase in STALE_CANDIDATE_PHRASES: + if phrase in lowered: + offenders.append(f"{relative}: {phrase!r}") + self.assertEqual(offenders, [], "\n" + "\n".join(offenders)) + + def test_current_docs_do_not_still_call_v141_the_current_release(self): + # v1.4.1 must stay nameable as history, but no current-facing page may + # present it as the entrypoint a reader should install. + for relative in CURRENT_FACING_DOCS: + with self.subTest(doc=relative): + text = " ".join(_read(relative).split()) + self.assertNotIn("current published package-index release entrypoint is `code-mower==1.4.1`", text) + self.assertNotIn("The current package-index release baseline is `v1.4.1`", text) + + def test_shared_baseline_sentence_matches_the_published_version(self): + sentence = versioning.public_baseline_sentence(__version__) + self.assertIn("`v1.4.2`", sentence) + self.assertIn("`code-mower==1.4.2`", sentence) + for relative in ("README.md", "docs/current-state-and-roadmap.md", + "docs/friendly-user-rollout-v05.md"): + with self.subTest(doc=relative): + self.assertIn(sentence, " ".join(_read(relative).split())) + + def test_release_records_bind_the_exact_published_evidence(self): + for relative in ("docs/v142-release-notes.md", "docs/v142-qualification.md"): + with self.subTest(doc=relative): + text = _read(relative) + self.assertIn(RELEASE_COMMIT, text) + self.assertIn(AUDITED_HEAD, text) + self.assertIn(WHEEL_SHA256, text) + self.assertIn(SDIST_SHA256, text) + self.assertIn("35189302150", text) + + def test_release_records_point_readers_past_the_immutable_tag_snapshots(self): + # The v1.4.2 tag carries the prepublication copies of both pages and is + # never rewritten, so each page on main has to say so. + for relative in ("docs/v142-release-notes.md", "docs/v142-qualification.md"): + with self.subTest(doc=relative): + text = _read(relative) + self.assertIn("tag", text) + self.assertIn("prepublication", text) + + def test_951_stays_an_open_unclaimed_boundary(self): + for relative in ("README.md", "docs/v142-release-notes.md", + "docs/v142-qualification.md", + "docs/current-state-and-roadmap.md"): + with self.subTest(doc=relative): + text = _read(relative) + self.assertIn("#951", text) + self.assertIn("not claimed", " ".join(text.split()).lower()) + + def test_no_current_doc_claims_the_hosted_canary_ran(self): + for relative in CURRENT_FACING_DOCS: + with self.subTest(doc=relative): + text = " ".join(_read(relative).split()).lower() + for claim in ("hosted devin canary passed", + "hosted canary passed", + "canary completed"): + self.assertNotIn(claim, text) + + def test_release_triggered_verification_skip_is_explained_not_reported_as_failure(self): + qualification = " ".join(_read("docs/v142-qualification.md").split()) + self.assertIn("35189721623", qualification) + self.assertIn("intentionally skipped", qualification) + + +class ReadmeLinkTests(unittest.TestCase): + """The README is the PyPI long description; relative links do not resolve there.""" + + LINK_RE = re.compile(r"!?\[[^\]]*\]\(([^)\s]+)\)") + BLOB_PREFIX = "https://github.com/codemower-ai/code-mower/blob/main/" + + def test_readme_has_no_relative_links(self): + readme = _read("README.md") + relative = [ + destination + for destination in self.LINK_RE.findall(readme) + if not destination.startswith(("http://", "https://", "mailto:", "#")) + ] + self.assertEqual(relative, [], "README links must be absolute for PyPI") + + def test_readme_repository_links_point_at_files_that_exist(self): + readme = _read("README.md") + missing = [] + checked = 0 + for destination in self.LINK_RE.findall(readme): + if not destination.startswith(self.BLOB_PREFIX): + continue + checked += 1 + relative = destination[len(self.BLOB_PREFIX):].partition("#")[0] + if not (ROOT / relative).exists(): + missing.append(relative) + self.assertGreater(checked, 20, "expected the README doc index to be absolute") + self.assertEqual(missing, []) + + def test_pyproject_still_ships_the_readme_as_the_long_description(self): + pyproject = _read("pyproject.toml") + self.assertIn('readme = "README.md"', pyproject) + + +class PackagedTemplateConsistencyTests(unittest.TestCase): + LANE_README = "templates/lanes/README.md" + PACKAGED_LANE_README = "src/code_mower/templates/lanes/README.md" + + def test_repo_and_packaged_lane_readme_are_identical(self): + self.assertEqual(_read(self.LANE_README), _read(self.PACKAGED_LANE_README)) + + def test_both_lane_readmes_document_the_supported_never_expiry(self): + for relative in (self.LANE_README, self.PACKAGED_LANE_README): + with self.subTest(template=relative): + self.assertIn("`YYYY-MM-DD`, or `never` for a non-expiring token.", + _read(relative)) + + def test_never_expiry_is_what_init_actually_advertises(self): + init_source = _read("src/code_mower/init.py") + self.assertIn("(YYYY-MM-DD or never)", init_source) + + +class PublicReleaseChecklistTests(unittest.TestCase): + def test_checklist_names_v142_as_the_published_entrypoint(self): + checklist = " ".join(_read("docs/public-release-checklist.md").split()) self.assertIn( - "The target package-index entrypoint after\n" - " v1.4.2's acceptance is `code-mower==1.4.2` (GitHub tag `v1.4.2`)", + "The current published package-index release entrypoint is " + "`code-mower==1.4.2` (GitHub tag `v1.4.2`)", checklist, ) - # Never re-introduce the ambiguous "current entrypoint is v1.4.2" - # framing while v1.4.2 is still an unpublished candidate. - self.assertNotIn("current package-index release entrypoint is `code-mower==1.4.2`", checklist) - self.assertNotIn("The corresponding GitHub tag is\n `v1.4.2`", checklist) class RoadmapDocFactsTests(unittest.TestCase): def test_role_policy_and_effective_authority_are_recorded_as_shipped(self): - roadmap = (ROOT / "docs/current-state-and-roadmap.md").read_text(encoding="utf-8") + roadmap = _read("docs/current-state-and-roadmap.md") self.assertIn( "These are main-line\nstabilization changes that shipped in `v1.4.1`.", roadmap, @@ -42,52 +208,94 @@ def test_role_policy_and_effective_authority_are_recorded_as_shipped(self): self.assertIn("both are part of the\npublished `v1.4.1` artifact", roadmap) def test_graphify_915_closeout_is_recorded_complete_not_pending(self): - roadmap = (ROOT / "docs/current-state-and-roadmap.md").read_text(encoding="utf-8") + roadmap = _read("docs/current-state-and-roadmap.md") self.assertIn( "completing the release-specific comparative scorecard, campaign,\n" "Board, and fresh aggregate evidence as part of that closeout", roadmap, ) self.assertNotIn("remain separately tracked release-specific follow-ups", roadmap) - self.assertNotIn("remain pending", roadmap.partition("Release #915")[2][:200]) - - def test_board_section_names_the_merging_prs_not_just_issues(self): - roadmap = (ROOT / "docs/current-state-and-roadmap.md").read_text(encoding="utf-8") - self.assertIn("via\n[PR #1001](https://github.com/codemower-ai/code-mower/pull/1001)", roadmap) - self.assertIn("via\n[PR #1003](https://github.com/codemower-ai/code-mower/pull/1003)", roadmap) - self.assertIn("#961 via PR #1001", roadmap) - self.assertNotIn("are drafts behind\nmain that need refreshing", roadmap) + + def test_all_three_v14_releases_are_recorded_as_shipped(self): + roadmap = " ".join(_read("docs/current-state-and-roadmap.md").split()) + self.assertIn("`v1.4.0`, `v1.4.1` and `v1.4.2` have all shipped", roadmap) + self.assertIn("Board shipped as `v1.4.2` from release commit `55339bf`", roadmap) self.assertNotIn("Board work is underway", roadmap) + self.assertNotIn("Board implementation is accepted on `main`, not underway", roadmap) + + def test_v150_slack_is_the_active_phase_and_no_longer_deferred(self): + roadmap = " ".join(_read("docs/current-state-and-roadmap.md").split()) + self.assertIn("active, `v1.5.0`", roadmap) + self.assertIn("This is the current roadmap phase.", roadmap) + self.assertNotIn( + "This runtime work is deferred until the sequence above is complete.", + roadmap, + ) + + def test_board_prs_are_linked_as_pulls_and_952_is_not_called_a_pr(self): + roadmap = _read("docs/current-state-and-roadmap.md") + for number in (999, 1000, 1001, 1002, 1003, 1006): + with self.subTest(pull=number): + self.assertIn( + f"https://github.com/codemower-ai/code-mower/pull/{number}", + roadmap, + ) + self.assertNotIn( + f"https://github.com/codemower-ai/code-mower/issues/{number}", + roadmap, + ) + # #952 and #961 are issues, not pull requests. + for number in (951, 952, 961): + with self.subTest(issue=number): + self.assertNotIn( + f"https://github.com/codemower-ai/code-mower/pull/{number}", + roadmap, + ) + self.assertNotIn("the release PR #952", roadmap) class ChangelogAndRunbookInclusionTests(unittest.TestCase): - def test_changelog_v142_section_lists_the_actually_shipping_board_work(self): - changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + def test_unreleased_section_is_first(self): + changelog = _read("CHANGELOG.md") + self.assertLess( + changelog.index("## Unreleased"), + changelog.index("## 1.4.2"), + "Unreleased belongs above the released sections", + ) + + def test_changelog_v142_section_is_marked_published_and_lists_the_board_work(self): + changelog = _read("CHANGELOG.md") + self.assertIn("## 1.4.2 — published", changelog) + self.assertNotIn("## 1.4.2 — source candidate", changelog) v142_section = changelog.partition("## 1.4.2")[2].partition("\n## 1.4.1")[0] - unreleased_section = changelog.partition("## Unreleased")[2] self.assertIn("code-mower board service", v142_section) self.assertIn("code-mower board stop --repo OWNER/REPO", v142_section) self.assertIn("#999", v142_section) self.assertIn("#1003", v142_section) - # Work that actually ships in 1.4.2 is not left double-booked under - # Unreleased. - self.assertNotIn("code-mower board service` manages", unreleased_section) - self.assertNotIn("board stop --repo OWNER/REPO", unreleased_section) + self.assertIn(RELEASE_COMMIT, v142_section) + + def test_unreleased_section_does_not_double_book_released_work(self): + changelog = _read("CHANGELOG.md") + unreleased = changelog.partition("## Unreleased")[2].partition("\n## 1.4.2")[0] + self.assertNotIn("code-mower board service` manages", unreleased) + self.assertNotIn("board stop --repo OWNER/REPO", unreleased) + # PR #1007 is open, not merged; it is neither released nor on main. + self.assertNotIn("#1007", changelog) def test_changelog_v141_section_is_marked_published_not_pending(self): - changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + changelog = _read("CHANGELOG.md") self.assertIn("## 1.4.1 — published", changelog) self.assertNotIn("## 1.4.1 — source candidate", changelog) def test_current_runbook_names_the_actual_v142_required_inclusion(self): - runbook = (ROOT / "docs/pypi-release.md").read_text(encoding="utf-8") + runbook = _read("docs/pypi-release.md") self.assertIn("#999/#1000/#1001/#1002/#1003", runbook) self.assertNotIn("including #876", runbook) class UpgradeRehearsalTests(unittest.TestCase): def test_runbook_binds_a_real_141_to_142_upgrade_with_preserved_state(self): - runbook = (ROOT / "docs/pypi-release.md").read_text(encoding="utf-8") + runbook = _read("docs/pypi-release.md") step = runbook.partition( "### 17. Rehearse the 1.4.1-to-1.4.2 upgrade in place, preserving existing state" )[2] @@ -123,13 +331,13 @@ def test_runbook_binds_a_real_141_to_142_upgrade_with_preserved_state(self): # A cold install cannot substitute for having actually upgraded. self.assertIn("do not record upgrade coverage as passed on a\ncold-install substitute", step) - def test_release_notes_and_qualification_claim_upgrade_coverage_that_exists(self): - release_notes = (ROOT / "docs/v142-release-notes.md").read_text(encoding="utf-8") - qualification = (ROOT / "docs/v142-qualification.md").read_text(encoding="utf-8") - runbook = (ROOT / "docs/pypi-release.md").read_text(encoding="utf-8") - self.assertIn("1.4.1-to-1.4.2 upgrade rehearsal", release_notes) + def test_release_records_claim_upgrade_coverage_that_exists(self): + release_notes = _read("docs/v142-release-notes.md") + qualification = _read("docs/v142-qualification.md") + runbook = _read("docs/pypi-release.md") + self.assertIn("1.4.1-to-1.4.2 upgrade", release_notes) self.assertIn("upgrade from v1.4.1", qualification) - # The claim in the candidate docs must point at a runbook step that + # The claim in the release docs must point at a runbook step that # actually exists, not an unimplemented promise. self.assertIn("### 17. Rehearse the 1.4.1-to-1.4.2 upgrade in place", runbook) @@ -148,29 +356,36 @@ def test_release_tag_for_current_version(self): class RunbookIdentityTests(unittest.TestCase): def test_pypi_release_doc_carries_the_v142_runbook_heading(self): - doc = (ROOT / "docs/pypi-release.md").read_text(encoding="utf-8") + doc = _read("docs/pypi-release.md") self.assertIn( f"## v1.4.2 {release_readiness.POST_MERGE_RUNBOOK_HEADING}", doc, ) def test_release_notes_and_qualification_docs_exist_for_v142(self): - release_notes = (ROOT / "docs/v142-release-notes.md").read_text(encoding="utf-8") - qualification = (ROOT / "docs/v142-qualification.md").read_text(encoding="utf-8") + release_notes = _read("docs/v142-release-notes.md") + qualification = _read("docs/v142-qualification.md") self.assertIn("# Code Mower v1.4.2 Release Notes", release_notes) self.assertIn("v1.4.2 qualification and evidence matrix", qualification) - # v1.4.1's own historical documents must remain untouched. + # v1.4.1's own historical documents must remain present. self.assertTrue((ROOT / "docs/v141-release-notes.md").is_file()) self.assertTrue((ROOT / "docs/v141-qualification.md").is_file()) + def test_preserved_v141_candidate_docs_carry_a_completed_release_banner(self): + for relative in ("docs/v141-release-notes.md", "docs/v141-qualification.md"): + with self.subTest(doc=relative): + text = " ".join(_read(relative).split()) + self.assertIn("v1.4.1 is a completed release", text) + self.assertIn("releases/tag/v1.4.1", text) + def test_release_history_orders_v142_before_v141_before_v131(self): - release_history = (ROOT / "docs/release-history.md").read_text(encoding="utf-8") + release_history = _read("docs/release-history.md") self.assertLess( - release_history.index("[v1.4.2 source candidate notes](v142-release-notes.md)"), - release_history.index("[v1.4.1 source candidate notes](v141-release-notes.md)"), + release_history.index("[v1.4.2 release notes](v142-release-notes.md)"), + release_history.index("[v1.4.1 release notes](v141-release-notes.md)"), ) self.assertLess( - release_history.index("[v1.4.1 source candidate notes](v141-release-notes.md)"), + release_history.index("[v1.4.1 release notes](v141-release-notes.md)"), release_history.index("[v1.3.1 release notes](v131-release-notes.md)"), ) @@ -186,7 +401,7 @@ def test_release_history_orders_v142_before_v141_before_v131(self): class BoardRestartBoundaryTests(unittest.TestCase): def test_qualification_doc_names_the_verified_two_service_inventory(self): - qualification = (ROOT / "docs/v142-qualification.md").read_text(encoding="utf-8") + qualification = _read("docs/v142-qualification.md") # A read-only `board list --json` verified exactly two live local # Board services pre-release: 5332 (the public repo) plus one # additional private-repository port. Posture (managed vs transient) @@ -197,25 +412,35 @@ def test_qualification_doc_names_the_verified_two_service_inventory(self): for phrase in STALE_BOARD_COUNT_PHRASES: with self.subTest(phrase=phrase): self.assertNotIn(phrase, qualification) - self.assertIn("serving ==", qualification) - self.assertIn("1.4.2", qualification) - - def test_release_notes_do_not_claim_951_hosted_canary_or_close_951(self): - release_notes = (ROOT / "docs/v142-release-notes.md").read_text(encoding="utf-8") - self.assertIn("bounded hosted Devin canary is still pending", release_notes) - self.assertIn("does not claim the hosted result or close", release_notes) + self.assertIn("serving == installed == 1.4.2", qualification) - def test_release_notes_name_the_verified_two_service_inventory(self): - release_notes = (ROOT / "docs/v142-release-notes.md").read_text(encoding="utf-8") - self.assertIn("two\nobserved local Board processes", release_notes) + def test_release_notes_record_the_verified_two_service_restart(self): + release_notes = _read("docs/v142-release-notes.md") self.assertIn("port 5332", release_notes) + self.assertIn("private-repository Board", release_notes) for phrase in STALE_BOARD_COUNT_PHRASES: with self.subTest(phrase=phrase): self.assertNotIn(phrase, release_notes) + def test_board_service_lifecycle_table_has_no_stranded_row(self): + lifecycle = _read("docs/board-service-lifecycle.md") + refusals = lifecycle.partition("## Fail-closed refusals")[2] + stranded = [ + line + for index, line in enumerate(refusals.splitlines()) + if line.startswith("| `") + and index + and not refusals.splitlines()[index - 1].startswith("|") + ] + self.assertEqual(stranded, [], "a table row is stranded outside its table") + # delayed_health_failed is decided after the apply, so it belongs with + # delayed health rather than with the no-state-change refusals. + delayed = lifecycle.partition("## Delayed health")[2].partition("## Fail-closed")[0] + self.assertIn("delayed_health_failed", delayed) + def test_current_runbook_and_hygiene_use_reconciled_board_heading(self): - runbook = (ROOT / "docs/pypi-release.md").read_text(encoding="utf-8") - hygiene = (ROOT / "tests/test_release_hygiene.py").read_text(encoding="utf-8") + runbook = _read("docs/pypi-release.md") + hygiene = _read("tests/test_release_hygiene.py") self.assertIn( "### 15. Restart the reconciled Board inventory from the release", runbook, @@ -231,6 +456,57 @@ def test_current_runbook_and_hygiene_use_reconciled_board_heading(self): self.assertTrue((ROOT / "docs/v140-release-runbook.md").is_file()) +class BoardAndGraphifyDiscoverabilityTests(unittest.TestCase): + def test_readme_surfaces_the_persistent_board_service_and_its_platform_boundary(self): + readme = " ".join(_read("README.md").split()) + self.assertIn("code-mower board service", readme) + self.assertIn("launchd", readme) + self.assertIn("every other platform refuses", readme) + self.assertIn("board-service-lifecycle.md", readme) + + def test_readme_navigates_to_graphify_setup_lifecycle_and_queries(self): + readme = _read("README.md") + for target in ("docs/graphify-setup.md", + "docs/context-graph-lifecycle.md", + "docs/context-graph-queries.md"): + with self.subTest(target=target): + self.assertIn(target, readme) + + def test_graphify_setup_documents_the_ramp_up_flow_and_its_exclusions(self): + setup = _read("docs/graphify-setup.md") + for command in ("context-graph doctor", + "context-graph build", + "context-graph status", + "context-graph connect", + "context-graph connection-status", + "context-graph query", + "context-graph refresh", + "context-graph disconnect", + "context-graph remove"): + with self.subTest(command=command): + self.assertIn(command, setup) + collapsed = " ".join(setup.split()) + for exclusion in ("Untracked and\n ignored files", "Symlinks and submodules", + "Committed private state"): + with self.subTest(exclusion=exclusion): + self.assertIn(" ".join(exclusion.split()), collapsed) + self.assertIn("Nothing watches the working tree", collapsed) + + def test_graphify_evaluation_is_framed_as_a_dated_historical_record(self): + evaluation = " ".join(_read("docs/graphify-evaluation.md").split()) + self.assertIn("Historical record. Graphify has since shipped.", evaluation) + self.assertIn("2026-09-12", evaluation) + self.assertIn("graphify-setup.md", evaluation) + # The recorded benchmark evidence is preserved, not rewritten. + self.assertIn("Clean-room experiment", evaluation) + + def test_board_demo_does_not_claim_serve_opens_a_browser(self): + demo = " ".join(_read("examples/board-demo/README.md").split()) + self.assertIn("It does not open a browser.", demo) + self.assertIn("--open", demo) + self.assertNotIn("To open the local browser Board", demo) + + class InstalledPromptPackTests(unittest.TestCase): def test_literal_starter_and_explicit_config_walkthrough(self): """Exercise installed 1.4.2 code, with no provider login or network doctor probes.""" From d5f66c3d7f989305af47607061d998811ec0edf4 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Thu, 17 Sep 2026 00:14:47 -0700 Subject: [PATCH 2/6] Docs: restore backslash continuations in the Graphify ramp-up commands Four multi-line commands in the new ramp-up flow lost their trailing line-continuation, joining each command onto one line with stray whitespace. Co-Authored-By: Claude Opus 5 (1M context) --- docs/graphify-setup.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/graphify-setup.md b/docs/graphify-setup.md index 39d9bb75..bb8e34be 100644 --- a/docs/graphify-setup.md +++ b/docs/graphify-setup.md @@ -144,7 +144,8 @@ their installation. ```bash GRAPHIFY_REVISION="$(git rev-parse HEAD)" -code-mower context-graph build --pin-file "$GRAPHIFY_ROOT/pin.json" --indexer "$GRAPHIFY_INDEXER" --revision "$GRAPHIFY_REVISION" +code-mower context-graph build --pin-file "$GRAPHIFY_ROOT/pin.json" \ + --indexer "$GRAPHIFY_INDEXER" --revision "$GRAPHIFY_REVISION" ``` The census comes from `git ls-tree -r` against that **commit** -- not the @@ -164,7 +165,8 @@ it. A provider run that admitted an incomplete census publishes a generation **4. Register the graph as a local context connection.** ```bash -code-mower context-graph connect --connection local-graph --repository owner/repo --recipient claude:builder --recipient codex:reviewer +code-mower context-graph connect --connection local-graph \ + --repository owner/repo --recipient claude:builder --recipient codex:reviewer code-mower context-graph connection-status --connection local-graph ``` @@ -176,7 +178,8 @@ decision that belongs to the connection, not to any individual query. **5. Ask one bounded question.** ```bash -code-mower context-graph query --question impact --target parse_config --authorization AUTH.json --packet-out /tmp/packet.json --json +code-mower context-graph query --question impact --target parse_config \ + --authorization AUTH.json --packet-out /tmp/packet.json --json ``` `--question` is one of `impact`, `dependency`, `symbol`, or `related_tests`. @@ -191,7 +194,8 @@ evidence goes to the `--packet-out` file, created `0600`, or nowhere at all. ```bash GRAPHIFY_REVISION="$(git rev-parse HEAD)" -code-mower context-graph refresh --pin-file "$GRAPHIFY_ROOT/pin.json" --indexer "$GRAPHIFY_INDEXER" --revision "$GRAPHIFY_REVISION" +code-mower context-graph refresh --pin-file "$GRAPHIFY_ROOT/pin.json" \ + --indexer "$GRAPHIFY_INDEXER" --revision "$GRAPHIFY_REVISION" ``` Nothing watches the working tree, so nothing refreshes on your behalf. When From 4f4a54562a5aade1bace98adfea4a324fcebe110 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Thu, 17 Sep 2026 00:23:29 -0700 Subject: [PATCH 3/6] Docs: distinguish the published v1.4.2 Graphify integration from main PR #1007 merged to main at b863e638 after v1.4.2 was published, so the two are no longer the same integration. Current docs still described #1007 as an open, unmerged pull request and left its entry out of the changelog entirely. - CHANGELOG: Unreleased is no longer empty. It carries #1007's Fixed entry and states the boundary directly -- accepted on main, in no published package, intended for the next appropriate release. The v1.4.1 shipped entries and the immutable 1.4.2 section are unchanged, and the Graphify fix is not double-booked into 1.4.2. - graphify-setup.md: replace the stale "separate open pull request ... not merged and not released" paragraph with a "Published `v1.4.2` versus current `main`" section naming what merged, that the accepted 0.9.58 pin and wheel digest are untouched because this is a Code Mower fix rather than a provider upgrade, and that a generation built before the next release must be rebuilt with `context-graph refresh`. A published generation is never rewritten in place, so upgrading alone does not repair an older partial frontend generation. - current-state-and-roadmap.md: the published package carries the originally shipped integration; the merged compatibility fixes are recorded as on main awaiting the next appropriate release, in the Graphify phase and in the delivery-order note about merged-but-unpublished work. - README, context-graph-lifecycle.md and context-graph-queries.md: state where the #1007 behaviour does and does not apply, so a reader on the published package is not told the separate provider-manifest budget, `doc_ref` exclusions, or JavaScript/TypeScript test conventions are available to them. - graphify-evaluation.md: point the historical banner at that boundary section. - test_release_v142.py: drop the assertion that #1007 is absent from the changelog, which encoded the now-false claim that it is unmerged. Replace it with coverage that Unreleased carries the merged entry, that the setup and roadmap pages state the boundary in both directions including the rebuild requirement, that no current page still calls #1007 open, and that the pin stays at 0.9.58. Rebased onto b863e638 with #1007's changelog entry and documentation preserved. Refs #1008. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 8 ++++ docs/context-graph-lifecycle.md | 2 + docs/context-graph-queries.md | 5 +++ docs/current-state-and-roadmap.md | 22 ++++++++-- docs/graphify-evaluation.md | 4 +- docs/graphify-setup.md | 34 +++++++++++++-- tests/test_release_v142.py | 69 ++++++++++++++++++++++++++++++- 7 files changed, 133 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3694644b..32f4a31a 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,14 @@ for what a build is allowed to see and where its state lives, and [Bounded Queries And Context Packets](https://github.com/codemower-ai/code-mower/blob/main/docs/context-graph-queries.md) for the four questions and the packet contract. +The published v1.4.2 package contains that originally shipped integration. +Further real-pilot compatibility fixes -- a bounded provider-manifest reader, +`doc_ref` exclusions, and JavaScript/TypeScript test-convention recognition -- +merged to `main` after the release and are intended for the next appropriate +release. The accepted `0.9.58` provider pin is unchanged, and a generation built +before that release has to be rebuilt explicitly; upgrading does not repair one. +See [Optional Graphify Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/graphify-setup.md#published-v142-versus-current-main). + ## Documentation ### Install And First Use diff --git a/docs/context-graph-lifecycle.md b/docs/context-graph-lifecycle.md index f705c814..eb1c67d0 100644 --- a/docs/context-graph-lifecycle.md +++ b/docs/context-graph-lifecycle.md @@ -518,6 +518,8 @@ refuses publication with an explicit provider-manifest diagnostic. It is never loaded whole and measured afterwards. Existing coverage/hash checks and the total artifact budget still apply; a larger readable inventory does not excuse missing or mismatched rows. A bound checked on bytes already in memory bounds nothing. +The separate provider-manifest budget arrived after `v1.4.2` was published; the +published package applies the single compact bound to both. The provider's own stdout and stderr are the other unbounded output, and they are discarded at the kernel: `stdin`, `stdout` and `stderr` are all diff --git a/docs/context-graph-queries.md b/docs/context-graph-queries.md index 9a49f968..63de93fc 100644 --- a/docs/context-graph-queries.md +++ b/docs/context-graph-queries.md @@ -201,6 +201,11 @@ although its validator omits that type. These are accepted as declared non-code exclusions, like document nodes. They and their incident edges do not become code query results or source citations; unknown node types remain invalid. +The test-path conventions and the `doc_ref` acceptance described in this section +arrived after `v1.4.2` was published and are on `main` for the next appropriate +release. In the published package a `doc_ref` node is an unknown type and the +JavaScript/TypeScript conventions are not recognized. + Each traversal is symbol-first: a target resolves to the symbols carrying that name, and only a target that names no symbol at all is read as a path. Each is breadth-first over adjacency sorted by the full relationship identity described diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index 126ecd7f..b40d1f50 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -224,11 +224,23 @@ Release #915 shipped that accepted source and stabilization baseline as `v1.4.1`, completing the release-specific comparative scorecard, campaign, Board, and fresh aggregate evidence as part of that closeout. -Graphify remains available in `v1.4.2`, unchanged. Installation stays opt-in and -outside the base dependency set, no command requires an index to exist, and Code -Mower owns refresh policy rather than parsing human-oriented MCP prose. See +The published `v1.4.2` package carries that originally shipped integration +unchanged. Installation stays opt-in and outside the base dependency set, no +command requires an index to exist, and Code Mower owns refresh policy rather +than parsing human-oriented MCP prose. See [Optional Graphify Setup](graphify-setup.md) for the current ramp-up flow. +Real-pilot compatibility fixes have since merged to `main` in +[PR #1007](https://github.com/codemower-ai/code-mower/pull/1007): a bounded +provider-manifest reader separate from the compact generation-manifest bound, +explicit refusal of an oversized provider manifest, `doc_ref` nodes as declared +non-code exclusions, and JavaScript/TypeScript test-convention and `imports` +recognition in `related_tests`. They are on `main` and intended for the next +appropriate release; the published `v1.4.2` package does not contain them. The +accepted `0.9.58` provider pin is unchanged, and a generation built before that +future release must be rebuilt explicitly -- upgrading Code Mower does not +repair one, because a published generation is never rewritten in place. + ### 3. Board Clarity And Session Visibility -- shipped in `v1.4.2` ([#945](https://github.com/codemower-ai/code-mower/issues/945) / release [#952](https://github.com/codemower-ai/code-mower/issues/952)) Board is complete at release commit `55339bf`. @@ -302,7 +314,9 @@ setup, or documentation work. Elapsed time, implementation difficulty, or an open draft PR never changes this release order. Merged fixes count as on main until a later published package is verified to contain them; #935/#973 and the phase-3 Board PRs are now verified -in the published `v1.4.2` artifact. +in the published `v1.4.2` artifact, while the merged Graphify compatibility +fixes in [PR #1007](https://github.com/codemower-ai/code-mower/pull/1007) are on +main awaiting the next appropriate release. Each child issue should produce one reviewable PR with one branch writer, independent current-head review, the normal gate, and package-level validation. diff --git a/docs/graphify-evaluation.md b/docs/graphify-evaluation.md index 4224fd64..77fd4a3e 100644 --- a/docs/graphify-evaluation.md +++ b/docs/graphify-evaluation.md @@ -13,7 +13,9 @@ forward to [epic #902](https://github.com/codemower-ai/code-mower/issues/902). > published `v1.4.2` release. For what exists today, read > [Optional Graphify Setup](graphify-setup.md), > [the revision-bound lifecycle](context-graph-lifecycle.md), and -> [bounded queries and context packets](context-graph-queries.md). The +> [bounded queries and context packets](context-graph-queries.md). For the +> real-pilot compatibility fixes that merged to `main` after the release, read +> [published `v1.4.2` versus current `main`](graphify-setup.md#published-v142-versus-current-main). The > benchmark evidence and thresholds below are preserved as recorded and are not > rewritten. diff --git a/docs/graphify-setup.md b/docs/graphify-setup.md index bb8e34be..5dcd9419 100644 --- a/docs/graphify-setup.md +++ b/docs/graphify-setup.md @@ -218,7 +218,33 @@ provider environment from [Separate acquisition environment](#separate-acquisition-environment) is yours to keep or delete separately; Code Mower never touches it. -A separate open pull request, -[#1007](https://github.com/codemower-ai/code-mower/pull/1007), is working on -further Graphify real-pilot compatibility. It is not merged and not released; -nothing on this page depends on it. +## Published `v1.4.2` versus current `main` + +The published `v1.4.2` package on the package index contains the optional +Graphify integration exactly as it originally shipped. Everything above +describes that package. + +[PR #1007](https://github.com/codemower-ai/code-mower/pull/1007) has since +merged to `main` with further real-pilot compatibility fixes: a bounded 16 MiB +provider-manifest reader separate from the 256 KiB bound on Code Mower's own +generation manifest, explicit refusal of an oversized provider manifest, +`doc_ref` nodes accepted as declared non-code exclusions, and `related_tests` +recognition of JavaScript/TypeScript `.test`/`.spec` and `__tests__` +conventions together with `imports` relationships. The language-extras and +runtime-ownership paragraphs under +[Separate acquisition environment](#separate-acquisition-environment) arrived +with the same change. All of it is on `main` and intended for the next +appropriate release; none of it is in the published `v1.4.2` package. + +The accepted provider pin is unchanged. This is a Code Mower compatibility fix, +not a Graphify upgrade: `graphifyy` `0.9.58` and the recorded wheel digest above +stay exactly as they are. + +Because a published generation is never rewritten in place, installing that +later release does not repair a generation you already built. A generation built +before it can be partial -- a frontend inventory whose oversized provider +manifest was refused, or inputs left unprocessed by a missing language parser. +After upgrading, rebuild it explicitly with `code-mower context-graph refresh` +(step 6), which publishes a new generation at the same revision, then confirm +`code-mower context-graph status --json` reports it usable rather than +`partial`. diff --git a/tests/test_release_v142.py b/tests/test_release_v142.py index c0eb8799..37222daf 100644 --- a/tests/test_release_v142.py +++ b/tests/test_release_v142.py @@ -279,8 +279,23 @@ def test_unreleased_section_does_not_double_book_released_work(self): unreleased = changelog.partition("## Unreleased")[2].partition("\n## 1.4.2")[0] self.assertNotIn("code-mower board service` manages", unreleased) self.assertNotIn("board stop --repo OWNER/REPO", unreleased) - # PR #1007 is open, not merged; it is neither released nor on main. - self.assertNotIn("#1007", changelog) + + def test_unreleased_carries_the_merged_graphify_compatibility_work(self): + """PR #1007 merged to main after v1.4.2 was published. + + Its entry belongs under Unreleased -- on main, in no published package + -- and must not be folded into the immutable 1.4.2 section. + """ + changelog = _read("CHANGELOG.md") + unreleased = changelog.partition("## Unreleased")[2].partition("\n## 1.4.2")[0] + v142_section = changelog.partition("## 1.4.2")[2].partition("\n## 1.4.1")[0] + collapsed = " ".join(unreleased.split()) + self.assertIn("16 MiB", collapsed) + self.assertIn("provider-manifest reader", collapsed) + self.assertIn("doc_ref", collapsed) + self.assertIn("__tests__", collapsed) + self.assertNotIn("16 MiB", " ".join(v142_section.split())) + self.assertNotIn("doc_ref", v142_section) def test_changelog_v141_section_is_marked_published_not_pending(self): changelog = _read("CHANGELOG.md") @@ -500,6 +515,56 @@ def test_graphify_evaluation_is_framed_as_a_dated_historical_record(self): # The recorded benchmark evidence is preserved, not rewritten. self.assertIn("Clean-room experiment", evaluation) + def test_graphify_docs_separate_the_published_package_from_current_main(self): + """v1.4.2 ships the original integration; #1007's fixes are only on main.""" + setup = " ".join(_read("docs/graphify-setup.md").split()) + self.assertIn("Published `v1.4.2` versus current `main`", setup) + self.assertIn("/pull/1007", setup) + # The boundary is stated in both directions. + self.assertIn("merged to `main`", setup) + self.assertIn("none of it is in the published `v1.4.2` package", setup) + # An upgrade alone does not repair a generation built earlier. + self.assertIn("does not repair a generation you already built", setup) + self.assertIn("context-graph refresh", setup) + + roadmap = " ".join(_read("docs/current-state-and-roadmap.md").split()) + self.assertIn("/pull/1007", roadmap) + self.assertIn("the published `v1.4.2` package does not contain them", roadmap) + + def test_no_current_doc_calls_1007_open_or_unmerged(self): + """#1007 merged at b863e638. Nothing current may still call it open.""" + stale = ( + "separate open pull request", + "it is not merged", + "is not merged and not released", + "#1007 is open", + "#1007 remains open", + "#1007 stays open", + "pending #1007", + ) + for relative in CURRENT_FACING_DOCS + ( + "docs/graphify-evaluation.md", + "docs/context-graph-lifecycle.md", + "docs/context-graph-queries.md", + ): + collapsed = " ".join(_read(relative).split()).lower() + for phrase in stale: + with self.subTest(doc=relative, phrase=phrase): + self.assertNotIn(phrase, collapsed) + + def test_graphify_provider_pin_is_unchanged_by_the_compatibility_work(self): + """#1007 is a Code Mower fix, not a provider upgrade.""" + setup = " ".join(_read("docs/graphify-setup.md").split()) + self.assertIn("graphifyy", setup) + self.assertIn("0.9.58", setup) + self.assertIn( + "e239803288e91c723d6e30540860bd6d5a1dc3f0914b9fc1104b0233e98aaeb8", setup + ) + self.assertIn("not a Graphify upgrade", setup) + for other in ("0.9.59", "0.9.60", "0.10.", "1.0.0"): + with self.subTest(version=other): + self.assertNotIn(f"graphifyy=={other}", setup) + def test_board_demo_does_not_claim_serve_opens_a_browser(self): demo = " ".join(_read("examples/board-demo/README.md").split()) self.assertIn("It does not open a browser.", demo) From 4129cb65d81334be09451d99ef2c968599cf7d4f Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Thu, 17 Sep 2026 00:59:29 -0700 Subject: [PATCH 4/6] Docs: scope the v1.4.2 Graphify boundary and narrow the rebuild claim Two documentation inaccuracies found in root review, both in the post-v1.4.2 Graphify guidance. "Everything above describes that package" was false. PR #1007 added the language-extras and runtime-ownership paragraphs to "Separate acquisition environment", which sits above the boundary section, so part of "above" describes current main rather than the published v1.4.2 package. The claim is now scoped to the base setup and ramp-up -- acquisition, the separate contained offline build, and steps 1 through 7 -- and the two #1007 paragraphs carry an explicit post-v1.4.2 marker where a reader meets them, which the boundary section names. The rebuild guidance implied every generation built before the next release must be rebuilt. It is narrowed to the generations #1007's compatibility gaps actually affected: most often an older frontend generation left partial, whose oversized provider manifest was refused or whose inputs a missing language parser could not process. A generation "context-graph status --json" already reports usable needs no rebuild. Applied in docs/graphify-setup.md, README.md, and docs/current-state-and-roadmap.md; the changelog and release records carried no equivalent claim. Two tests in tests/test_release_v142.py enforce the boundary: one rejects any "everything above describes that package" spelling and requires the scoped claim plus the in-place post-v1.4.2 marker above the boundary section, the other rejects blanket rebuild-everything-built-before wording across all three pages and requires each to tie the rebuild to the partial state and exempt a generation status already reports usable. Both fail against the pre-fix text. No product code changed. Co-Authored-By: Claude Opus 5 --- README.md | 6 ++- docs/current-state-and-roadmap.md | 10 +++-- docs/graphify-setup.md | 31 ++++++++++---- tests/test_release_v142.py | 70 +++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 32f4a31a..d10ccafa 100644 --- a/README.md +++ b/README.md @@ -302,8 +302,10 @@ The published v1.4.2 package contains that originally shipped integration. Further real-pilot compatibility fixes -- a bounded provider-manifest reader, `doc_ref` exclusions, and JavaScript/TypeScript test-convention recognition -- merged to `main` after the release and are intended for the next appropriate -release. The accepted `0.9.58` provider pin is unchanged, and a generation built -before that release has to be rebuilt explicitly; upgrading does not repair one. +release. The accepted `0.9.58` provider pin is unchanged. Upgrading never +repairs a generation already built, so a generation those gaps left `partial` -- +most often an older partial frontend generation -- has to be rebuilt explicitly; +one `code-mower context-graph status --json` already reports usable does not. See [Optional Graphify Setup](https://github.com/codemower-ai/code-mower/blob/main/docs/graphify-setup.md#published-v142-versus-current-main). ## Documentation diff --git a/docs/current-state-and-roadmap.md b/docs/current-state-and-roadmap.md index b40d1f50..235a545d 100644 --- a/docs/current-state-and-roadmap.md +++ b/docs/current-state-and-roadmap.md @@ -237,9 +237,13 @@ explicit refusal of an oversized provider manifest, `doc_ref` nodes as declared non-code exclusions, and JavaScript/TypeScript test-convention and `imports` recognition in `related_tests`. They are on `main` and intended for the next appropriate release; the published `v1.4.2` package does not contain them. The -accepted `0.9.58` provider pin is unchanged, and a generation built before that -future release must be rebuilt explicitly -- upgrading Code Mower does not -repair one, because a published generation is never rewritten in place. +accepted `0.9.58` provider pin is unchanged. Because a published generation is +never rewritten in place, upgrading Code Mower repairs no generation already +built -- but only the generations those compatibility gaps actually affected +need rebuilding. A generation they left `partial`, most often an older partial +frontend generation, must be rebuilt explicitly with +`code-mower context-graph refresh`; one `code-mower context-graph status --json` +already reports usable does not. ### 3. Board Clarity And Session Visibility -- shipped in `v1.4.2` ([#945](https://github.com/codemower-ai/code-mower/issues/945) / release [#952](https://github.com/codemower-ai/code-mower/issues/952)) diff --git a/docs/graphify-setup.md b/docs/graphify-setup.md index 5dcd9419..f88efc0b 100644 --- a/docs/graphify-setup.md +++ b/docs/graphify-setup.md @@ -86,6 +86,11 @@ configuration files with `PIP_CONFIG_FILE=/dev/null`. `--isolated` alone still permits global/site configuration and a file selected by `PIP_CONFIG_FILE`; those sources must not add an alternate index or local dependency source. +The next two paragraphs are **post-`v1.4.2`**: they describe current `main` and +arrived with [PR #1007](https://github.com/codemower-ai/code-mower/pull/1007), +so they are not part of the published `v1.4.2` package. See +[Published `v1.4.2` versus current `main`](#published-v142-versus-current-main). + Install any required language extras into this same separate environment before the contained build. For SQL inputs, select `[sql]` on the verified local wheel using the same canonical-index restrictions; keep the accepted provider version @@ -221,8 +226,13 @@ to keep or delete separately; Code Mower never touches it. ## Published `v1.4.2` versus current `main` The published `v1.4.2` package on the package index contains the optional -Graphify integration exactly as it originally shipped. Everything above -describes that package. +Graphify integration exactly as it originally shipped. The base setup and +ramp-up above -- acquisition, the separate contained offline build, and steps 1 +through 7 -- describe that published package. The paragraphs above that are +explicitly marked post-`v1.4.2` describe current `main` instead: the +language-extras and runtime-ownership paragraphs under +[Separate acquisition environment](#separate-acquisition-environment) are the +only ones so marked today. [PR #1007](https://github.com/codemower-ai/code-mower/pull/1007) has since merged to `main` with further real-pilot compatibility fixes: a bounded 16 MiB @@ -241,10 +251,15 @@ not a Graphify upgrade: `graphifyy` `0.9.58` and the recorded wheel digest above stay exactly as they are. Because a published generation is never rewritten in place, installing that -later release does not repair a generation you already built. A generation built -before it can be partial -- a frontend inventory whose oversized provider -manifest was refused, or inputs left unprocessed by a missing language parser. -After upgrading, rebuild it explicitly with `code-mower context-graph refresh` +later release does not repair a generation you already built. That matters only +for a generation one of #1007's compatibility gaps actually affected -- most +often an older frontend generation left **partial**: one whose oversized +provider manifest was refused, or one whose inputs a missing language parser +could not process. Those are the generations to rebuild. + +This is not a blanket rebuild of everything built before that future release. +Ask `code-mower context-graph status --json` first: a generation it already +reports usable is unaffected and needs no rebuild. If it reports `partial`, +rebuild that generation explicitly with `code-mower context-graph refresh` (step 6), which publishes a new generation at the same revision, then confirm -`code-mower context-graph status --json` reports it usable rather than -`partial`. +`status` reports it usable rather than `partial`. diff --git a/tests/test_release_v142.py b/tests/test_release_v142.py index 37222daf..3d31030d 100644 --- a/tests/test_release_v142.py +++ b/tests/test_release_v142.py @@ -531,6 +531,76 @@ def test_graphify_docs_separate_the_published_package_from_current_main(self): self.assertIn("/pull/1007", roadmap) self.assertIn("the published `v1.4.2` package does not contain them", roadmap) + def test_graphify_setup_does_not_claim_every_paragraph_is_the_published_package(self): + """#1007's paragraphs sit above the boundary section, so "everything + above" would be false. The page must scope the claim to the base setup + and ramp-up, and mark the post-v1.4.2 paragraphs where they appear.""" + raw = _read("docs/graphify-setup.md") + setup = " ".join(raw.split()) + # The false blanket claim must not come back in any spelling. + for blanket in ("Everything above describes that package", + "Everything above describes the published", + "All of the above describes that package"): + with self.subTest(phrase=blanket): + self.assertNotIn(blanket.lower(), setup.lower()) + # The published package is claimed only for the base setup and ramp-up. + self.assertIn("The base setup and ramp-up above", setup) + self.assertIn("describe that published package", setup) + # The #1007 paragraphs are marked where a reader meets them, above the + # boundary section, and the boundary section points back at that mark. + marker = "The next two paragraphs are **post-`v1.4.2`**" + self.assertIn(marker, setup) + self.assertLess( + raw.index("The next two paragraphs are"), + raw.index("## Published `v1.4.2` versus current `main`"), + "the post-v1.4.2 marker must precede the boundary section it explains", + ) + self.assertIn("explicitly marked post-`v1.4.2` describe current `main`", setup) + # Both #1007 paragraphs still sit under the acquisition heading the + # boundary section names. + acquisition = raw.split("## Separate acquisition environment", 1)[1] + acquisition = acquisition.split("## Separate contained offline build", 1)[0] + self.assertIn("Install any required language extras", acquisition) + self.assertIn("If runtime ownership checks refuse", acquisition) + self.assertIn("The next two paragraphs are", acquisition) + + def test_rebuild_guidance_is_scoped_to_generations_the_1007_gaps_affected(self): + """Not every generation built before the next release needs a rebuild -- + only one the #1007 compatibility gaps left partial.""" + pages = { + "docs/graphify-setup.md": _read("docs/graphify-setup.md"), + "README.md": _read("README.md"), + "docs/current-state-and-roadmap.md": _read( + "docs/current-state-and-roadmap.md" + ), + } + # Wording that tells every reader to rebuild regardless of state. + overclaims = ( + "a generation built before that release has to be rebuilt explicitly", + "a generation built before that future release must be rebuilt", + "every generation built before", + "all generations built before", + "any generation built before that release must be rebuilt", + ) + for relative, raw in pages.items(): + collapsed = " ".join(raw.split()).lower() + for phrase in overclaims: + with self.subTest(doc=relative, phrase=phrase): + self.assertNotIn(phrase, collapsed) + with self.subTest(doc=relative, requirement="partial-scoped"): + # The rebuild is tied to the partial state, not to a build date. + self.assertIn("partial", collapsed) + self.assertIn("frontend generation", collapsed) + with self.subTest(doc=relative, requirement="usable-is-exempt"): + # A generation status already reports usable is left alone. + self.assertIn("context-graph status --json", collapsed) + self.assertIn("already reports usable", collapsed) + + setup = " ".join(pages["docs/graphify-setup.md"].split()) + self.assertIn("This is not a blanket rebuild", setup) + self.assertIn("is unaffected and needs no rebuild", setup) + self.assertIn("If it reports `partial`", setup) + def test_no_current_doc_calls_1007_open_or_unmerged(self): """#1007 merged at b863e638. Nothing current may still call it open.""" stale = ( From 58976cc7700f354f7ad9a09de59cc25cbc39eec7 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Thu, 17 Sep 2026 01:06:45 -0700 Subject: [PATCH 5/6] Require a path-segment boundary for README public-doc links _links_to_repository_doc accepted any destination whose path ended with the required relative path, so docs/OTHER_SUPPORT.md satisfied the SUPPORT.md requirement and the public-docs-linked-from-readme check could pass on a README that never links the real file. Accept a destination only when its path is exactly the relative path or ends with "/" + the relative path. Both spellings the check exists for still pass: the relative form in the repository README and the absolute GitHub blob URL the packaged long description needs. Query strings are now dropped alongside fragments, since neither changes which file the destination resolves to. Co-Authored-By: Claude Opus 5 --- src/code_mower/release_readiness.py | 10 +++- tests/test_release_hygiene.py | 77 +++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/code_mower/release_readiness.py b/src/code_mower/release_readiness.py index 30c9d164..7e5f94b2 100644 --- a/src/code_mower/release_readiness.py +++ b/src/code_mower/release_readiness.py @@ -111,13 +111,21 @@ def _links_to_repository_doc(markdown: str, label: str, relative_path: str) -> b destination resolves against the package index rather than the repository, so repository links there are absolute GitHub URLs. Both spellings satisfy this check; only the label and the file it lands on are required. + + The destination has to land on ``relative_path`` at a path-segment + boundary, so a neighbouring file whose name merely ends in the required + one -- ``docs/OTHER_SUPPORT.md`` for ``SUPPORT.md`` -- does not count. A + query string or fragment decorates the destination without changing which + file it resolves to, so both are dropped before the comparison. """ pattern = re.compile( r"\[" + re.escape(label) + r"\]\(\s*]+)>?[^)]*\)" ) + nested_suffix = "/" + relative_path for destination in pattern.findall(markdown): - if destination.partition("#")[0].rstrip("/").endswith(relative_path): + path = destination.partition("#")[0].partition("?")[0].rstrip("/") + if path == relative_path or path.endswith(nested_suffix): return True return False diff --git a/tests/test_release_hygiene.py b/tests/test_release_hygiene.py index e3f28baf..373a2c17 100644 --- a/tests/test_release_hygiene.py +++ b/tests/test_release_hygiene.py @@ -10903,6 +10903,83 @@ def test_public_hygiene_checks_fail_when_docs_or_links_are_missing(self) -> None self.assertEqual(check_ids["public-docs-linked-from-readme"]["status"], "fail") self.assertEqual(check_ids["public-support-redaction-guidance"]["status"], "fail") + def test_public_doc_link_requires_a_path_segment_boundary(self) -> None: + accepted = ( + "SUPPORT.md", + "./SUPPORT.md", + "/SUPPORT.md", + "SUPPORT.md#getting-help", + "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md", + "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md?plain=1", + "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md#getting-help", + ) + for destination in accepted: + with self.subTest(destination=destination): + self.assertTrue( + release_readiness._links_to_repository_doc( + f"See [Support]({destination}) for help.\n", + "Support", + "SUPPORT.md", + ) + ) + + rejected = ( + "OTHER_SUPPORT.md", + "docs/OTHER_SUPPORT.md", + "nested/docs/OTHER_SUPPORT.md", + "https://github.com/codemower-ai/code-mower/blob/main/docs/OTHER_SUPPORT.md", + "OTHER_SUPPORT.md#getting-help", + "OTHER_SUPPORT.md?plain=1", + ) + for destination in rejected: + with self.subTest(destination=destination): + self.assertFalse( + release_readiness._links_to_repository_doc( + f"See [Support]({destination}) for help.\n", + "Support", + "SUPPORT.md", + ) + ) + + def test_public_docs_link_check_rejects_lookalike_destinations(self) -> None: + readmes = { + "lookalike": "\n".join( + [ + "[Support](docs/OTHER_SUPPORT.md)", + "[Security Policy](SECURITY.md)", + "[Code of Conduct](CODE_OF_CONDUCT.md)", + ] + ), + "exact": "\n".join( + [ + "[Support](SUPPORT.md)", + "[Security Policy](SECURITY.md)", + "[Code of Conduct](CODE_OF_CONDUCT.md)", + ] + ), + "absolute": "\n".join( + [ + "[Support](https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md)", + "[Security Policy](https://github.com/codemower-ai/code-mower/blob/main/SECURITY.md)", + "[Code of Conduct](https://github.com/codemower-ai/code-mower/blob/main/CODE_OF_CONDUCT.md)", + ] + ), + } + expected = {"lookalike": "fail", "exact": "pass", "absolute": "pass"} + + for label, readme in readmes.items(): + with self.subTest(readme=label), tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + (repo / "README.md").write_text(readme + "\n", encoding="utf-8") + + payload = release_readiness.render_release_readiness(repo) + + check_ids = {check["id"]: check for check in payload["checks"]} + self.assertEqual( + check_ids["public-docs-linked-from-readme"]["status"], + expected[label], + ) + def test_public_redaction_guidance_requires_support_and_conduct(self) -> None: with tempfile.TemporaryDirectory() as tmp: repo = Path(tmp) From 05b01f5c698c16669912ac5dfaa0d19721e02408 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Thu, 17 Sep 2026 01:15:32 -0700 Subject: [PATCH 6/6] Resolve README public-doc links instead of matching their suffix The path-segment boundary added in 58976cc7 still let two destinations satisfy the public-docs-linked-from-readme check without addressing the required file: a nested relative path that does not exist in this tree, such as docs/SUPPORT.md for SUPPORT.md, and any unrelated absolute URL whose path ends in /SUPPORT.md, including another owner's or another repository's GitHub URL. Resolve the destination to the one repository-relative path it lands on, then require that path to equal the required document. A relative destination is normalized against the repository root, where README.md sits, so docs/SUPPORT.md resolves to itself and no longer matches. An absolute destination resolves only when it is this repository's own GitHub URL -- github.com/codemower-ai/code-mower/{blob,raw}// -- reduced to the path under its ref; every other host, owner, and repository resolves to nothing. Query strings and fragments are still dropped first, since neither changes which file is addressed. A site-root /SUPPORT.md no longer passes. GitHub does not resolve a site-root path against the repository, so that spelling was a broken link the check was accepting; nothing in this repository uses it. Co-Authored-By: Claude Opus 5 --- src/code_mower/release_readiness.py | 76 ++++++++++++++++---- tests/test_release_hygiene.py | 103 +++++++++++++++++++++------- 2 files changed, 142 insertions(+), 37 deletions(-) diff --git a/src/code_mower/release_readiness.py b/src/code_mower/release_readiness.py index 7e5f94b2..737668f9 100644 --- a/src/code_mower/release_readiness.py +++ b/src/code_mower/release_readiness.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import posixpath import re import tempfile from pathlib import Path @@ -60,6 +61,17 @@ ".github/PULL_REQUEST_TEMPLATE.md", ".github/dependabot.yml", ) +REPOSITORY_SLUG = "codemower-ai/code-mower" +# The absolute spellings that address a file inside this repository's tree: +# ``blob`` renders it, ``raw`` serves it, and the segment between the view and +# the path is the git ref. Any other host, owner, or repository addresses a +# different tree, however its URL happens to end. +_REPOSITORY_FILE_URL_PATTERN = re.compile( + r"^https://github\.com/" + + re.escape(REPOSITORY_SLUG) + + r"/(?:blob|raw)/[^/]+/(?P.+)$" +) +_URI_SCHEME_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:") PACKAGE_INDEX_SETUP_URLS = { "github_environments": ( "https://github.com/codemower-ai/code-mower/settings/environments" @@ -104,30 +116,66 @@ def _read_text_if_exists(path: Path) -> str: return "" +def _repository_destination_path(destination: str) -> str: + """The repository-relative path ``destination`` addresses, else ``""``. + + A destination is resolved, not pattern-matched, so the answer is the one + file a reader lands on. Two spellings resolve: + + * a relative path, normalized against the repository root, which is where + README.md sits; + * an absolute GitHub URL for *this* repository, reduced to the path under + its ref. + + Everything else -- another repository, another host, a ``mailto:``, a + scheme-relative ``//host/...``, or a site-root ``/SUPPORT.md`` that GitHub + does not resolve against the repository -- addresses nothing in this tree + and returns ``""``. A query string or fragment only decorates a + destination, so both are dropped before resolving. + """ + + trimmed = destination.strip().partition("#")[0].partition("?")[0] + if not trimmed: + return "" + + repository_url = _REPOSITORY_FILE_URL_PATTERN.match(trimmed) + if repository_url: + candidate = repository_url.group("path") + elif ( + _URI_SCHEME_PATTERN.match(trimmed) + or trimmed.startswith("//") + or trimmed.startswith("/") + ): + return "" + else: + candidate = trimmed + + normalized = posixpath.normpath(candidate) + if normalized in {".", ".."} or normalized.startswith("../"): + return "" + return normalized + + def _links_to_repository_doc(markdown: str, label: str, relative_path: str) -> bool: """Whether ``markdown`` links ``label`` at ``relative_path``. README.md is also the built package's long description, where a relative destination resolves against the package index rather than the repository, - so repository links there are absolute GitHub URLs. Both spellings satisfy - this check; only the label and the file it lands on are required. - - The destination has to land on ``relative_path`` at a path-segment - boundary, so a neighbouring file whose name merely ends in the required - one -- ``docs/OTHER_SUPPORT.md`` for ``SUPPORT.md`` -- does not count. A - query string or fragment decorates the destination without changing which - file it resolves to, so both are dropped before the comparison. + so repository links there are absolute GitHub URLs for this repository. + Both spellings satisfy this check, and each has to resolve to + ``relative_path`` itself -- a neighbouring or nested file such as + ``docs/SUPPORT.md`` is a different document, and an unrelated URL that + merely ends in ``/SUPPORT.md`` is a different repository. """ pattern = re.compile( r"\[" + re.escape(label) + r"\]\(\s*]+)>?[^)]*\)" ) - nested_suffix = "/" + relative_path - for destination in pattern.findall(markdown): - path = destination.partition("#")[0].partition("?")[0].rstrip("/") - if path == relative_path or path.endswith(nested_suffix): - return True - return False + required = posixpath.normpath(relative_path) + return any( + _repository_destination_path(destination) == required + for destination in pattern.findall(markdown) + ) def _python_package_version(repo_path: Path) -> str: diff --git a/tests/test_release_hygiene.py b/tests/test_release_hygiene.py index 373a2c17..a1b68c69 100644 --- a/tests/test_release_hygiene.py +++ b/tests/test_release_hygiene.py @@ -10903,18 +10903,35 @@ def test_public_hygiene_checks_fail_when_docs_or_links_are_missing(self) -> None self.assertEqual(check_ids["public-docs-linked-from-readme"]["status"], "fail") self.assertEqual(check_ids["public-support-redaction-guidance"]["status"], "fail") - def test_public_doc_link_requires_a_path_segment_boundary(self) -> None: - accepted = ( - "SUPPORT.md", - "./SUPPORT.md", - "/SUPPORT.md", - "SUPPORT.md#getting-help", - "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md", - "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md?plain=1", - "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md#getting-help", - ) - for destination in accepted: - with self.subTest(destination=destination): + def test_public_doc_link_resolves_the_destination_to_the_actual_document( + self, + ) -> None: + accepted = { + "relative": "SUPPORT.md", + "relative_dot_slash": "./SUPPORT.md", + "relative_traversal_that_lands_on_the_file": "docs/../SUPPORT.md", + "relative_trailing_slash": "SUPPORT.md/", + "relative_fragment": "SUPPORT.md#getting-help", + "relative_query": "SUPPORT.md?plain=1", + "absolute_blob": ( + "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md" + ), + "absolute_blob_on_a_tag": ( + "https://github.com/codemower-ai/code-mower/blob/v1.4.2/SUPPORT.md" + ), + "absolute_raw": ( + "https://github.com/codemower-ai/code-mower/raw/main/SUPPORT.md" + ), + "absolute_query": ( + "https://github.com/codemower-ai/code-mower/blob/main/SUPPORT.md?plain=1" + ), + "absolute_fragment": ( + "https://github.com/codemower-ai/code-mower/blob/main/" + "SUPPORT.md#getting-help" + ), + } + for label, destination in accepted.items(): + with self.subTest(accepted=label): self.assertTrue( release_readiness._links_to_repository_doc( f"See [Support]({destination}) for help.\n", @@ -10923,16 +10940,36 @@ def test_public_doc_link_requires_a_path_segment_boundary(self) -> None: ) ) - rejected = ( - "OTHER_SUPPORT.md", - "docs/OTHER_SUPPORT.md", - "nested/docs/OTHER_SUPPORT.md", - "https://github.com/codemower-ai/code-mower/blob/main/docs/OTHER_SUPPORT.md", - "OTHER_SUPPORT.md#getting-help", - "OTHER_SUPPORT.md?plain=1", - ) - for destination in rejected: - with self.subTest(destination=destination): + rejected = { + # A nested path is a different document, and nothing in this + # repository puts SUPPORT.md under docs/. + "nested_relative_path_that_does_not_exist": "docs/SUPPORT.md", + "nested_absolute_path_that_does_not_exist": ( + "https://github.com/codemower-ai/code-mower/blob/main/docs/SUPPORT.md" + ), + # An unrelated URL that merely ends in the required filename. + "unrelated_host": "https://example.com/SUPPORT.md", + "unrelated_host_nested": "https://example.com/code-mower/SUPPORT.md", + "another_github_owner": ( + "https://github.com/someone-else/code-mower/blob/main/SUPPORT.md" + ), + "another_github_repository": ( + "https://github.com/codemower-ai/other-repo/blob/main/SUPPORT.md" + ), + "scheme_relative": "//github.com/codemower-ai/code-mower/blob/main/SUPPORT.md", + # GitHub does not resolve a site-root path against the repository. + "site_root": "/SUPPORT.md", + # Names that merely end in the required one. + "sibling_suffix": "OTHER_SUPPORT.md", + "nested_sibling_suffix": "docs/OTHER_SUPPORT.md", + "deeply_nested_sibling_suffix": "nested/docs/OTHER_SUPPORT.md", + "sibling_suffix_fragment": "OTHER_SUPPORT.md#getting-help", + "sibling_suffix_query": "OTHER_SUPPORT.md?plain=1", + # Escapes the repository root README.md sits at. + "parent_traversal": "../SUPPORT.md", + } + for label, destination in rejected.items(): + with self.subTest(rejected=label): self.assertFalse( release_readiness._links_to_repository_doc( f"See [Support]({destination}) for help.\n", @@ -10950,6 +10987,20 @@ def test_public_docs_link_check_rejects_lookalike_destinations(self) -> None: "[Code of Conduct](CODE_OF_CONDUCT.md)", ] ), + "nested_path_that_does_not_exist": "\n".join( + [ + "[Support](docs/SUPPORT.md)", + "[Security Policy](SECURITY.md)", + "[Code of Conduct](CODE_OF_CONDUCT.md)", + ] + ), + "foreign_url": "\n".join( + [ + "[Support](https://example.com/SUPPORT.md)", + "[Security Policy](SECURITY.md)", + "[Code of Conduct](CODE_OF_CONDUCT.md)", + ] + ), "exact": "\n".join( [ "[Support](SUPPORT.md)", @@ -10965,7 +11016,13 @@ def test_public_docs_link_check_rejects_lookalike_destinations(self) -> None: ] ), } - expected = {"lookalike": "fail", "exact": "pass", "absolute": "pass"} + expected = { + "lookalike": "fail", + "nested_path_that_does_not_exist": "fail", + "foreign_url": "fail", + "exact": "pass", + "absolute": "pass", + } for label, readme in readmes.items(): with self.subTest(readme=label), tempfile.TemporaryDirectory() as tmp: