Add the project rules, requirements and task records
This commit is contained in:
252 files changed
+3829
-8
No files matched your search
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: review.sh <item-id> <round> [path...] — codex high review.
|
||||
set -euo pipefail
|
||||
|
||||
[ "${HERDR_ENV:-}" = 1 ] || { echo "review.sh: not inside herdr" >&2; exit 2; }
|
||||
id=${1:?item id}
|
||||
round=${2:?round}
|
||||
shift 2
|
||||
scope=${*:-"the files this item touched; find them from the item line and the tree"}
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
name="rv-$(echo "$id" | tr 'A-Z.' 'a-z-')-r$round"
|
||||
verdict="tasks/reviews/$id-r$round.md"
|
||||
mkdir -p tasks/reviews
|
||||
rm -f "$verdict"
|
||||
|
||||
prompt="You review item $id of Ely Office, round $round. Work dir is the project root; agents.md holds the rules.
|
||||
Read the line for $id in tasks/TASKS.md and the PRD.md sections it cites.
|
||||
Scope: $scope
|
||||
Find defects only: behavior that breaks the PRD, errors swallowed, silent fallbacks, dead code, files over 500 lines, comments over one line or ten words, boxes inside boxes or useless subtitles in UI, abstractions with one user, missing or weak tests, claims no evidence backs. Build and run the tests yourself.
|
||||
Copies of the tree you build for probes use their own CARGO_TARGET_DIR, never ./target: crates bake their source path in. Delete those copies and their target dirs before you write the verdict; keep only small logs.
|
||||
Do not suggest enhancements. Do not reject for taste. Edit no file except the verdict.
|
||||
Round 2 and later: read the previous verdict tasks/reviews/$id-r$((round - 1)).md, confirm each defect is fixed, then look for new defects in changed code.
|
||||
Write the verdict to $verdict. Line one is PASS or FAIL. After that, one entry per defect: file:line, what is wrong, a concrete failing scenario, the direction of the fix.
|
||||
When done, reply with the single word PASS or FAIL."
|
||||
|
||||
# The last review's pane stays open until the next starts.
|
||||
last=tasks/reviews/.pane
|
||||
if [ -f "$last" ]; then
|
||||
herdr pane close "$(cat "$last")" >/dev/null 2>&1 || true
|
||||
fi
|
||||
pane=$(herdr pane split --current --direction right --cwd "$PWD" --no-focus | jq -r '.result.pane.pane_id')
|
||||
echo "$pane" >"$last"
|
||||
started=
|
||||
for _ in 1 2 3 4 5; do
|
||||
if err=$(herdr agent start "$name" --kind codex --pane "$pane" --timeout 120000 -- --model gpt-6.1-sol -c 'model_reasoning_effort="high"' -c check_for_update_on_startup=false 2>&1); then
|
||||
started=1
|
||||
break
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
[ -n "$started" ] || { echo "review.sh: agent did not start in $pane: $err" >&2; exit 1; }
|
||||
herdr agent prompt "$name" "$prompt" --wait --timeout 3000000 >/dev/null
|
||||
for _ in $(seq 360); do
|
||||
[ -f "$verdict" ] && break
|
||||
sleep 5
|
||||
done
|
||||
|
||||
[ -f "$verdict" ] || { echo "review.sh: no verdict at $verdict; pane $pane left open" >&2; exit 1; }
|
||||
cat "$verdict"
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Writes docs/test-map.md: each PRD id, its tasks, their tests."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
LATER = ("MAIL", "AGENT")
|
||||
# Where each area's tests live, named or not.
|
||||
WHERE = {
|
||||
"UX": "crates/ely-ui/tests、shell.sh、doc-shell.sh、settings.sh",
|
||||
"FILE": "crates/ely-files、crates/ely-office、recovery.sh、autosave.sh",
|
||||
"LOK": "crates/ely-lok/src/client/tests、crates/ely-lok/src/view/tests、lokview.sh",
|
||||
"DOC": "apps/ely-doc/src/tests、doc-props.sh",
|
||||
"TABLE": "apps/ely-table/src/tests、theme.sh",
|
||||
"SHOW": "apps/ely-show/src/tests",
|
||||
"PDF": "apps/ely-pdf/src/tests、crates/ely-pdf-engine/tests、pdf-recovery.sh",
|
||||
"TREE": "apps/ely-tree/src/tests",
|
||||
"MARK": "apps/ely-mark 单测、mark-*.sh",
|
||||
"PERF": "tests/perf/bench.py、lag.sh",
|
||||
"QA": "见 docs/qa.md",
|
||||
}
|
||||
|
||||
|
||||
def read(name):
|
||||
with open(os.path.join(ROOT, name)) as text:
|
||||
return text.read()
|
||||
|
||||
|
||||
def ids():
|
||||
"""PRD table rows: id and its requirement."""
|
||||
for line in read("PRD.md").splitlines():
|
||||
if m := re.match(r"\| ([A-Z]+-\d+) \| (.+?) \|", line):
|
||||
yield m.group(1), m.group(2).split("|")[0].strip()
|
||||
|
||||
|
||||
def cites(line):
|
||||
"""PRD ids a line names: single, lists, ranges."""
|
||||
cited = set()
|
||||
for prefix, rest in re.findall(r"([A-Z]+)-(\d+(?:(?: ?至 ?|、)(?:[A-Z]+-)?\d+)*)", line):
|
||||
numbers = re.split(r"(?: ?至 ?|、)(?:[A-Z]+-)?", rest)
|
||||
ranges = re.findall(r"(?: ?至 ?|、)", rest)
|
||||
n = [int(x) for x in numbers]
|
||||
cited.add(n[0])
|
||||
for kind, (a, b) in zip(ranges, zip(n, n[1:])):
|
||||
cited.update(range(a, b + 1) if "至" in kind else [b])
|
||||
yield from (f"{prefix}-{k:02d}" for k in sorted(cited))
|
||||
cited = set()
|
||||
|
||||
|
||||
def tasks():
|
||||
"""Task lines and decisions: a label, the PRD ids, the line."""
|
||||
for line in read("tasks/TASKS.md").splitlines():
|
||||
if m := re.match(r"- \[(.)\] (C[\d.]+[a-z]?) ", line):
|
||||
yield m.group(2) + ("" if m.group(1) == "x" else f"({m.group(1)})"), set(cites(line)), line
|
||||
for line in read("agents.md").splitlines():
|
||||
if m := re.match(r"- (\d{4}-\d\d-\d\d)[^:]*:.*?((C[\d.]+)", line):
|
||||
yield f"决策 {m.group(2)}", set(cites(line)), line
|
||||
|
||||
|
||||
def known():
|
||||
"""Test function and script names present in the tree."""
|
||||
found = subprocess.run(["grep", "-rhoE", r"fn [a-z0-9_]+\(", "crates", "apps", "--include=*.rs"],
|
||||
cwd=ROOT, capture_output=True, text=True).stdout
|
||||
names = {name[3:-1] for name in found.split()}
|
||||
names |= {f for f in os.listdir(os.path.join(ROOT, "tests", "scripts"))}
|
||||
return names
|
||||
|
||||
|
||||
def main():
|
||||
present = known()
|
||||
rows = ["# 功能编号与测试对照", "", "对应 PRD §22。由 `scripts/test-map.py` 从 PRD、`tasks/TASKS.md` 与 `agents.md` 生成:每个编号列出引用它的任务与决策,其中写明、且在代码里确有其名的测试(单测函数名或 `tests/scripts` 的脚本),以及该领域测试所在之处。邮件与 Agent 属第二批,不列。", "",
|
||||
"| 编号 | 要求 | 任务 | 记下的测试 | 测试所在 |", "| --- | --- | --- | --- | --- |"]
|
||||
listed = list(tasks())
|
||||
for id_, need in ids():
|
||||
if id_.startswith(LATER):
|
||||
continue
|
||||
mine = [(label, line) for label, cited, line in listed if id_ in cited]
|
||||
named = []
|
||||
for _, line in mine:
|
||||
for name in re.findall(r"`([a-z0-9_.-]+)`|([a-z0-9-]+\.sh)", line):
|
||||
name = name[0] or name[1]
|
||||
if name in present and name not in named:
|
||||
named.append(name)
|
||||
labels = list(dict.fromkeys(label for label, _ in mine))
|
||||
where = WHERE[id_.split("-")[0]]
|
||||
rows.append(f"| {id_} | {need} | {'、'.join(labels) or '无'} | {'、'.join(f'`{n}`' for n in named) or '—'} | {where} |")
|
||||
with open(os.path.join(ROOT, "docs", "test-map.md"), "w") as out:
|
||||
out.write("\n".join(rows) + "\n")
|
||||
print(f"docs/test-map.md: {len(rows) - 6} ids")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user