willow-reconciler 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- willow_reconciler-0.2.0/.github/FUNDING.yml +1 -0
- willow_reconciler-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- willow_reconciler-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- willow_reconciler-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
- willow_reconciler-0.2.0/.github/pull_request_template.md +31 -0
- willow_reconciler-0.2.0/.github/workflows/release-please.yml +64 -0
- willow_reconciler-0.2.0/.github/workflows/release.yml +68 -0
- willow_reconciler-0.2.0/.github/workflows/tests.yml +39 -0
- willow_reconciler-0.2.0/.github/workflows/trailers.yml +41 -0
- willow_reconciler-0.2.0/.gitignore +5 -0
- willow_reconciler-0.2.0/.release-please-manifest.json +3 -0
- willow_reconciler-0.2.0/CHANGELOG.md +35 -0
- willow_reconciler-0.2.0/CODE_OF_CONDUCT.md +39 -0
- willow_reconciler-0.2.0/CONTRIBUTING.md +44 -0
- willow_reconciler-0.2.0/CONVENTION.md +85 -0
- willow_reconciler-0.2.0/LICENSE +202 -0
- willow_reconciler-0.2.0/PKG-INFO +17 -0
- willow_reconciler-0.2.0/README.md +125 -0
- willow_reconciler-0.2.0/SECURITY.md +35 -0
- willow_reconciler-0.2.0/SUPPORT.md +10 -0
- willow_reconciler-0.2.0/docs/ideas.md +77 -0
- willow_reconciler-0.2.0/pyproject.toml +33 -0
- willow_reconciler-0.2.0/reconciler/__init__.py +18 -0
- willow_reconciler-0.2.0/reconciler/classify.py +167 -0
- willow_reconciler-0.2.0/reconciler/cli.py +265 -0
- willow_reconciler-0.2.0/reconciler/emit.py +51 -0
- willow_reconciler-0.2.0/reconciler/gitevidence.py +213 -0
- willow_reconciler-0.2.0/reconciler/hooks.py +195 -0
- willow_reconciler-0.2.0/reconciler/ids.py +17 -0
- willow_reconciler-0.2.0/reconciler/ledger.py +102 -0
- willow_reconciler-0.2.0/reconciler/parse.py +51 -0
- willow_reconciler-0.2.0/reconciler/validate.py +119 -0
- willow_reconciler-0.2.0/reconciler/verify.py +75 -0
- willow_reconciler-0.2.0/release-please-config.json +26 -0
- willow_reconciler-0.2.0/tests/test_classify.py +216 -0
- willow_reconciler-0.2.0/tests/test_cli.py +130 -0
- willow_reconciler-0.2.0/tests/test_emit.py +56 -0
- willow_reconciler-0.2.0/tests/test_gitevidence.py +182 -0
- willow_reconciler-0.2.0/tests/test_hooks.py +199 -0
- willow_reconciler-0.2.0/tests/test_ids.py +18 -0
- willow_reconciler-0.2.0/tests/test_ledger.py +65 -0
- willow_reconciler-0.2.0/tests/test_parse.py +60 -0
- willow_reconciler-0.2.0/tests/test_validation.py +110 -0
- willow_reconciler-0.2.0/tests/test_verify.py +82 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github: [rudi193-cmd]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something does the wrong thing
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What happened
|
|
8
|
+
|
|
9
|
+
<!-- The wrong behavior, plainly. -->
|
|
10
|
+
|
|
11
|
+
## What should have happened
|
|
12
|
+
|
|
13
|
+
<!-- The right behavior, plainly. -->
|
|
14
|
+
|
|
15
|
+
## Receipts
|
|
16
|
+
|
|
17
|
+
<!-- How to reproduce: exact commands, versions, OS. Paste real
|
|
18
|
+
output, not a paraphrase. If data/privacy is involved, redact
|
|
19
|
+
values but keep the shape. -->
|
|
20
|
+
|
|
21
|
+
## Blast radius
|
|
22
|
+
|
|
23
|
+
<!-- What it breaks, who hits it, any workaround you found. -->
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Something this tool should do
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Bite
|
|
8
|
+
|
|
9
|
+
<!-- One sentence — the single outcome you want. -->
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
<!-- The gap or friction that makes this worth building. A real
|
|
14
|
+
scenario beats an abstraction. -->
|
|
15
|
+
|
|
16
|
+
## Out of scope
|
|
17
|
+
|
|
18
|
+
<!-- What this deliberately is NOT asking for, if you can see the line. -->
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Bite
|
|
2
|
+
|
|
3
|
+
<!-- One sentence — the single outcome this PR delivers.
|
|
4
|
+
Follow-up to a merged PR? Say so: "Follow-up to #NN (merged)". -->
|
|
5
|
+
|
|
6
|
+
## What was done
|
|
7
|
+
|
|
8
|
+
<!-- Bullets, boldest fact first. If two unrelated changes rode this
|
|
9
|
+
branch, flag them separately so they can be reviewed (or split) cleanly. -->
|
|
10
|
+
|
|
11
|
+
## Evidence
|
|
12
|
+
|
|
13
|
+
<!-- Receipts, not claims. Check only what you actually ran, and state the result. -->
|
|
14
|
+
- [ ] Tests: `python -m pytest tests/ -q` → N passed
|
|
15
|
+
- [ ] Gates: `<gate command>` → clean
|
|
16
|
+
- [ ] Driven for real: <!-- what you exercised end-to-end, and what you saw -->
|
|
17
|
+
|
|
18
|
+
## Idea-Id trailer
|
|
19
|
+
|
|
20
|
+
<!-- If a commit here lands an idea recorded in a fleet doc, it carries an
|
|
21
|
+
`Idea-Id: <corpus>-<docslug>-<num>` trailer (add `Idea-Status: partial`
|
|
22
|
+
if it only partly lands it). See CONVENTION.md. Tick when it applies. -->
|
|
23
|
+
- [ ] Not applicable, or the trailer is present on the landing commit(s).
|
|
24
|
+
|
|
25
|
+
## Out of scope
|
|
26
|
+
|
|
27
|
+
<!-- What this deliberately does not do, and where that work lives. -->
|
|
28
|
+
|
|
29
|
+
## Next bite
|
|
30
|
+
|
|
31
|
+
<!-- The single next bite this opens, if any. -->
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch: {}
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
release-please:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
steps:
|
|
21
|
+
- name: Mint a willow-ci installation token
|
|
22
|
+
id: app-token
|
|
23
|
+
uses: actions/create-github-app-token@v3
|
|
24
|
+
with:
|
|
25
|
+
app-id: ${{ vars.WILLOW_CI_APP_ID }}
|
|
26
|
+
private-key: ${{ secrets.WILLOW_CI_PRIVATE_KEY }}
|
|
27
|
+
|
|
28
|
+
- uses: actions/checkout@v7
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
fetch-tags: true
|
|
32
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
33
|
+
|
|
34
|
+
- uses: googleapis/release-please-action@v5
|
|
35
|
+
with:
|
|
36
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
37
|
+
config-file: release-please-config.json
|
|
38
|
+
manifest-file: .release-please-manifest.json
|
|
39
|
+
|
|
40
|
+
- name: Arm auto-merge on the release PR
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
43
|
+
REPO: ${{ github.repository }}
|
|
44
|
+
run: |
|
|
45
|
+
set -euo pipefail
|
|
46
|
+
pr=$(gh pr list --repo "$REPO" --state open --json number,headRefName \
|
|
47
|
+
--jq '[.[] | select(.headRefName | startswith("release-please--"))]
|
|
48
|
+
| first | .number // empty')
|
|
49
|
+
if [ -z "$pr" ]; then
|
|
50
|
+
echo "No open release PR — nothing releasable since the last tag."
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
# Arming can fail for reasons that are not this job's fault (most
|
|
54
|
+
# commonly: the default branch has no required status checks, so
|
|
55
|
+
# GitHub refuses enablePullRequestAutoMerge). That must not fail the
|
|
56
|
+
# release-please job — but it must not be silent either. A bare
|
|
57
|
+
# `|| true` hid exactly this for six releases, during which every
|
|
58
|
+
# release PR merged immediately and published to PyPI before CI ran.
|
|
59
|
+
if ! out=$(gh pr merge --auto --merge "$pr" --repo "$REPO" 2>&1); then
|
|
60
|
+
echo "::warning::could not arm auto-merge on PR #${pr}: ${out}"
|
|
61
|
+
echo "::warning::the release PR is open and must be merged by hand"
|
|
62
|
+
else
|
|
63
|
+
echo "auto-merge armed on PR #${pr}"
|
|
64
|
+
fi
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a version tag is pushed (e.g. `git tag v0.2.0 && git push origin v0.2.0`).
|
|
4
|
+
# release-please (release-please.yml) opens the release PR, bumps the version in
|
|
5
|
+
# pyproject.toml (release-type: python) and CHANGELOG.md, and tags on merge.
|
|
6
|
+
#
|
|
7
|
+
# Auth: Trusted Publishing (OIDC). Register on PyPI as:
|
|
8
|
+
# project: willow-reconciler
|
|
9
|
+
# repository: willow-memory/willow-reconciler
|
|
10
|
+
# workflow: release.yml
|
|
11
|
+
# environment: pypi
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
tags:
|
|
16
|
+
- "v*"
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v7
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-python@v7
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.11"
|
|
29
|
+
|
|
30
|
+
- name: Build sdist and wheel
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade build
|
|
33
|
+
python -m build
|
|
34
|
+
|
|
35
|
+
- name: Assert the tag matches the built version
|
|
36
|
+
run: |
|
|
37
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
38
|
+
built="$(ls dist/willow_reconciler-*.tar.gz | sed -E 's|.*/willow_reconciler-(.+)\.tar\.gz|\1|')"
|
|
39
|
+
echo "tag=${tag} built=${built}"
|
|
40
|
+
if [ "$tag" != "$built" ]; then
|
|
41
|
+
echo "::error::tag v${tag} builds version ${built} — refusing to publish"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
- name: Check metadata
|
|
46
|
+
run: |
|
|
47
|
+
python -m pip install --upgrade twine
|
|
48
|
+
twine check dist/*
|
|
49
|
+
|
|
50
|
+
- uses: actions/upload-artifact@v7
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
|
|
55
|
+
publish:
|
|
56
|
+
needs: build
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
environment: pypi
|
|
59
|
+
permissions:
|
|
60
|
+
id-token: write
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/download-artifact@v8
|
|
63
|
+
with:
|
|
64
|
+
name: dist
|
|
65
|
+
path: dist/
|
|
66
|
+
|
|
67
|
+
- name: Publish to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test-matrix:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v7
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install
|
|
24
|
+
run: pip install -e ".[test]"
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: python -m pytest tests/ -q
|
|
28
|
+
|
|
29
|
+
test:
|
|
30
|
+
needs: [test-matrix]
|
|
31
|
+
if: always()
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- name: Assert every matrix leg actually succeeded
|
|
35
|
+
if: ${{ needs.test-matrix.result != 'success' }}
|
|
36
|
+
run: |
|
|
37
|
+
echo "::error::test-matrix did not succeed (result: ${{ needs.test-matrix.result }})"
|
|
38
|
+
exit 1
|
|
39
|
+
- run: echo "all matrix legs green"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Trailers
|
|
2
|
+
|
|
3
|
+
# Guards the join key the classifier trusts most. `reconciler verify` resolves
|
|
4
|
+
# every Idea-Id trailer in this repo's history against docs/ideas.md and exits
|
|
5
|
+
# non-zero on one that names an item the doc does not contain. A dangling
|
|
6
|
+
# trailer is worse than a missing one: rule 2a asserts LANDED from it ahead of
|
|
7
|
+
# every other inferred signal, and nothing else in the rule stack can tell a
|
|
8
|
+
# real id from a plausible-looking dead one.
|
|
9
|
+
#
|
|
10
|
+
# A history with no trailers passes — the convention is prospective by design.
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
push:
|
|
14
|
+
branches: [main]
|
|
15
|
+
pull_request:
|
|
16
|
+
branches: [main]
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
verify-trailers:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v7
|
|
23
|
+
with:
|
|
24
|
+
# verify reads the whole history; the default shallow clone would
|
|
25
|
+
# hide every trailer written before the last commit and pass
|
|
26
|
+
# vacuously.
|
|
27
|
+
fetch-depth: 0
|
|
28
|
+
|
|
29
|
+
- uses: actions/setup-python@v7
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
|
|
33
|
+
- name: Install
|
|
34
|
+
run: pip install -e .
|
|
35
|
+
|
|
36
|
+
- name: Verify Idea-Id trailers resolve
|
|
37
|
+
# --repo is resolved against the package's own location, not the cwd,
|
|
38
|
+
# so this works from anywhere: on a runner the checkout sits at
|
|
39
|
+
# <work>/willow-reconciler/willow-reconciler, whose parent is the
|
|
40
|
+
# fleet root the sibling lookup expects.
|
|
41
|
+
run: reconciler verify --repo willow-reconciler --doc docs/ideas.md
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Conventional Commits](https://www.conventionalcommits.org/) and this file is
|
|
5
|
+
maintained by [release-please](https://github.com/googleapis/release-please).
|
|
6
|
+
|
|
7
|
+
## [0.2.0](https://github.com/willow-memory/willow-reconciler/compare/v0.1.0...v0.2.0) (2026-09-11)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* close the write-side loop for the Idea-Id convention ([717c877](https://github.com/willow-memory/willow-reconciler/commit/717c87786a13df54053ef1c2d13d088517059e74))
|
|
13
|
+
* close the write-side loop for the Idea-Id convention ([d01cdb0](https://github.com/willow-memory/willow-reconciler/commit/d01cdb0af18c1032d83d4e9e0a9a270674bfd140))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* anchor legend-tag regexes so a mid-prose marker is not a tag ([9c8be17](https://github.com/willow-memory/willow-reconciler/commit/9c8be17416898ebc6c7ff80811e9a127ccd47736))
|
|
19
|
+
* close two false-LANDED paths found by adversarial audit ([46d18bd](https://github.com/willow-memory/willow-reconciler/commit/46d18bd5bbc9b4065ddf11a005cddb8df59f66b5))
|
|
20
|
+
|
|
21
|
+
## 0.1.0 (2026-09-11)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
* Initial Slice 0 idea-to-landing reconciler: reads a fleet doc's numbered
|
|
27
|
+
items and the target repo's own git history, then reports — deterministically
|
|
28
|
+
and stdlib-only, with no model call — how many proposed ideas have landed,
|
|
29
|
+
partially landed, or show no evidence of having started.
|
|
30
|
+
* An honest, non-circular hold-out acceptance test (`reconciler validate`
|
|
31
|
+
wiring in `validate.py`): the legend tag is stripped from each hand-tagged
|
|
32
|
+
item before reclassification, so the score measures what the tool can
|
|
33
|
+
actually recover rather than echoing a tag it just read.
|
|
34
|
+
* The `Idea-Id` commit-trailer convention (see `CONVENTION.md`) that gives the
|
|
35
|
+
reconciler a durable join key going forward.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
This organization adopts the [Contributor Covenant, version 2.1][covenant], in full and without
|
|
4
|
+
modification, as the code of conduct for every repository here, including
|
|
5
|
+
willow-memory/willow-reconciler.
|
|
6
|
+
|
|
7
|
+
The canonical text is at
|
|
8
|
+
<https://www.contributor-covenant.org/version/2/1/code_of_conduct/>.
|
|
9
|
+
|
|
10
|
+
In short: participate in a way that would not embarrass you if quoted. Assume good faith,
|
|
11
|
+
disagree about the work rather than the person, and accept that maintainers may decline a
|
|
12
|
+
contribution without that being an insult.
|
|
13
|
+
|
|
14
|
+
## Scope
|
|
15
|
+
|
|
16
|
+
This applies in every space the organization controls — issues, pull requests, discussions,
|
|
17
|
+
commit messages, and code review across all repositories — and whenever someone is representing
|
|
18
|
+
the project in public.
|
|
19
|
+
|
|
20
|
+
## Reporting
|
|
21
|
+
|
|
22
|
+
Report unacceptable behavior to **[@rudi193-cmd](https://github.com/rudi193-cmd)** by opening a
|
|
23
|
+
private report through GitHub's **"Report a vulnerability"** form on this organization's
|
|
24
|
+
[`.github` Security tab][report]. Say plainly that it is a conduct report; it will not be
|
|
25
|
+
treated as a security matter.
|
|
26
|
+
|
|
27
|
+
[report]: https://github.com/willow-memory/.github/security/advisories/new
|
|
28
|
+
|
|
29
|
+
If the report concerns that maintainer, contact any other organization owner directly.
|
|
30
|
+
|
|
31
|
+
## Enforcement
|
|
32
|
+
|
|
33
|
+
Maintainers may take any action they judge proportionate, including editing or removing
|
|
34
|
+
contributions, and temporary or permanent bans from the organization. The Contributor Covenant's
|
|
35
|
+
enforcement guidelines describe the ladder that is normally followed.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
[covenant]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This repo (willow-memory/willow-reconciler) shares one working method with the
|
|
4
|
+
rest of the fleet, whether the contributor is a person or an agent.
|
|
5
|
+
|
|
6
|
+
## The method
|
|
7
|
+
|
|
8
|
+
- **One bite at a time.** A PR delivers one outcome.
|
|
9
|
+
- **Receipts, not claims.** The PR template asks for Evidence — check
|
|
10
|
+
only what you actually ran, and state the result.
|
|
11
|
+
- **Verify in a clean environment.** If you touched dependencies, prove
|
|
12
|
+
the suite in a clean environment before pushing.
|
|
13
|
+
- **Match the house style.** Read the surrounding code first — this is a
|
|
14
|
+
stdlib-only, deterministic, read-only tool; keep it that way (no runtime
|
|
15
|
+
dependencies, no model calls).
|
|
16
|
+
|
|
17
|
+
## Build and test
|
|
18
|
+
|
|
19
|
+
This package is stdlib-only; the only extra is `test` (pytest):
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install -e ".[test]"
|
|
23
|
+
python -m pytest tests/ -q
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Python 3.10+. CI runs the suite across 3.10–3.14 and must be green
|
|
27
|
+
before merge.
|
|
28
|
+
|
|
29
|
+
## The Idea-Id commit-trailer convention
|
|
30
|
+
|
|
31
|
+
This repo's own convention: a commit that lands an idea recorded in a fleet doc
|
|
32
|
+
carries an `Idea-Id: <corpus>-<docslug>-<num>` git trailer (add
|
|
33
|
+
`Idea-Status: partial` when a commit only partly lands it). It is the durable
|
|
34
|
+
join key the reconciler reads. Emit it when a commit lands an idea. See
|
|
35
|
+
[CONVENTION.md](CONVENTION.md) for the full rule and what counts as landing
|
|
36
|
+
evidence.
|
|
37
|
+
|
|
38
|
+
## Practical bits
|
|
39
|
+
|
|
40
|
+
- PRs use the closeout template (Bite / What was done / Evidence /
|
|
41
|
+
Out of scope / Next bite).
|
|
42
|
+
- Security findings go through [SECURITY.md](SECURITY.md), not issues.
|
|
43
|
+
|
|
44
|
+
See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [SUPPORT.md](SUPPORT.md).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# The Idea-Id trailer convention
|
|
2
|
+
|
|
3
|
+
Adopted 2026-09-11 (operator ruling: "adopt the trailers going forward").
|
|
4
|
+
Recorded as governance decision `decision-2026-09-11-adopt-idea-id-trailers`;
|
|
5
|
+
proposed to Nestor as draft `11a1744c-6b83-48f5-9fd8-d7a929b5eabf` (seal pending).
|
|
6
|
+
|
|
7
|
+
## What
|
|
8
|
+
|
|
9
|
+
A commit that lands an idea recorded in a fleet doc carries a git trailer:
|
|
10
|
+
|
|
11
|
+
Idea-Id: <corpus>-<docslug>-<num>
|
|
12
|
+
|
|
13
|
+
matching the id this reconciler derives for that doc entry
|
|
14
|
+
(e.g. `willow-ideas-042` for the 42nd entry of willow-mcp/docs/ideas.md).
|
|
15
|
+
When a commit only partly lands an idea, add:
|
|
16
|
+
|
|
17
|
+
Idea-Status: partial
|
|
18
|
+
|
|
19
|
+
The default (trailer present, no status) means landed.
|
|
20
|
+
|
|
21
|
+
## Why it is prospective, not retroactive
|
|
22
|
+
|
|
23
|
+
willow-reconciler Slice 0 measured, on a held-out split of the *current*
|
|
24
|
+
corpus with tags stripped, a landing-recovery of **0.0**: no durable join
|
|
25
|
+
key was ever written, so deterministic inference recovers nothing, and a
|
|
26
|
+
model-based guess would violate the cheapest-capable rule. The ID's absence
|
|
27
|
+
is itself why "format predicts landing." The fix cannot look backward — it
|
|
28
|
+
starts writing the key now, and this reconciler grows useful as trailers
|
|
29
|
+
accumulate.
|
|
30
|
+
|
|
31
|
+
## Tooling (you should not be typing these by hand)
|
|
32
|
+
|
|
33
|
+
reconciler id --repo <repo> --doc <doc> --num 42 # print the trailer line
|
|
34
|
+
reconciler id --repo <repo> --doc <doc> --grep "text" # ... or find it by text
|
|
35
|
+
reconciler install-hook --repo <repo> # write it automatically
|
|
36
|
+
reconciler verify --repo <repo> --doc <doc> # check they all resolve
|
|
37
|
+
|
|
38
|
+
`install-hook` adds a `prepare-commit-msg` hook that derives the trailer from
|
|
39
|
+
a branch name that names an idea number (`idea-42`, `ideas-042`,
|
|
40
|
+
`feature/idea_42-thing`), and a `commit-msg` hook that rejects a malformed
|
|
41
|
+
`Idea-Id` or `Idea-Status` line. Neither hook can invent a link: the first
|
|
42
|
+
fires only when the branch already carries the number, and it never overwrites
|
|
43
|
+
a trailer written by hand.
|
|
44
|
+
|
|
45
|
+
A commit may carry several `Idea-Id` trailers when it lands several ideas.
|
|
46
|
+
`Idea-Status` is commit-level, so a commit saying `partial` says it about
|
|
47
|
+
every id it names.
|
|
48
|
+
|
|
49
|
+
## A wrong id is worse than no id
|
|
50
|
+
|
|
51
|
+
An `Idea-Id` that resolves to nothing is not a harmless typo. Rule 2a asserts
|
|
52
|
+
LANDED from a trailer ahead of every other inferred signal, and nothing else in
|
|
53
|
+
the rule stack can distinguish a real join key from a plausible-looking dead
|
|
54
|
+
one — so a dangling trailer is a silent, permanent, confident wrong answer.
|
|
55
|
+
That is why the id shape is fixed (`willow-ideas-NNN`, three digits), why the
|
|
56
|
+
commit-msg hook rejects anything else, and why `reconciler verify` runs in CI.
|
|
57
|
+
|
|
58
|
+
## Nothing to backfill on the doc side
|
|
59
|
+
|
|
60
|
+
The doc-side id is *derived* by `reconciler/ids.py` from the entry's position
|
|
61
|
+
in the doc — it is not stored in the doc. So no edit to ideas.md is required.
|
|
62
|
+
The only new discipline is on the commit side: emit the trailer when a commit
|
|
63
|
+
lands an idea. This sits alongside the `Co-Authored-By` / `Claude-Session`
|
|
64
|
+
trailers the harness already appends, so it is one more line.
|
|
65
|
+
|
|
66
|
+
## What counts as landing evidence
|
|
67
|
+
|
|
68
|
+
- The `Idea-Id` trailer on a commit **reachable from the checkout being
|
|
69
|
+
reconciled** (primary key). Evidence is scoped to `git log HEAD`, not
|
|
70
|
+
`git log --all`: a trailer on an abandoned or rejected branch is not a
|
|
71
|
+
landing, and reading it as one was a false LANDED sourced from the ref scope.
|
|
72
|
+
- A commit body that says "PR #N" *and* git shows PR #N merged **as a real
|
|
73
|
+
merge commit** (two or more parents, with GitHub's own merge subject). A bare
|
|
74
|
+
`#N`, or an idea-number cross-reference like "former #103", is NOT a PR ref —
|
|
75
|
+
the reconciler rejects it (see tests/).
|
|
76
|
+
|
|
77
|
+
Deliberately NOT evidence: a subject ending `(#N)`. That is GitHub's
|
|
78
|
+
squash-merge title shape, but it is also the ordinary conventional-commit habit
|
|
79
|
+
of naming an issue, and the two are indistinguishable from git alone — it
|
|
80
|
+
asserted LANDED for unrelated commits. Repos that squash-merge should rely on
|
|
81
|
+
the trailer, which is the durable key regardless of merge strategy.
|
|
82
|
+
|
|
83
|
+
A legend tag in the doc is read as a tag only when it LEADS the item text or
|
|
84
|
+
when its dash-introduced clause NAMES its legend keyword (`shipped` / `partial`).
|
|
85
|
+
An ordinary sentence containing an em-dash and a marker is prose, not a tag.
|