Compare commits

..
64 Commits
Author SHA1 Message Date
conductor 95e907a634 chore(release): auto-bump version to 0.54.8 — earned by #250 (research file for #245)
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 43s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 14s
Promotion record: #250
2026-09-08 08:33:27 +01:00
conductor c38ec4e0ec chore(release): auto-bump version to 0.54.8 — earned by #250 (research file for #245)
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #250
2026-09-08 08:33:25 +01:00
conductor ed61c85444 chore(release): auto-bump version to 0.54.8 — earned by #250 (research file for #245)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #250
2026-09-08 08:33:14 +01:00
conductor add5e333b9 chore(release): auto-bump version to 0.54.8 — earned by #250 (research file for #245)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #250
2026-09-08 08:33:10 +01:00
conductor c0b2ef3e9c chore(release): auto-bump version to 0.54.8 — earned by #250 (research file for #245)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Failing after 40s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #250
2026-09-08 08:33:09 +01:00
conductor a911a5f829 Merge pull request 'research file for #245' (#250) from research-file-245 into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 08:32:54 +01:00
conductor f6beca546a docs(research): file 4-phase research round for #245 (headless stage)
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / canary (pull_request) Successful in 27s
CI Pipeline / lint (pull_request) Successful in 32s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / type-check (pull_request) Successful in 25s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / unit-tests (pull_request) Successful in 44s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 42s
2026-09-08 08:31:50 +01:00
conductor b02d31a515 chore(release): auto-bump version to 0.54.7 — earned by #249 (fix(research): extract ALL text blocks from mimo events — progressive output con)
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 47s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
Promotion record: #249
2026-09-08 08:22:16 +01:00
conductor 5ad28a767e chore(release): auto-bump version to 0.54.7 — earned by #249 (fix(research): extract ALL text blocks from mimo events — progressive output con)
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 43s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #249
2026-09-08 08:22:13 +01:00
conductor 7f85c4b6d2 chore(release): auto-bump version to 0.54.7 — earned by #249 (fix(research): extract ALL text blocks from mimo events — progressive output con)
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #249
2026-09-08 08:22:04 +01:00
conductor 78faab1f60 chore(release): auto-bump version to 0.54.7 — earned by #249 (fix(research): extract ALL text blocks from mimo events — progressive output con)
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #249
2026-09-08 08:22:03 +01:00
conductor f8c12dcc8b chore(release): auto-bump version to 0.54.7 — earned by #249 (fix(research): extract ALL text blocks from mimo events — progressive output con)
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 43s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #249
2026-09-08 08:22:02 +01:00
conductor 5091d38702 Merge pull request 'fix(research): extract ALL text blocks from mimo events — progressive output concatenation' (#249) from research-structuralization into master
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / unit-tests (push) Successful in 46s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 08:21:46 +01:00
Nick 807c024159 merge master
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 3s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / type-check (push) Successful in 25s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (pull_request) Successful in 30s
CI Pipeline / unit-tests (pull_request) Successful in 45s
CI Pipeline / canary (pull_request) Successful in 28s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / type-check (pull_request) Successful in 23s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / required-checks (pull_request) Successful in 2s
Auto-version and Release / release (pull_request) Successful in 37s
2026-09-08 08:19:12 +01:00
Nick dfaa469cba fix(research): extract ALL text blocks from mimo events — the agent produces progressive output across tool rounds; concatenating gives the full research (110-event run had real work but single-text extraction missed it)
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Successful in 2s
2026-09-08 08:18:39 +01:00
conductor 4c60ab8879 chore(release): auto-bump version to 0.54.6 — earned by #248 (fix(research): mimo stderr captured + event count — empty-output diagnosis no lo)
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
Promotion record: #248
2026-09-08 07:51:17 +01:00
conductor 1d4c185d02 chore(release): auto-bump version to 0.54.6 — earned by #248 (fix(research): mimo stderr captured + event count — empty-output diagnosis no lo)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 28s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #248
2026-09-08 07:51:14 +01:00
conductor 0e9b48fca8 chore(release): auto-bump version to 0.54.6 — earned by #248 (fix(research): mimo stderr captured + event count — empty-output diagnosis no lo)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #248
2026-09-08 07:51:10 +01:00
conductor f6c09c8f48 chore(release): auto-bump version to 0.54.6 — earned by #248 (fix(research): mimo stderr captured + event count — empty-output diagnosis no lo)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #248
2026-09-08 07:51:07 +01:00
conductor 4aa270ef02 chore(release): auto-bump version to 0.54.6 — earned by #248 (fix(research): mimo stderr captured + event count — empty-output diagnosis no lo)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #248
2026-09-08 07:51:06 +01:00
conductor 3d7c745108 Merge pull request 'fix(research): mimo stderr captured + event count — empty-output diagnosis no longer blind' (#248) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 07:50:50 +01:00
Nick ec039e1baf merge master
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 5s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 45s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (pull_request) Successful in 29s
CI Pipeline / unit-tests (pull_request) Successful in 44s
CI Pipeline / canary (pull_request) Successful in 27s
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 24s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 35s
2026-09-08 07:48:23 +01:00
Nick e63b6dcaf1 fix(research): mimo stderr captured to CI log + event count printed — empty-output diagnosis no longer blind
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 07:47:48 +01:00
conductor 78584cd6ce chore(release): auto-bump version to 0.54.5 — earned by #247 (fix(research): issue context from API — dispatch events carry no issue payload)
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 43s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #247
2026-09-08 07:35:05 +01:00
conductor 6ea9a5d4e4 chore(release): auto-bump version to 0.54.5 — earned by #247 (fix(research): issue context from API — dispatch events carry no issue payload)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 46s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #247
2026-09-08 07:35:03 +01:00
conductor 1a522c6602 chore(release): auto-bump version to 0.54.5 — earned by #247 (fix(research): issue context from API — dispatch events carry no issue payload)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #247
2026-09-08 07:35:00 +01:00
conductor a0a1d4f3d3 chore(release): auto-bump version to 0.54.5 — earned by #247 (fix(research): issue context from API — dispatch events carry no issue payload)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #247
2026-09-08 07:34:57 +01:00
conductor c85bfa9a85 chore(release): auto-bump version to 0.54.5 — earned by #247 (fix(research): issue context from API — dispatch events carry no issue payload)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #247
2026-09-08 07:34:55 +01:00
conductor c76f5d9cc8 Merge pull request 'fix(research): issue context from API — dispatch events carry no issue payload' (#247) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 2s
2026-09-08 07:34:41 +01:00
Nick 275715fbc0 merge master
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 46s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 31s
CI Pipeline / canary (pull_request) Successful in 28s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / type-check (pull_request) Successful in 24s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / unit-tests (pull_request) Successful in 44s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 28s
2026-09-08 07:32:36 +01:00
Nick 1ea3462edb fix(research): issue title/body fetched from API — dispatch events carry no issue payload
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 07:32:11 +01:00
conductor 4573bb6a2b chore(release): auto-bump version to 0.54.4 — earned by #246 (fix(research): dispatch trigger for the research stage — label-event quirk)
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 47s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
Promotion record: #246
2026-09-08 07:22:31 +01:00
conductor 4b67831aa8 chore(release): auto-bump version to 0.54.4 — earned by #246 (fix(research): dispatch trigger for the research stage — label-event quirk)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 43s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #246
2026-09-08 07:22:29 +01:00
conductor 1f1bd9e620 chore(release): auto-bump version to 0.54.4 — earned by #246 (fix(research): dispatch trigger for the research stage — label-event quirk)
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / unit-tests (push) Successful in 50s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #246
2026-09-08 07:22:25 +01:00
conductor e1f8ff0406 chore(release): auto-bump version to 0.54.4 — earned by #246 (fix(research): dispatch trigger for the research stage — label-event quirk)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 50s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #246
2026-09-08 07:22:23 +01:00
conductor cb0ed1bf60 chore(release): auto-bump version to 0.54.4 — earned by #246 (fix(research): dispatch trigger for the research stage — label-event quirk)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / documentation (push) Successful in 11s
CI Pipeline / unit-tests (push) Successful in 43s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #246
2026-09-08 07:22:21 +01:00
conductor 301a751761 Merge pull request 'fix(research): dispatch trigger for the research stage — label-event quirk' (#246) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / required-checks (push) Successful in 2s
2026-09-08 07:22:08 +01:00
Nick 6c824c1e62 fix(research): workflow_dispatch trigger for the research stage — label-event payload quirk (label.name match skipped on API-applied labels); dispatch route is deterministic
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / lint (pull_request) Successful in 31s
CI Pipeline / canary (pull_request) Successful in 27s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (pull_request) Successful in 44s
CI Pipeline / type-check (pull_request) Successful in 24s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 31s
2026-09-08 07:20:30 +01:00
Nick 6140f8e087 feat(research): PR template gains Research provenance section (P0.7 planning callback — synthesis digest flows to implementation)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 52s
CI Pipeline / required-checks (push) Successful in 0s
2026-09-08 07:16:57 +01:00
Nick a8cebea264 feat(research): MORE_RESEARCH re-dispatch semantics (P0.6) — sweep keeps MORE_RESEARCH rounds eligible (re-dispatch when label lost); PROCEED rounds are done. Disposition routing documented in stage workflow
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 26s
CI Pipeline / unit-tests (push) Successful in 1m39s
CI Pipeline / required-checks (push) Successful in 0s
2026-09-08 07:12:34 +01:00
Nick 7f050a13df feat(research): structured research artifact schema + causal validation (P0.4, red-first)
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 50s
CI Pipeline / required-checks (push) Successful in 1s
Per the Forge Cognitive Research Loop handoff §8/§9/§11/§12:
- OUTWARD valid only with real discovery (≥2 sources, url+tool+learned each, ≥1 query recorded)
- INNOVATION candidates must derive_from prior claims; ≥1 from OUTWARD unless exempt
- SYNTHESIS must cite evidence_used; MORE_RESEARCH requires named blocking_unknowns
- dispositions: PROCEED | MORE_RESEARCH (nothing else)

10 red-first tests frozen green.
2026-09-08 07:08:06 +01:00
Nick 78e506b99d fix(canary): release-token binding asserts ROBOT_TOKEN — 242's merge dropped the change (canary froze the broken GITEA_TOKEN contract while the workflow correctly uses ROBOT_TOKEN)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 06:56:10 +01:00
conductor 6bfe993801 chore(release): auto-bump version to 0.54.3 — earned by #244 (fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution)
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #244
2026-09-08 06:55:42 +01:00
conductor 092393a620 chore(release): auto-bump version to 0.54.3 — earned by #244 (fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution)
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / canary (push) Failing after 29s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #244
2026-09-08 06:55:39 +01:00
conductor 3162c99e4f chore(release): auto-bump version to 0.54.3 — earned by #244 (fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Failing after 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / required-checks (push) Successful in 0s
Promotion record: #244
2026-09-08 06:55:37 +01:00
conductor 1e0c1a0618 chore(release): auto-bump version to 0.54.3 — earned by #244 (fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Failing after 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #244
2026-09-08 06:55:34 +01:00
conductor 2974b173d3 chore(release): auto-bump version to 0.54.3 — earned by #244 (fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #244
2026-09-08 06:55:32 +01:00
conductor 90fc6061bc Merge pull request 'fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution' (#244) from umbrella-rewire into master
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Failing after 28s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 06:55:19 +01:00
Nick 3c688bd126 fix(pipeline): re-wire umbrella impact gate — lost in #212 merge resolution
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 3s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 30s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 50s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 32s
CI Pipeline / canary (pull_request) Failing after 25s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / unit-tests (pull_request) Successful in 45s
CI Pipeline / type-check (pull_request) Successful in 24s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 31s
2026-09-08 06:50:29 +01:00
conductor 5ba6301047 chore(release): auto-bump version to 0.54.2 — earned by #243 (chore: trigger release (conductor push-whitelisted))
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 28s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / required-checks (push) Successful in 0s
Promotion record: #243
2026-09-08 06:40:44 +01:00
conductor bfb0a28626 chore(release): auto-bump version to 0.54.2 — earned by #243 (chore: trigger release (conductor push-whitelisted))
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #243
2026-09-08 06:40:43 +01:00
conductor f15e0f1b0f chore(release): auto-bump version to 0.54.2 — earned by #243 (chore: trigger release (conductor push-whitelisted))
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 44s
CI Pipeline / required-checks (push) Successful in 2s
Promotion record: #243
2026-09-08 06:40:39 +01:00
conductor 661f1f9869 chore(release): auto-bump version to 0.54.2 — earned by #243 (chore: trigger release (conductor push-whitelisted))
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #243
2026-09-08 06:40:36 +01:00
conductor 892ae96de8 chore(release): auto-bump version to 0.54.2 — earned by #243 (chore: trigger release (conductor push-whitelisted))
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 40s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #243
2026-09-08 06:40:34 +01:00
conductor f5d8ab15bc Merge pull request 'chore: trigger release (conductor push-whitelisted)' (#243) from trigger-bump-2 into master
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 06:40:22 +01:00
Nick 656afa07c6 chore: trigger release — conductor push-whitelisted, direct-commit bump unblocked
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / best-practices (push) Successful in 4s
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 28s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / lint (pull_request) Successful in 29s
CI Pipeline / canary (pull_request) Failing after 29s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (push) Successful in 1s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / unit-tests (pull_request) Successful in 44s
CI Pipeline / type-check (pull_request) Successful in 25s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Failing after 24s
2026-09-08 06:38:22 +01:00
conductor 280c40394f Merge pull request 'fix(release): ROBOT_TOKEN binding + canary contract update (supersedes #241)' (#242) from release-token-fix into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 47s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 06:32:40 +01:00
Nick 810af91368 merge research-structuralization (ROBOT_TOKEN in auto-version + canary contract update)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / canary (push) Failing after 26s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 2s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Failing after 26s
CI Pipeline / lint (pull_request) Successful in 32s
CI Pipeline / type-check (pull_request) Successful in 25s
Spec gate (headless) / spec-gate (pull_request) Successful in 6s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (pull_request) Successful in 47s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Failing after 9s
2026-09-08 06:29:30 +01:00
Nick 7089285a3e Merge remote-tracking branch 'gitea/research-structuralization' into sweep-live
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / best-practices (push) Successful in 4s
Auto-label / auto-label (pull_request) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 27s
CI Pipeline / lint (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / required-checks (push) Successful in 2s
Spec gate (headless) / spec-gate (pull_request) Successful in 6s
CI Pipeline / lint (pull_request) Successful in 30s
CI Pipeline / unit-tests (pull_request) Successful in 43s
CI Pipeline / canary (pull_request) Failing after 27s
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 24s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (pull_request) Successful in 2s
Auto-version and Release / release (pull_request) Failing after 10s
2026-09-08 06:23:10 +01:00
Nick 4a33074b6a fix(release): auto-version authenticates via ROBOT_TOKEN — the reserved GITEA_TOKEN is the read-only auto token (gitea-actions bot cannot commit to protected master; 403 'user cannot commit to repo') 2026-09-08 06:22:55 +01:00
conductor 99de8ce1be Merge pull request 'fix(release): surface gate self-heals base drift + drift detail to stdout' (#240) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 49s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 06:18:44 +01:00
conductor a1dcdadde2 Merge branch 'master' into research-structuralization
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 30s
CI Pipeline / canary (pull_request) Successful in 28s
CI Pipeline / unit-tests (pull_request) Successful in 44s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 24s
Spec gate (headless) / spec-gate (pull_request) Successful in 7s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Failing after 7s
2026-09-08 06:17:03 +01:00
Nick 56bc364832 fix(release): surface gate recovers from base drift — unchanged surfaces carrying a stale version are force-edited to NEW (with evidence line); drift detail printed to stdout (stderr was swallowed by runner logs)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / canary (push) Successful in 27s
Auto-label / auto-label (pull_request) Successful in 5s
CI Pipeline / unit-tests (push) Successful in 48s
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 31s
CI Pipeline / canary (pull_request) Successful in 26s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / type-check (pull_request) Successful in 24s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (pull_request) Successful in 45s
CI Pipeline / required-checks (pull_request) Successful in 1s
2026-09-08 06:15:19 +01:00
conductor 8bcf6a81b0 Merge pull request 'chore: trigger release pipeline (empty commit — proves direct-commit bump)' (#239) from trigger-bump into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 48s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-08 06:09:39 +01:00
14 changed files with 598 additions and 39 deletions

No files matched your search

+6
View File
@@ -83,3 +83,9 @@ DEPENDENCIES (epic + issues). -->
## Plain-language review notes
<!-- Explain what Nick should inspect, approve, reject, or question. -->
## Research provenance
<!-- If this PR's issue went through a research round: link the research file
(research/issue-<N>-research.md) and state which synthesis decisions were
adopted. If no research round: write "none" — the spec-gate will flag it. -->
-->
+18 -7
View File
@@ -31,7 +31,7 @@ jobs:
- name: Auto-bump if the PR did not carry a version bump
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
RELEASE_SERVER_URL: ${{ gitea.server_url }}
RELEASE_REPO: ${{ gitea.repository }}
PR_TITLE: ${{ gitea.event.pull_request.title }}
@@ -126,20 +126,31 @@ jobs:
drifts = []
for rel, pat in checks:
if rel not in edited:
# unchanged surface: must ALREADY carry NEW
# unchanged surface: must ALREADY carry NEW — if it carries a
# DIFFERENT version (base drift, e.g. pyproject at 0.49.0 while
# master is 0.54.1), RECOVER by force-editing to NEW. The gate
# stays: nothing commits unless every surface carries NEW.
status, cur = api(f"/contents/{rel}?ref=master")
text = base64.b64decode(cur.get("content", "")).decode("utf-8", "replace") if status == 200 else ""
if status != 200:
drifts.append(f"{rel}: unreadable ({status})")
continue
text = base64.b64decode(cur.get("content", "")).decode("utf-8", "replace")
hits = re.findall(pat, text)
if NEW not in hits:
drifts.append(f"{rel}: unchanged but does not carry {NEW} (found {sorted(set(hits))})")
fixed = re.sub(pat, lambda m: m.group(0).replace(m.group(1), NEW), text, count=1)
if fixed != text:
edited[rel] = (fixed, cur["sha"])
print(f" RECOVERED {rel}: base drift {sorted(set(hits))} -> {NEW}")
else:
drifts.append(f"{rel}: pattern present but unfixable (found {sorted(set(hits))})")
else:
hits = re.findall(pat, edited[rel][0])
if NEW not in hits:
drifts.append(f"{rel}: edited content does not carry {NEW}")
if drifts:
print("VERSION-SURFACE DRIFT — bump aborted, nothing committed:", file=sys.stderr)
print("VERSION-SURFACE DRIFT — bump aborted, nothing committed:")
for d in drifts:
print(f" - {d}", file=sys.stderr)
print(f" - {d}")
sys.exit(1)
# Commit each surface DIRECTLY to master (bookkeeping, not reviewable code)
@@ -162,7 +173,7 @@ jobs:
PYBUMP
- name: Create release from merged pull request
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
PR_TITLE: ${{ gitea.event.pull_request.title }}
PR_BODY: ${{ gitea.event.pull_request.body }}
RELEASE_API_URL: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases
+4
View File
@@ -80,6 +80,10 @@ jobs:
python3 -m venv .test-venv
.test-venv/bin/pip install --disable-pip-version-check -r requirements-ci.txt
.test-venv/bin/pip install --disable-pip-version-check -e . --no-deps
- name: Umbrella impact gate (propagation contract)
run: |
python3 scripts/umbrella_impact.py check \
--base "$(git merge-base origin/master HEAD)" --head HEAD
- name: Test with coverage (global floor + changed-lines ratchet)
run: |
# #149 coverage ratchet — both floors live in .coveragerc; raise,
+60 -23
View File
@@ -21,19 +21,28 @@ name: Research stage (headless)
on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue_number:
description: "Issue to research"
required: true
round:
description: "Research round number"
required: false
default: "1"
permissions:
issues: write
contents: write
concurrency:
group: research-stage-${{ gitea.event.issue.number }}
group: research-stage-${{ gitea.event.inputs.issue_number || gitea.event.issue.number }}
cancel-in-progress: false
jobs:
research:
runs-on: rune-ci-desktop
if: gitea.event.label.name == 'research-pending'
if: ${{ gitea.event.label.name == 'research-pending' || gitea.event_name == 'workflow_dispatch' }}
steps:
- name: Check out master (research reads the codebase, not a PR)
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -47,56 +56,76 @@ jobs:
HOME: /var/lib/gitea-desktop-runner
XDG_CONFIG_HOME: /home/nick/.config
XDG_DATA_HOME: /var/lib/gitea-desktop-runner/.local-share-mimo
ISSUE_NUMBER: ${{ gitea.event.issue.number }}
ISSUE_TITLE: ${{ gitea.event.issue.title }}
ISSUE_BODY: ${{ gitea.event.issue.body }}
ISSUE_NUMBER: ${{ gitea.event.inputs.issue_number || gitea.event.issue.number }}
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
SERVER: ${{ gitea.server_url }}
run: |
# Prompt = governed repo template (prompts/research-round.md) with
# placeholders substituted. The template is the TUI-callback seam:
# future L3 (Rune TUI) owns/overrides it via reviewed diffs.
# Fetch issue title/body from the API (dispatch events carry no issue
# payload). Deterministic, no scraping.
python3 - <<'PY'
import os
import json, os, urllib.request
token = os.environ["GITEA_TOKEN"]
base = os.environ["SERVER"].rstrip("/") + "/api/v1/repos/conductor/rune"
n = os.environ["ISSUE_NUMBER"]
req = urllib.request.Request(
f"{base}/issues/{n}",
headers={"Authorization": "token " + token})
with urllib.request.urlopen(req, timeout=30) as r:
issue = json.loads(r.read())
tpl = open("prompts/research-round.md").read()
out = (tpl.replace("{{ISSUE_NUMBER}}", os.environ["ISSUE_NUMBER"])
.replace("{{ISSUE_TITLE}}", os.environ["ISSUE_TITLE"])
.replace("{{ISSUE_BODY}}", os.environ["ISSUE_BODY"] or "(empty body)"))
out = (tpl.replace("{{ISSUE_NUMBER}}", n)
.replace("{{ISSUE_TITLE}}", issue.get("title", ""))
.replace("{{ISSUE_BODY}}", issue.get("body") or "(empty body)"))
open("/tmp/research_prompt.txt", "w").write(out)
print(f"prompt built: {len(out)} chars from prompts/research-round.md")
print(f"prompt built: {len(out)} chars (issue #{n}: {issue.get('title','')[:50]})")
PY
/var/lib/gitea-desktop-runner/bin/mimo run \
--agent build \
-m "Verboo Code/glm-5.3-flash" \
--dangerously-skip-permissions \
--format json \
"$(cat /tmp/research_prompt.txt)" \
> /tmp/research_events.jsonl 2>/tmp/research_err.log
> /tmp/research_events.jsonl 2>/tmp/research_err.log || {
echo "mimo exited nonzero — stderr:"
cat /tmp/research_err.log | tail -10
exit 1
}
echo "mimo events: $(wc -l < /tmp/research_events.jsonl) lines"
# Extract final assistant text (fail-loud: no swallow)
# Extract ALL text parts (the agent may produce multiple text blocks
# across tool-use rounds; the last contiguous block is the synthesis)
python3 - <<'PY'
import json
text = ""
texts = []
try:
for line in open("/tmp/research_events.jsonl"):
try:
ev = json.loads(line)
except json.JSONDecodeError:
continue
if ev.get("type") == "assistant" and isinstance(ev.get("message", {}).get("content"), list):
for part in ev["message"]["content"]:
if part.get("type") == "text":
text = part.get("text", text)
if ev.get("type") == "text":
t = ev.get("part", {}).get("text", "")
if t.strip():
texts.append(t)
except FileNotFoundError:
pass
if not text.strip():
raise SystemExit("research stage: agent produced NO text — infrastructure or model failure (fail-loud)")
if not texts:
raise SystemExit(
f"research stage: agent produced NO text in {sum(1 for _ in open('/tmp/research_events.jsonl'))} events "
"— infrastructure or model failure (fail-loud)"
)
# Join all text blocks (the agent's progressive output IS the round)
text = "\n\n".join(texts)
open("/tmp/research_round.md", "w").write(text)
print(f"research text: {len(text)} chars from {len(texts)} blocks")
PY
- name: File research as comment + versioned research file
env:
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
SERVER: ${{ gitea.server_url }}
ISSUE_NUMBER: ${{ gitea.event.issue.number }}
ISSUE_NUMBER: ${{ gitea.event.inputs.issue_number || gitea.event.issue.number }}
run: |
python3 - <<'PY'
import json, os, subprocess, urllib.request
@@ -119,6 +148,14 @@ jobs:
with urllib.request.urlopen(req, timeout=30) as r:
print(f"research comment posted: {r.status}")
# Disposition routing (§14): PROCEED → research-clear fires via the
# marker comment. MORE_RESEARCH → strip the synthesis from the
# comment is NOT needed (the gate keys on all 4 phase markers, which
# are present); instead the run_agent_stage result records the
# disposition and the ROUND increments on the next dispatch. The
# scheduled sweep re-dispatches when research-pending persists and
# no stage is running (§19 reconciliation path).
# 2. Research file committed straight to master by the robot identity
subprocess.run(["git", "config", "user.name", "conductor"], check=True)
subprocess.run(["git", "config", "user.email", "nick@thingscouldgetdicey.online"], check=True)
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.54.1**
**Current release: v0.54.8**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.54.1"
version = "0.54.8"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+156
View File
@@ -0,0 +1,156 @@
Research round for #245 (4-phase, agent-generated, filed by research stage per #204; comment on the issue is the research-clear trigger)
---
No skill is a better fit than the task's own mandated 4-phase format, so I'll execute the research round directly. Starting with INWARD codebase analysis.
INWARD picture is forming. Let me check the research artifact gate contract and one prior round for format conventions, then move to OUTWARD.
INWARD is complete — I have the full map of reactive usage. Now OUTWARD: fetching live sources on Textual 8.x reactive performance.
Reactivity guide and changelog retrieved (repo pins textual==8.2.8, which is the latest release). Two more targeted fetches: the `reactive`/`var` API reference and the `textual-speedups` / `Widget.BLANK` optimization notes.
All four phases are researched — I have the inward map, four live-fetched sources (one dead link substituted), and I can now evaluate candidates and write the deliverable file.
Both artifacts are complete: the round is written to `research/issue-245-research.md` (verified: exact required header line, phase markers on their own lines for the research-clear gate) and nothing was committed or pushed. Here is the same content as my final message:
---
Research round for #245 — E2E acceptance: research round on Textual 8.x reactive performance patterns (4-phase, agent-generated)
ISSUE: Disposable acceptance issue for the Forge Cognitive Research Loop (P0.8).
Topic: Textual 8.x reactive performance patterns as applied to the Rune TUI.
Repo pin: `textual==8.2.8` (requirements-ci.txt:105, pyproject.toml:12) — 8.2.8 is the current upstream release as of this run, so the codebase is on the latest stable.
## INWARD
The TUI surface lives in `src/rune/` (governed: every TUI surface must be registered per henxels AGENTS.md:27; rune modules need tests or a canary per AGENTS.md:28).
Reactive inventory — RuneApp (`src/rune/rune_tui.py:1520-1527`), 8 class-level reactives:
| Reactive | Line | Visual? | Watcher? |
|---|---|---|---|
| `mode` | rune_tui.py:1520 | yes (status bar) | none defined |
| `project` | rune_tui.py:1521 | yes (status bar) | none |
| `model` | rune_tui.py:1522 | yes (status bar) | none |
| `task_width` | rune_tui.py:1523 | yes (pane split) | none |
| `_last_ctrl_c` | rune_tui.py:1524 | no | none |
| `planning_active` | rune_tui.py:1525 | no | none |
| `plan_threshold` | rune_tui.py:1526 | no | none |
| `work_pane_visible` | rune_tui.py:1527 | yes (pane display) | none |
Key findings:
1. **No `watch_*` methods exist anywhere in the TUI** (grep across rune_tui.py, chat_bubble.py, message_widgets.py: zero `def watch_`). Instead, every reactive change is followed by a manual propagation call:
- `action_resize_left/right` set `self.task_width` (rune_tui.py:4043, 4048) then call `_apply_resize()` (rune_tui.py:4050-4057) which mutates `task.styles.width`/`chat.styles.width`. The reactive assignment *also* triggers Textual's smart refresh on the App. Result: two refresh paths per keypress, one of them a full-App refresh for a change only two children care about.
- `action_toggle_work_pane` sets `self.work_pane_visible` (rune_tui.py:4030) then calls `_apply_work_pane_visibility()` (rune_tui.py:4035-4040).
- `_update_status()` (rune_tui.py:4067-4072) manually copies `mode`/`model`/`project` into `StatusBar` attributes — state duplication with manual fan-out.
2. **Non-visual state declared as `reactive()`**: `_last_ctrl_c`, `planning_active`, `plan_threshold` (rune_tui.py:1524-1526) never affect rendering, yet every assignment triggers App-level refresh machinery. `planning_active` is flipped per message cycle (rune_tui.py:2039, 2099, 2236).
3. **Unbounded widget growth in chat logs**: `ChannelView.add_message` (rune_tui.py:1198-1210) mounts a new `MessageBox` (chat_bubble.py:279, a `Horizontal` container wrapping a `ChatBubble(Static)`) per message into `VerticalScroll` and calls `scroll_end` — no cap. Contrast: `WorkPane` is explicitly bounded at `_WORK_PANE_MAX_ENTRIES = 120` (rune_tui.py:1262), and `RichLog` (used for command results, rune_tui.py:552) supports `max_lines` upstream. A long session therefore accumulates thousands of mounted widgets; Textual renders/keeps strips for all of them.
4. **Double render/compose pattern**: `MessageBox.compose()` (chat_bubble.py:305-309) plus `ChatBubble` markup-string `render()` — consistent with Textual guidance, no issue. ThinkingBubble toggling does header `update()` correctly (chat_bubble.py:260-271).
5. **Existing contracts this touches**:
- `tests/test_message_widgets.py` covers `ToolCallWidget`; `tests/test_version_footer.py:23` imports `textual.widgets.Static`.
- `src/rune/canary_tui_drift.py` is the TUI-wiring canary (registered per henxels AGENTS.md:15).
- `scripts/check_dep_lock.py` + `tests/test_check_dep_lock.py:27` pin the textual version; dependency changes are gated.
- `config/umbrellas.json` — umbrella impact map must stay truthful (AGENTS.md:20); any `src/rune/rune_tui.py` behavioral change propagates through the umbrella gate.
- Prior in-repo research `research/textual-craft/findings/F1.md` (reactivity best practices), `F2.md` (render/compose), `F3.md` (async/performance) already documented the Textual guides — this round's job is to verify those claims live against 8.x and find what is BETTER than the obvious.
INWARD claim IDs (for causal links):
- **I1**: 3 of 8 RuneApp reactives are non-visual and needlessly trigger refresh (rune_tui.py:1524-1526).
- **I2**: Reactive state changes are propagated by manual imperative calls, not watch methods, causing double refresh paths (rune_tui.py:4030-4072).
- **I3**: Chat message widgets grow unbounded while WorkPane and RichLog demonstrate the bounded pattern exists in-repo (rune_tui.py:1198, 1262, 552).
## OUTWARD
Live sources retrieved this run (tool = webfetch over HTTPS):
- **S1** — Textual Reactivity guide (current docs): https://textual.textualize.io/guide/reactivity/
- learned: smart refresh coalesces multiple reactive changes into one refresh; assignment of an unchanged value does NOT refresh; `var()` gives reactive superpowers with no refresh/layout; `watch_<name>` methods fire only on real change; `set_reactive()` avoids watcher invocation pre-mount; `recompose=True` is "slightly less efficient than a simple refresh, and best avoided if you need to update rapidly or you have many child widgets"; compute methods are re-run when ANY reactive on the object changes — keep them cheap.
- **S2** — Textual CHANGELOG (main branch, covers 8.0.0 → 8.2.8): https://raw.githubusercontent.com/Textualize/textual/main/CHANGELOG.md
- learned (performance-relevant, all present in 8.2.8): 8.1.0 replaced DOM circular references with weak references to improve GC times (#6410); 7.1.0 added `Widget.BLANK` to optimize rendering of large widgets/scrolling containers; 7.0.3 fixed a performance issue with large scrollable containers (#6317); 8.2.3 moved resize handling to a timer to reduce window-resize lag (#6471); 6.2.0 reduced layout operations per screen update (#6108); 5.2.0 added the faster undocumented `stream` layout (#6013).
- **S3** — textual-speedups (Rust optional accelerators): https://github.com/willmcgugan/textual-speedups
- NOTE: the canonical URL https://github.com/Textualize/textual-speedups returned HTTP 404 this run — substituted with the maintainer's repo above (the changelog S2 links it as "willmcgugan/textual-speedups", confirming the substitution).
- learned: auto-imported by Textual ≥5.0.0 when installed (`TEXTUAL_SPEEDUPS=0` disables); accelerates `Offset`/`Size`/`Region`/`Spacing` geometry math used heavily during layout; upstream README itself says "unless your Textual app is particularly complex, you might not even notice a difference".
- **S4** — RichLog widget reference: https://textual.textualize.io/widgets/rich_log/
- learned: `max_lines` (a `var`) bounds retained lines; `write()` defers rendering until size is known; `auto_scroll` is a var; the simpler `Log` widget is recommended for plain text. Confirms the bounded-retention idiom that WorkPane (I3) already mirrors.
Search queries recorded: "Textual 8.x reactive performance patterns", "textual changelog 8.x performance GC scrollable", "textual-speedups repository", "RichLog max_lines widget reference".
OUTWARD claims (IDs for causal links):
- **O1** (S1): `var()` is the sanctioned way to hold non-visual reactive state without refresh cost; watch methods are the sanctioned way to propagate reactive state — supporting I1 and I2.
- **O2** (S2): Textual 8.2.x already contains the major framework-side performance work (weak-ref GC, large-container fix, Widget.BLANK) — framework-level wins are already banked at our pin; remaining wins are application-level.
- **O3** (S3): The only extra framework accelerator (textual-speedups) is optional, geometry-only, and self-described as possibly imperceptible — weak candidate for our lock-file-gated dependency policy.
- **O4** (S4): Bounded log retention (`max_lines`-style) is a first-class upstream idiom — supporting I3.
Answer to the driving question "is there anything BETTER than the obvious approach?": **No framework-level mechanism beats the obvious ones.** 8.2.8 already includes the GC/layout/container work; the leverage is in how RuneApp *uses* reactives (var vs reactive, watchers vs manual fan-out, bounded widget population), not in adding anything.
## INNOVATION
Candidates, each evaluated for effort / risk / fit:
- **C1 — Convert non-visual reactives to `var()`.**
derived_from: [I1, O1]
proposal: `from textual.reactive import reactive, var`; change `_last_ctrl_c`, `planning_active`, `plan_threshold` to `var(...)` (rune_tui.py:1524-1526). Watchers and validation still work; refresh machinery no longer engaged.
expected_gain: removes App-level refresh on every planning-state flip and every Ctrl+C (frequent paths — rune_tui.py:2039/2099/2236). Low absolute cost each, but on the hottest assignment paths.
effort: trivial (~5 lines). risk: negligible (no rendering depends on them). fit: exact match to documented upstream idiom. **ADOPT.**
- **C2 — Replace manual fan-out with watch methods.**
derived_from: [I2, O1]
proposal: add `watch_task_width` (applies the pane split), `watch_work_pane_visible` (applies display policy), and `watch_mode`/`watch_model`/`watch_project` (update StatusBar) in RuneApp; delete the explicit `_apply_resize()`/`_apply_work_pane_visibility()`/`_update_status()` call-sites that merely duplicate reactive propagation (keep the helpers as the watcher bodies; they contain try/except guards worth keeping). Caveats from S1 to respect: watchers fire only on real change (fine — all call-sites are real changes); DOM-querying watchers must not run pre-mount, so keep the existing try/except (or use `set_reactive()` in `__init__` for the saved-model assignment at rune_tui.py:1545-1547, which currently *does* invoke watcher machinery pre-mount — with watchers added this becomes load-bearing).
expected_gain: single refresh path per change; eliminates full-App refresh where a two-child update suffices; aligns with upstream idiom so future Textual refresh optimizations apply automatically.
effort: small (~40 lines, no behavior change). risk: low — must verify the pre-mount `self.model = saved` path and `mode` changes during startup. fit: high. **ADOPT.**
- **C3 — Bound chat-log widget population.**
derived_from: [I3, O4]
proposal: in `ChannelView.add_message` (rune_tui.py:1198), after mounting, prune the oldest `MessageBox` children of `#log-<channel>` beyond a cap (e.g. 200), mirroring `_WORK_PANE_MAX_ENTRIES` (rune_tui.py:1262) and RichLog's `max_lines` (S4). The `self.messages` projection list (rune_tui.py:1207-1210) stays the durable display record; only mounted widgets are bounded. This is application-level compensation because the bubbles must remain interactive widgets (expand/collapse ThinkingBubble, CopyMenu — chat_bubble.py:16, 260-271), so a RichLog/Line-API rewrite (virtual rendering) is not viable without losing interactivity.
expected_gain: prevents multi-thousand-widget DOM in long sessions — the single largest real-world latency factor for scroll/refresh in this app per S2's large-container history (7.0.3) and per WorkPane's existing bounded design.
effort: medium (~25 lines + tests). risk: low-medium — must not prune the conversation ledger (durable state lives in ConversationStateRepository per rune_tui.py:1172-1191 comment), only DOM children; scroll position after prune needs care (only prune when scrolled to bottom, or accept anchor jump).
fit: high. **ADOPT.**
- **C4 — Add `textual-speedups` optional dependency.**
derived_from: [O3] — **REJECTED.** Gain unquantified and self-described as possibly imperceptible; adds a native/Rust wheel to `requirements-ci.txt` whose lock is contract-gated (tests/test_check_dep_lock.py); upstream auto-import means we already get it for free wherever users install it. Honesty over novelty: no measured problem it solves here.
- **C5 — Rewrite chat log on RichLog / Line API for virtual rendering.**
derived_from: [I3, S4] — **REJECTED:** loses interactive per-message widgets (collapse/expand, copy menu, tool decision buttons in ToolCallWidget, message_widgets.py:17-35). C3 captures the win at far lower risk.
Adopted set: C1 + C2 + C3. (All three derive from OUTWARD evidence, satisfying the causality gate.)
## SYNTHESIS
Decision: **PROCEED.** Adopt reactive hygiene (C1, C2) and bounded log population (C3); reject framework additions (C4, C5).
evidence_used: [I1, I2, I3, O1, O2, O3, O4]
blocking_unknowns: none blocking. (Non-blocking: precise perf delta of C1/C2 is expected small; C3's win is structural, not micro-benchmarked. If the implementer wants a number, a before/after widget-count assertion in tests is the cheap proxy.)
kill_test: if adding `watch_task_width` breaks the pre-mount saved-model path or the startup `mode` assignment such that behavior changes, fall back to keeping manual calls AND make the reactives `var()`/watch-free as in C1 — the round still delivers value.
feasibility: high — all changes confined to `src/rune/rune_tui.py` (+ one test file). No new dependencies, no schema changes.
### BUILD INSTRUCTIONS (ordered, self-contained for the implementer)
1. Branch `perf/245-reactive-hygiene` off master. Do NOT bump the textual pin — 8.2.8 already contains the framework-side work (O2).
2. RED-FIRST tests — write these before touching source (bug-fix discharge contract, AGENTS.md:53-60; they will fail on current code):
a. tests/test_rune_tui_reactive.py (new file; follow the import style of tests/test_message_widgets.py):
- test_non_visual_state_is_var: assert that `_last_ctrl_c`, `planning_active`, `plan_threshold` on RuneApp are instances of `textual.reactive.var` (and that `mode`, `model`, `project`, `task_width`, `work_pane_visible` remain `reactive`). RED on current code.
- test_watch_methods_propagate: run RuneApp under `textual.testing` AppTest / Pilot; set `app.task_width = 65` and assert `#task-channel` style width became "65%" and `#chat-channel` "35%" WITHOUT calling `_apply_resize` (monkeypatch it to raise, or assert it was not invoked). RED on current code.
- test_status_bar_tracks_reactives: set `app.model = "x"`; assert StatusBar reflects it without `_update_status()` being called. RED.
- test_chat_log_bounded: mount RuneApp with Pilot, push > cap (e.g. 220) messages via `channel.add_message(...)`, assert `len(log.children) <= cap` while `len(channel.messages)` keeps growing (projection survives pruning). RED on current code.
b. Update tests/test_henxels_contract.py expectations only if the contract check counts rune_tui.py reactives (it does not today — verify with `henxels explain src/rune/rune_tui.py` before writing).
3. Implement (src/rune/rune_tui.py only):
- Step 1 (C1): rune_tui.py:108 import gains `var`; lines 1524-1526 become `var(0.0)`, `var(False)`, `var(0)`. Keep the trailing comments.
- Step 2 (C2): convert `_apply_resize`, `_apply_work_pane_visibility` bodies into `watch_task_width(self, width)` / `watch_work_pane_visible(self, visible)`; add `watch_mode`, `watch_model`, `watch_project` calling the StatusBar update (replace `_update_status` body or keep it as a shared helper called by the three watchers). Delete now-redundant explicit call-sites at rune_tui.py:4043/4048 (keep `self.task_width = ...` clamping — or move clamping into `validate_task_width`), rune_tui.py:4030, and wherever `_update_status` is invoked. In `__init__` (rune_tui.py:1545-1547), replace `self.model = saved` with `self.set_reactive(RuneApp.model, saved)` to avoid firing the new watcher pre-mount (S1).
- Step 3 (C3): add `_CHAT_LOG_MAX_WIDGETS = 200` beside `_WORK_PANE_MAX_ENTRIES` (rune_tui.py:1262). In `ChannelView.add_message` (rune_tui.py:1198-1210), after `log.mount(bubble)`, prune: `while len(log.children) > _CHAT_LOG_MAX_WIDGETS: await-removed first child` — in sync context use `log.remove_children`-style batch removal of `log.children[:overflow]` via `self.call_after_refresh` or mount-then-remove pattern; preserve `scroll_end(animate=False)` semantics. Never touch `self.messages`.
- Step 4: update `src/rune/canary_tui_drift.py` wiring tests if they introspect rune_tui source for these symbols (they introspect by `inspect.getsource` — check section 8 pattern, canary_tui_drift.py:88-96, and extend rather than break).
4. Gates that apply:
- Full suite + ruff + pyright ratchets + canaries + henxels (bug-fix contract re-entry, AGENTS.md:56-58).
- Umbrella propagation: `config/umbrellas.json` — rune_tui.py is mapped; run the umbrella impact check (scripts/umbrella_impact.py); do not add phantom entries.
- research-clear/pipeline gate: this round's file lands via the research stage; the build PR references #245.
- dep lock: untouched (no dependency changes — C4 rejected).
5. PR strategy: one PR, title `perf(tui): reactive hygiene + bounded chat log (#245)`, body citing this research file path `research/issue-245-research.md`, the three adopted candidates and two rejections with reasons. Single commit-set reviewable in one pass; no release-bump trigger unless auto-version policy requires it for src changes.
6. Verification: run the new test file plus `pytest tests/test_message_widgets.py tests/test_version_footer.py` and a manual `python -m src.rune` smoke (bubble render, Ctrl+1/2/3, Ctrl+left/right, /commands) confirming no visual regressions.
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""research_artifact.py — structured research-round schema + validation (P0.4).
Per the Forge Cognitive Research Loop handoff §8: machine-readable research
round with causal evidence links. Phases are FROZEN artifacts — the later
phase's input is the persisted prior artifact (§7: "the later phase must not
be able to pretend it saw research that the pipeline never recorded").
Schema (minimal, causally sufficient):
issue, round, receipt (from run_agent_stage.build_receipt),
phases: {inward: {claims: [{id, claim, evidence[]}]},
outward: {sources: [{id, url, title, retrieved_at, tool,
content_hash, learned}],
claims: [{id, claim, supports[]}]},
innovation: {candidates: [{id, proposal, derived_from[],
expected_gain, risks[]}]},
synthesis: {decision, evidence_used[], blocking_unknowns[],
feasibility, kill_test,
disposition: PROCEED|MORE_RESEARCH}}
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
def validate_outward(outward: dict) -> list[str]:
"""§9: OUTWARD valid only with real discovery — ≥2 sources, each with
url + tool receipt + learned note; ≥1 search query recorded."""
errs = []
sources = outward.get("sources", [])
if len(sources) < 2:
errs.append(f"OUTWARD: {len(sources)} sources < 2 (real discovery required)")
for s_ in sources:
for field in ("id", "url", "tool", "learned"):
if not s_.get(field):
errs.append(f"OUTWARD source {s_.get('id', '?')}: missing {field}")
if not re.match(r"https?://", s_.get("url", "")):
errs.append(f"OUTWARD source {s_.get('id', '?')}: url not http(s)")
if not outward.get("queries"):
errs.append("OUTWARD: no search queries recorded (discovery, not recall)")
return errs
def validate_causal_links(artifact: dict) -> list[str]:
"""§11/§12: innovation candidates derive_from prior claims; synthesis
decisions cite evidence_used. At least one candidate depends on OUTWARD
evidence unless synthesis records 'no external mechanism relevant'."""
errs = []
phases = artifact.get("phases", {})
inward_ids = {c["id"] for c in phases.get("inward", {}).get("claims", [])}
outward_ids = {c["id"] for c in phases.get("outward", {}).get("claims", [])}
candidates = phases.get("innovation", {}).get("candidates", [])
synthesis = phases.get("synthesis", {})
known = inward_ids | outward_ids
for c in candidates:
refs = set(c.get("derived_from", []))
if not refs:
errs.append(f"INNOVATION {c.get('id', '?')}: no derived_from (decorative, not causal)")
continue
if not refs & known:
errs.append(f"INNOVATION {c.get('id', '?')}: derived_from references unknown claims {sorted(refs - known)}")
outward_dependent = any(set(c.get("derived_from", [])) & outward_ids for c in candidates)
if not outward_dependent and "no external mechanism relevant" not in synthesis.get("decision", ""):
errs.append("no INNOVATION candidate derives from OUTWARD evidence — research was decorative")
if not synthesis.get("evidence_used"):
errs.append("SYNTHESIS: evidence_used empty — decisions untraceable")
if synthesis.get("disposition") == "MORE_RESEARCH" and not synthesis.get("blocking_unknowns"):
errs.append("MORE_RESEARCH disposition without named blocking_unknowns")
return errs
def validate(artifact: dict) -> list[str]:
errs = validate_outward(artifact.get("phases", {}).get("outward", {}))
errs += validate_causal_links(artifact)
if artifact.get("phases", {}).get("synthesis", {}).get("disposition") not in ("PROCEED", "MORE_RESEARCH"):
errs.append("synthesis.disposition must be PROCEED or MORE_RESEARCH")
return errs
def main_with(argv: list[str]) -> int:
artifact = json.loads(Path(argv[1]).read_text())
errs = validate(artifact)
if errs:
print("research artifact INVALID:")
for e in errs:
print(f" - {e}")
return 1
print("research artifact VALID (outward discovery + causal links verified)")
return 0
if __name__ == "__main__":
sys.exit(main_with(sys.argv))
+7 -2
View File
@@ -133,8 +133,13 @@ def compute_targets(issues: list[dict], api) -> list[dict]:
if unsatisfied:
continue
_, comments = api.get(f"/issues/{i['number']}/comments")
if has_research_round(comments if isinstance(comments, list) else []):
continue
comments = comments if isinstance(comments, list) else []
if has_research_round(comments):
# A round exists — re-queue ONLY if its disposition says MORE_RESEARCH
# (§14: the loop continues). Latest round wins (comments are append-only).
more = any(re.search(r"Disposition:\s*MORE_RESEARCH", c.get("body", ""), re.IGNORECASE) for c in comments)
if not more:
continue
targets.append(i)
return targets
+1 -1
View File
@@ -3,4 +3,4 @@
This is the main entry point for the Rune program.
"""
__version__ = "0.54.1"
__version__ = "0.54.8"
+2 -2
View File
@@ -252,7 +252,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.54.1"' in rune_version,
'__version__ = "0.54.8"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
@@ -270,7 +270,7 @@ def main() -> int:
)
check(
"release job explicitly binds its Gitea token",
"GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}" in versioning,
"GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}" in versioning,
failures,
)
+2 -2
View File
@@ -7,7 +7,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.54.1
Version: v0.54.8
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1923,7 +1923,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.54.1 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.54.8 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+227
View File
@@ -0,0 +1,227 @@
"""Red-first tests for scripts/research_artifact.py — P0.4 causal validation.
Per the Forge Cognitive Research Loop handoff:
§9 OUTWARD = real discovery (≥2 sources with url+tool+learned, ≥1 query)
§11 INNOVATION candidates MUST derive_from prior claims; ≥1 from OUTWARD
§12 SYNTHESIS decisions cite evidence_used
§14 MORE_RESEARCH requires named blocking_unknowns
"""
from __future__ import annotations
import importlib.util
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
SPEC = importlib.util.spec_from_file_location("research_artifact", ROOT / "scripts" / "research_artifact.py")
ra = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(ra)
def make_artifact(**overrides):
base = {
"issue": 42,
"round": 1,
"receipt": {"search_or_fetch_calls": 3, "distinct_web_domains": 2},
"phases": {
"inward": {
"claims": [{"id": "I1", "claim": "store uses FTS5", "evidence": ["src/rune/memory_store.py:86"]}]
},
"outward": {
"queries": ["python sqlite fts5 hybrid search"],
"sources": [
{
"id": "O1",
"url": "https://sqlite.org/fts5.html",
"title": "FTS5",
"retrieved_at": "2026-09-08T00:00Z",
"tool": "webfetch",
"content_hash": "abc",
"learned": "FTS5 supports external content tables",
},
{
"id": "O2",
"url": "https://github.com/asg017/sqlite-vec",
"title": "sqlite-vec",
"retrieved_at": "2026-09-08T00:00Z",
"tool": "webfetch",
"content_hash": "def",
"learned": "vec0 virtual tables for embedding search",
},
],
"claims": [{"id": "O-C1", "claim": "hybrid FTS+vec is viable", "supports": ["O1", "O2"]}],
},
"innovation": {
"candidates": [
{
"id": "C1",
"proposal": "hybrid recall via FTS+vec union",
"derived_from": ["I1", "O-C1"],
"expected_gain": "better recall",
"risks": ["latency"],
},
]
},
"synthesis": {
"decision": "adopt hybrid recall",
"evidence_used": ["I1", "O-C1", "C1"],
"blocking_unknowns": [],
"feasibility": "mechanism proven by O2",
"kill_test": "recall returns >0 rows on seeded store",
"disposition": "PROCEED",
},
},
}
for k, v in overrides.items():
if k == "phases":
base["phases"].update(v)
else:
base[k] = v
return base
def test_valid_artifact_passes():
assert ra.validate(make_artifact()) == []
def test_outward_single_source_fails():
a = make_artifact(
phases={
"outward": {
"queries": ["q"],
"sources": [{"id": "O1", "url": "https://x", "tool": "webfetch", "learned": "y"}],
"claims": [],
}
}
)
errs = ra.validate(a)
assert any("sources < 2" in e for e in errs)
def test_outward_missing_learned_fails():
a = make_artifact(
phases={
"outward": {
"queries": ["q"],
"sources": [
{"id": "O1", "url": "https://x", "tool": "webfetch", "learned": ""},
{"id": "O2", "url": "https://y", "tool": "webfetch", "learned": "z"},
],
"claims": [],
}
}
)
errs = ra.validate(a)
assert any("missing learned" in e for e in errs)
def test_outward_no_queries_fails():
a = make_artifact(
phases={
"outward": {
"queries": [],
"sources": [
{"id": "O1", "url": "https://x", "tool": "webfetch", "learned": "y"},
{"id": "O2", "url": "https://y", "tool": "webfetch", "learned": "z"},
],
"claims": [],
}
}
)
errs = ra.validate(a)
assert any("no search queries" in e for e in errs)
def test_innovation_without_derived_from_fails():
a = make_artifact(
phases={
"innovation": {
"candidates": [{"id": "C1", "proposal": "x", "derived_from": [], "expected_gain": "", "risks": []}]
}
}
)
errs = ra.validate(a)
assert any("no derived_from" in e for e in errs)
def test_innovation_unknown_claim_reference_fails():
a = make_artifact(
phases={
"innovation": {
"candidates": [
{"id": "C1", "proposal": "x", "derived_from": ["I999"], "expected_gain": "", "risks": []}
]
}
}
)
errs = ra.validate(a)
assert any("unknown claims" in e for e in errs)
def test_no_outward_derived_candidate_fails_unless_exempt():
a = make_artifact(
phases={
"innovation": {
"candidates": [{"id": "C1", "proposal": "x", "derived_from": ["I1"], "expected_gain": "", "risks": []}]
}
}
)
errs = ra.validate(a)
assert any("decorative" in e for e in errs)
# exemption: synthesis explicitly records no external mechanism relevant
a["phases"]["synthesis"]["decision"] = "internal-only; no external mechanism relevant"
errs2 = ra.validate(a)
assert not any("decorative" in e for e in errs2)
def test_synthesis_empty_evidence_used_fails():
a = make_artifact(
phases={
"synthesis": {
"decision": "x",
"evidence_used": [],
"blocking_unknowns": [],
"feasibility": "",
"kill_test": "",
"disposition": "PROCEED",
}
}
)
errs = ra.validate(a)
assert any("evidence_used empty" in e for e in errs)
def test_more_research_without_unknowns_fails():
a = make_artifact(
phases={
"synthesis": {
"decision": "x",
"evidence_used": ["I1"],
"blocking_unknowns": [],
"feasibility": "",
"kill_test": "",
"disposition": "MORE_RESEARCH",
}
}
)
errs = ra.validate(a)
assert any("blocking_unknowns" in e for e in errs)
def test_invalid_disposition_fails():
a = make_artifact(
phases={
"synthesis": {
"decision": "x",
"evidence_used": ["I1"],
"blocking_unknowns": [],
"feasibility": "",
"kill_test": "",
"disposition": "VIBES",
}
}
)
errs = ra.validate(a)
assert any("disposition" in e for e in errs)
+16
View File
@@ -174,3 +174,19 @@ def test_skips_issues_with_open_pr(forge, monkeypatch):
monkeypatch.setattr(rs, "open_pr_issue_refs", lambda api: {30})
targets = rs.compute_targets(forge.issues, forge)
assert [t["number"] for t in targets] == [31]
def test_more_research_round_not_treated_as_done(forge):
"""P0.6 §14: a completed round with MORE_RESEARCH disposition must NOT be
treated as done (the backfill sweep must not skip it as 'already
researched' if the label was lost — the stage re-dispatches)."""
forge.issues = [_issue(32)] # label lost (event delivery failure)
forge.comments = {32: [{"body": RESEARCH_COMMENT + "\n\nDisposition: MORE_RESEARCH (blocking: U1 perf numbers)"}]}
targets = rs.compute_targets(forge.issues, forge)
assert [t["number"] for t in targets] == [32], "MORE_RESEARCH stays eligible for re-dispatch"
def test_proceed_round_not_requeued(forge):
forge.issues = [_issue(33)] # NOT labeled (research-clear already ran)
forge.comments = {33: [{"body": RESEARCH_COMMENT + "\n\nDisposition: PROCEED"}]}
assert rs.compute_targets(forge.issues, forge) == []