Skip to content

Commit fc04897

Browse files
authored
[rustjava-worklog-proposal-threshold-and-kind] chore(worklog): 제안 문턱(최대 2) + 선택 필드 kind (#99)
[rustjava-worklog-proposal-threshold-and-kind] chore(worklog): 제안 문턱(최대 2) + 선택 필드 kind
2 parents b1947e0 + 6e5b218 commit fc04897

4 files changed

Lines changed: 43 additions & 1 deletion

File tree

‎.github/workflows/rust.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ jobs:
6060
runs-on: ubuntu-latest
6161
steps:
6262
- uses: actions/checkout@v7
63+
with:
64+
fetch-depth: 2 # HEAD^1 = base side of the PR merge commit (or previous main on push)
6365
- run: python3 scripts/check-worklog-json.py
66+
env:
67+
WORKLOG_BASE: HEAD^1
6468
- run: python3 scripts/check-ledgers-frozen.py
6569

6670
# a conflict resolution that drops one side's work leaves no other trace: no marker, mergeable

‎AGENTS.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ as written because it records decisions that were made at the time.
7373
Any other key (`schema`, `taskId`, `summary`, `changes`, `verification`, `issues`, …) is free —
7474
the consumer does not read them, so they are for humans and the next round.
7575

76+
**Proposal threshold (2026-09-25).** 09-14~24 this repo left 63 proposals, and each adoption bred
77+
0.98 new ones — 62% of them meta, longest chain 11 generations. So:
78+
- Ask otterpebble's three questions first (`.claude/rules/autonomy.md` §추천도 3문): observed this
79+
round · runnable now without waiting on anything · not a duplicate. Any "no" ⇒ one line in the reply, not a card.
80+
- **Zero proposals is the normal value.** At most **2 per worklog** — the lock fails a new or changed `.json` with 3+.
81+
- A proposal in the **3rd or later generation of a chain** (adopting a card that itself came from an adopted card) is not filed if it is meta.
82+
- Optional `proposals[i].kind`: `"product"` = a user- or product-visible result changes · `"meta"` = checker,
83+
guard, census, self-test, ratchet, ledger/doc rule, CI wiring. Absent = unclassified; do not backfill old
84+
worklogs. Tower builds its card badge and default filter from it. The lock rejects any other value.
85+
7686
**No retroactive conversion.** The convention applies to new rounds only; the lock asks only
7787
"if a `.json` exists, is it well-formed and does it have its `.md` sibling" — it never demands a
7888
`.json` for an existing `.md`. Lock: `scripts/check-worklog-json.py`, run by the `worklog_json`
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## [2026-09-25] worklog 제안 문턱 + `kind` 필드 (rustjava-worklog-proposal-threshold-and-kind)
2+
- 무엇을: `AGENTS.md` 에 제안 문턱(추천도 3문 참조 · 0개가 정상 · worklog 당 최대 2 · 체인 3대째 meta 금지)과 선택 필드 `proposals[i].kind`(`product`|`meta`)를 넣고, `check-worklog-json.py` 가 `kind` 값과 «새/변경 worklog 의 제안 3개 이상»을 red 로 잡게 했다. 과거 파일은 base 와 바이트 동일하면 통과(CI 는 `WORKLOG_BASE=HEAD^1` · fetch-depth 2).
3+
- 왜: 09-14~24 제안 63건 · 채택 1건당 새 제안 0.98 · meta 62% · 최장 체인 11대 — 입구에 문턱이 없었다.
4+
- 사용자 영향: 없음(문서·검사기만). 후속 제안 없음.

‎scripts/check-worklog-json.py‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
"""
1414

1515
import json
16+
import os
1617
import re
18+
import subprocess
1719
import sys
1820
from pathlib import Path
1921

@@ -25,6 +27,23 @@
2527
PROPOSAL_KEYS = ("title", "plainSummary", "userBenefit", "why", "tradeoff", "effort", "target")
2628
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}")
2729

30+
KINDS = ("product", "meta")
31+
MAX_PROPOSALS = 2 # AGENTS.md "Proposal threshold"
32+
33+
# The cap binds only new or changed files: a past worklog identical to the base passes (no retroactive edits).
34+
# CI sets WORKLOG_BASE=HEAD^1 (the PR merge commit's base side); locally the default is the fork point from origin/main.
35+
BASE = os.environ.get("WORKLOG_BASE") or subprocess.run(
36+
["git", "merge-base", "HEAD", "origin/main"], cwd=ROOT, capture_output=True, text=True
37+
).stdout.strip()
38+
39+
40+
def at_base(p):
41+
if not BASE:
42+
return None
43+
r = subprocess.run(["git", "show", f"{BASE}:{p.relative_to(ROOT).as_posix()}"], cwd=ROOT, capture_output=True)
44+
return r.stdout if r.returncode == 0 else None
45+
46+
2847
fail = []
2948

3049

@@ -58,6 +77,9 @@ def check(cond, msg):
5877
for k in PROPOSAL_KEYS:
5978
v = prop.get(k)
6079
check(isinstance(v, str) and v.strip(), f"{p.name}#p{i}: '{k}' missing — that field renders empty on the card")
80+
check("kind" not in prop or prop["kind"] in KINDS, f"{p.name}#p{i}: kind {prop.get('kind')!r} — must be one of {KINDS} or absent")
81+
if len(proposals) > MAX_PROPOSALS and at_base(p) != p.read_bytes():
82+
fail.append(f"{p.name}: {len(proposals)} proposals in a new/changed worklog — the cap is {MAX_PROPOSALS} (AGENTS.md \"Proposal threshold\")")
6183

6284
for key in ("adoptedProposals", "declinedProposals"):
6385
refs = obj.get(key, [])
@@ -74,5 +96,7 @@ def check(cond, msg):
7496

7597
for m in fail:
7698
print(f"WORKLOG-JSON: {m}", file=sys.stderr)
77-
print(f"checked {len(list(WORKLOG.glob('*.json')))} worklog .json file(s), {len(fail)} problem(s)")
99+
if not BASE:
100+
print("WORKLOG-JSON: no git base resolved — every file counts as changed for the proposal cap", file=sys.stderr)
101+
print(f"checked {len(list(WORKLOG.glob('*.json')))} worklog .json file(s) against base {BASE[:12] or '-'}, {len(fail)} problem(s)")
78102
sys.exit(1 if fail else 0)

0 commit comments

Comments
 (0)