tasks: stop tracking current task set; fix t2 integration test for emptyNote
Context:
The current 40-task set is being split into a private holdout set plus a
new public set. The public repo will ship a different task set that
doesn't give away the holdout; in the meantime, stop tracking the current
tasks/ directory so benchmarking can continue locally without exposing
the set externally.
Changes:
- .gitignore: add tasks/ and lab-pr68627/ (vendored PR content, also
moving out of the public repo).
- git rm --cached tasks/: remove from tracking (files remain on disk
locally).
- tests/test_integration_checks.py:
* Module-level pytest.mark.skipif that skips the whole file when
tasks/ is absent — so CI against the public repo (no tasks)
stays green once the private set moves out.
* Update the t2-node-search-patch fixture to also define emptyNote()
since the task was hardened with that distractor. Without this, the
integration test asserts score==1.0 but gets 0.0 (the new
"emptyNote stays empty" test fails against a fixture that never
defines emptyNote).
Follow-up (separate work):
Public task set lands in a subsequent commit. Holdout access path
(encrypted-in-repo or private-repo) gets wired into the harness's
private_tasks_root / hidden_tasks_dir plumbing.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
95b226dfed
commit
deb3d5d85d
5
.gitignore
vendored
5
.gitignore
vendored
@ -15,3 +15,8 @@ reports/
|
||||
scripts/__pycache__/
|
||||
scripts/*.tmp
|
||||
scripts/*.local.py
|
||||
|
||||
# Task set is being split into a private holdout + a new public set.
|
||||
# Until the public set lands, keep the current tasks/ local-only.
|
||||
tasks/
|
||||
lab-pr68627/
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
"""Shared search helper for v0.5 verifiers.
|
||||
|
||||
Verifiers must accept artifacts wherever the agent wrote them. The OpenClaw
|
||||
agent's AGENTS.md instructs it to capture notes in memory/YYYY-MM-DD.md, so
|
||||
many tasks end up with content in memory/ rather than the workspace path the
|
||||
verifier originally expected.
|
||||
|
||||
This module is copied into each asset pack rather than imported, because
|
||||
verifiers run from the per-run workspace where the package isn't installed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_",
|
||||
"/.git/",
|
||||
"/.openclaw/",
|
||||
"BOOTSTRAP.md",
|
||||
"IDENTITY.md",
|
||||
"AGENTS.md",
|
||||
"USER.md",
|
||||
"SOUL.md",
|
||||
"HEARTBEAT.md",
|
||||
"MEMORY.md",
|
||||
)
|
||||
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path | str = "."):
|
||||
root = Path(root)
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def find_with_all(needed_lower: list[str], root: str = ".") -> tuple[Path | None, str]:
|
||||
"""Return the first text file containing every substring in needed_lower."""
|
||||
needed = [s.lower() for s in needed_lower]
|
||||
for path, text in iter_workspace_text_files(root):
|
||||
text_lower = text.lower()
|
||||
if all(s in text_lower for s in needed):
|
||||
return path, text
|
||||
return None, ""
|
||||
|
||||
|
||||
def find_with_any(any_lower: list[str], root: str = ".") -> tuple[Path | None, str]:
|
||||
"""Return the first text file containing any substring in any_lower."""
|
||||
any_set = [s.lower() for s in any_lower]
|
||||
for path, text in iter_workspace_text_files(root):
|
||||
text_lower = text.lower()
|
||||
if any(s in text_lower for s in any_set):
|
||||
return path, text
|
||||
return None, ""
|
||||
|
||||
|
||||
def collect_all_text(root: str = ".") -> str:
|
||||
"""Concatenate every text file in the workspace into one searchable blob."""
|
||||
parts = []
|
||||
for _, text in iter_workspace_text_files(root):
|
||||
parts.append(text)
|
||||
return "\n".join(parts)
|
||||
@ -1,12 +0,0 @@
|
||||
from shop.cart import CartService
|
||||
from shop.formatting import format_cents
|
||||
|
||||
|
||||
def render_total(price_cents: int, quantity: int) -> str:
|
||||
total = CartService().line_total(price_cents, quantity)
|
||||
return format_cents(total)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(render_total(1299, 2))
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
class CartService:
|
||||
def line_total(self, price_cents: int, quantity: int) -> int:
|
||||
return price_cents * quantity
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
def format_cents(value: int) -> str:
|
||||
dollars = value / 100
|
||||
return f"${dollars:0.2f}"
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
from app import render_total
|
||||
|
||||
|
||||
def test_render_total():
|
||||
assert render_total(1299, 2) == "$25.98"
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
REQUIRED_KEYS = {
|
||||
"entrypoint",
|
||||
"total_module",
|
||||
"formatter_module",
|
||||
"smoke_test",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_path(value: str) -> str:
|
||||
return value.strip().replace("\\", "/").removeprefix("./").lower()
|
||||
|
||||
|
||||
def _contains_path(value: str, expected: str) -> bool:
|
||||
normalized = _normalize_path(value)
|
||||
return normalized == expected or normalized.endswith(expected)
|
||||
|
||||
|
||||
def _is_valid_smoke_command(value: str) -> bool:
|
||||
normalized = " ".join(value.strip().lower().split())
|
||||
if "pytest" not in normalized:
|
||||
return False
|
||||
return (
|
||||
"tests/test_smoke.py" in normalized
|
||||
or "test_smoke.py" in normalized
|
||||
or normalized in {"pytest -q", "python -m pytest -q", "python3 -m pytest -q"}
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
payload = json.loads(Path("architecture.json").read_text(encoding="utf-8"))
|
||||
assert isinstance(payload, dict)
|
||||
assert REQUIRED_KEYS.issubset(payload)
|
||||
assert _contains_path(str(payload["entrypoint"]), "app.py")
|
||||
assert _contains_path(str(payload["total_module"]), "shop/cart.py")
|
||||
assert _contains_path(str(payload["formatter_module"]), "shop/formatting.py")
|
||||
assert _is_valid_smoke_command(str(payload["smoke_test"]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,6 +0,0 @@
|
||||
from pricing import apply_discount
|
||||
|
||||
|
||||
def checkout_total(subtotal: int, discount_percent: int) -> int:
|
||||
return apply_discount(subtotal, discount_percent)
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
def apply_discount(subtotal_cents: int, discount_percent: int) -> int:
|
||||
# BUG: this subtracts the raw percent value instead of a percentage of the subtotal.
|
||||
return subtotal_cents - discount_percent
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
from cart import checkout_total
|
||||
|
||||
|
||||
def test_percentage_discount_applies_to_full_subtotal():
|
||||
assert checkout_total(2_000, 10) == 1_800
|
||||
|
||||
|
||||
def test_zero_discount_keeps_subtotal():
|
||||
assert checkout_total(1_250, 0) == 1_250
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
import re
|
||||
|
||||
LIST_PATTERNS = [
|
||||
re.compile(r"^\s*[-*+]\s+"),
|
||||
re.compile(r"^\s*\d+[.)]\s+"),
|
||||
re.compile(r"^\s*\[[ x]\]\s+"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for path, text in iter_workspace_text_files():
|
||||
if any(t in text.lower() for t in ("dry clean", "sam", "babysit", "60")):
|
||||
list_lines = sum(1 for line in text.splitlines() if any(p.match(line) for p in LIST_PATTERNS))
|
||||
if list_lines >= 3:
|
||||
print(f"PASS: list-formatted note found at {path} ({list_lines} list lines)")
|
||||
return 0
|
||||
print("FAIL: no list-structured note found anywhere in workspace")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,56 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blob = workspace_blob().lower()
|
||||
if not blob:
|
||||
print("FAIL: workspace contains no agent-written text files")
|
||||
return 1
|
||||
needed = ['dry clean', 'sam', 'babysit']
|
||||
if not all(s in blob for s in needed):
|
||||
missing = [s for s in needed if s not in blob]
|
||||
print(f"FAIL: workspace missing required content: {missing}")
|
||||
return 1
|
||||
needed = ['60']
|
||||
if not all(s in blob for s in needed):
|
||||
missing = [s for s in needed if s not in blob]
|
||||
print(f"FAIL: workspace missing required content: {missing}")
|
||||
return 1
|
||||
print("PASS: t1_fs_quick_note/verify_three_items.py")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,9 +0,0 @@
|
||||
Dear Mr. Chen,
|
||||
|
||||
Thank you very much for your patience while we finalized the contract terms.
|
||||
We are pleased to confirm that everything is in order and we look forward
|
||||
to beginning our partnership next Tuesday afternoon. Please let us know if
|
||||
you have any questions.
|
||||
|
||||
Best regards,
|
||||
The Procurement Team
|
||||
@ -1,48 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for path, text in iter_workspace_text_files():
|
||||
if not any("\u4e00" <= ch <= "\u9fff" for ch in text):
|
||||
continue
|
||||
if "您" in text:
|
||||
print(f"PASS: formal register (您) used in {path}")
|
||||
return 0
|
||||
print("FAIL: no Chinese text using formal 您 found in workspace")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,51 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for path, text in iter_workspace_text_files():
|
||||
if not any("\u4e00" <= ch <= "\u9fff" for ch in text):
|
||||
continue
|
||||
if "Dear Mr. Chen" in text or "The Procurement Team" in text:
|
||||
continue # the original english source
|
||||
if len(text.strip()) < 20:
|
||||
continue
|
||||
print(f"PASS: Chinese translation present at {path}")
|
||||
return 0
|
||||
print("FAIL: no non-trivial Chinese translation found anywhere in workspace")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,18 +0,0 @@
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def load_rows(lines: Iterable[str]) -> list[dict[str, str | int]]:
|
||||
rows: list[dict[str, str | int]] = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
raw_name, raw_count = line.split(",", 1)
|
||||
rows.append(
|
||||
{
|
||||
"name": raw_name.strip().lower(),
|
||||
"count": int(raw_count.strip()),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
def summarize_inventory(lines: list[str]) -> dict[str, int]:
|
||||
"""Aggregate counts per item. Case-insensitive bucketing but the output
|
||||
key uses the FIRST-seen display case (so a user-facing report keeps the
|
||||
original capitalization as entered)."""
|
||||
summary: dict[str, int] = {}
|
||||
display_name: dict[str, str] = {} # lowercase key -> first-seen original
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
raw_name, raw_count = line.split(",", 1)
|
||||
original = raw_name.strip()
|
||||
key = original.lower()
|
||||
count = int(raw_count.strip())
|
||||
if key not in display_name:
|
||||
display_name[key] = original
|
||||
# Aggregate under the display-name (first-seen case)
|
||||
display = display_name[key]
|
||||
summary[display] = summary.get(display, 0) + count
|
||||
return summary
|
||||
@ -1,22 +0,0 @@
|
||||
from csv_loader import load_rows
|
||||
from report_builder import summarize_inventory
|
||||
|
||||
|
||||
def test_load_rows_skips_comments_and_normalizes_names():
|
||||
# load_rows returns lowercase-normalized names (matches stored SKU keys).
|
||||
rows = load_rows(["# ignore", " Apples , 2 ", "bananas,5"])
|
||||
assert rows == [{"name": "apples", "count": 2}, {"name": "bananas", "count": 5}]
|
||||
|
||||
|
||||
def test_summarize_inventory_aggregates_case_insensitively_preserving_display_case():
|
||||
# summarize_inventory aggregates case-insensitively BUT keeps the first-seen
|
||||
# original display case (APPLES + apples both roll up under "APPLES").
|
||||
# This is distinct from load_rows's lowercase normalization — any shared
|
||||
# helper must accommodate BOTH behaviors without duplicating the parsing.
|
||||
summary = summarize_inventory(["APPLES,2", "apples,3", "pears,1"])
|
||||
assert summary == {"APPLES": 5, "pears": 1}
|
||||
|
||||
|
||||
def test_summarize_inventory_preserves_first_seen_case_across_variants():
|
||||
summary = summarize_inventory(["Bread,1", "BREAD,2", "bread,3"])
|
||||
assert summary == {"Bread": 6}
|
||||
@ -1,27 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parsing = Path("parsing.py").read_text(encoding="utf-8")
|
||||
csv_loader = Path("csv_loader.py").read_text(encoding="utf-8")
|
||||
report_builder = Path("report_builder.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "def parse_inventory_row" in parsing
|
||||
assert "from parsing import parse_inventory_row" in csv_loader
|
||||
assert "from parsing import parse_inventory_row" in report_builder
|
||||
|
||||
completed = subprocess.run(
|
||||
[sys.executable, "-m", "pytest", "-q"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert completed.returncode == 0, completed.stdout + completed.stderr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,14 +0,0 @@
|
||||
import re
|
||||
|
||||
EMOJI_RE = re.compile(r"[\U0001F300-\U0001FAFF]")
|
||||
|
||||
|
||||
def normalize_title(text: str) -> str:
|
||||
cleaned = " ".join(text.split())
|
||||
cleaned = EMOJI_RE.sub("", cleaned)
|
||||
return cleaned.strip().title()
|
||||
|
||||
|
||||
def normalize_tags(raw: str) -> list[str]:
|
||||
return [part.strip().lower() for part in raw.split(",") if part.strip()]
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
BUGGY_EMOJI = """import re
|
||||
|
||||
EMOJI_RE = re.compile(r"[\\U0001F300-\\U0001FAFF]")
|
||||
|
||||
|
||||
def normalize_title(text: str) -> str:
|
||||
cleaned = " ".join(text.split())
|
||||
return cleaned.strip().title()
|
||||
|
||||
|
||||
def normalize_tags(raw: str) -> list[str]:
|
||||
return [part.strip().lower() for part in raw.split(",") if part.strip()]
|
||||
"""
|
||||
|
||||
BUGGY_TAGS = """import re
|
||||
|
||||
EMOJI_RE = re.compile(r"[\\U0001F300-\\U0001FAFF]")
|
||||
|
||||
|
||||
def normalize_title(text: str) -> str:
|
||||
cleaned = " ".join(text.split())
|
||||
cleaned = EMOJI_RE.sub("", cleaned)
|
||||
return cleaned.strip().title()
|
||||
|
||||
|
||||
def normalize_tags(raw: str) -> list[str]:
|
||||
return [part.strip().lower() for part in raw.split(",")]
|
||||
"""
|
||||
|
||||
|
||||
def _run_pytest(*args: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, "-m", "pytest", "-q", *args],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def _expect_mutant_failure(normalizer_path: Path, mutant_source: str, label: str) -> None:
|
||||
backup = normalizer_path.read_text(encoding="utf-8")
|
||||
normalizer_path.write_text(mutant_source, encoding="utf-8")
|
||||
try:
|
||||
result = _run_pytest("tests/test_normalizer.py")
|
||||
assert result.returncode != 0, f"student tests did not catch mutant: {label}"
|
||||
finally:
|
||||
normalizer_path.write_text(backup, encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
test_path = Path("tests/test_normalizer.py")
|
||||
assert test_path.exists(), "tests/test_normalizer.py is missing"
|
||||
|
||||
baseline = _run_pytest()
|
||||
assert baseline.returncode == 0, baseline.stdout + baseline.stderr
|
||||
|
||||
normalizer_path = Path("normalizer.py")
|
||||
_expect_mutant_failure(normalizer_path, BUGGY_EMOJI, "emoji stripping")
|
||||
_expect_mutant_failure(normalizer_path, BUGGY_TAGS, "blank tag handling")
|
||||
|
||||
source = test_path.read_text(encoding="utf-8").lower()
|
||||
assert "normalize_title" in source
|
||||
assert "normalize_tags" in source
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,16 +0,0 @@
|
||||
const form = document.getElementById("contact-formm");
|
||||
const emailInput = document.getElementById("email");
|
||||
const statusNode = document.getElementById("status");
|
||||
|
||||
if (form) {
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const email = emailInput.value.trim();
|
||||
if (!email.includes("@")) {
|
||||
statusNode.textContent = "Enter a valid email.";
|
||||
return;
|
||||
}
|
||||
statusNode.textContent = `Saved ${email}`;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Newsletter Signup</title>
|
||||
<script defer src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Join the Newsletter</h1>
|
||||
<form id="contact-form">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" name="email" type="email" />
|
||||
<button id="submit-button" type="submit">Sign up</button>
|
||||
</form>
|
||||
<p id="status" aria-live="polite"></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
|
||||
class Handler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
if self.path == "/health":
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"ok")
|
||||
return
|
||||
return super().do_GET()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(os.environ.get("PORT", "8123"))
|
||||
server = ThreadingHTTPServer(("127.0.0.1", port), Handler)
|
||||
server.serve_forever()
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
async function main() {
|
||||
const url = process.argv[2];
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
await page.goto(url, { waitUntil: "networkidle" });
|
||||
await page.fill("#email", "reader@example.com");
|
||||
await page.click("#submit-button");
|
||||
await page.waitForFunction(() => document.querySelector("#status").textContent.includes("Saved"), null, {
|
||||
timeout: 3000,
|
||||
});
|
||||
const status = await page.textContent("#status");
|
||||
await browser.close();
|
||||
if (status.trim() !== "Saved reader@example.com") {
|
||||
throw new Error(`Unexpected status: ${status}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message || String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
DEFAULTS = {
|
||||
"host": "127.0.0.1",
|
||||
"port": 8080,
|
||||
"debug": False,
|
||||
}
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from app_config import DEFAULTS
|
||||
|
||||
|
||||
def load_config(path: str | None = None) -> dict[str, object]:
|
||||
config = dict(DEFAULTS)
|
||||
if path:
|
||||
config.update(json.loads(Path(path).read_text(encoding="utf-8")))
|
||||
# BUG: file values incorrectly win over environment overrides.
|
||||
if "APP_PORT" in os.environ and path:
|
||||
config["port"] = json.loads(Path(path).read_text(encoding="utf-8")).get("port", DEFAULTS["port"])
|
||||
if "APP_DEBUG" in os.environ:
|
||||
config["debug"] = os.environ["APP_DEBUG"]
|
||||
return config
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from config_loader import load_config
|
||||
|
||||
|
||||
def test_env_port_overrides_file(tmp_path, monkeypatch):
|
||||
config_path = tmp_path / "config.json"
|
||||
config_path.write_text(json.dumps({"port": 9000, "debug": False}), encoding="utf-8")
|
||||
monkeypatch.setenv("APP_PORT", "9200")
|
||||
cfg = load_config(str(config_path))
|
||||
assert cfg["port"] == 9200
|
||||
|
||||
|
||||
def test_debug_flag_is_boolean(monkeypatch):
|
||||
monkeypatch.setenv("APP_DEBUG", "true")
|
||||
cfg = load_config(None)
|
||||
assert cfg["debug"] is True
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blob = workspace_blob().lower()
|
||||
if not blob:
|
||||
print("FAIL: workspace contains no agent-written text files")
|
||||
return 1
|
||||
needed = ['shanghai']
|
||||
if not all(s in blob for s in needed):
|
||||
missing = [s for s in needed if s not in blob]
|
||||
print(f"FAIL: workspace missing required content: {missing}")
|
||||
return 1
|
||||
needed = ['shenzhen']
|
||||
if not all(s in blob for s in needed):
|
||||
missing = [s for s in needed if s not in blob]
|
||||
print(f"FAIL: workspace missing required content: {missing}")
|
||||
return 1
|
||||
any_of = ['tuesday', 'tues', 'next week']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
print("PASS: t2_ctx_pronoun_resolve/verify_resolution.py")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"groups": [
|
||||
{"id": "design_team", "name": "Design Team", "members": ["sasha", "jin", "rio"]},
|
||||
{"id": "design_council", "name": "Design Council", "members": ["external_advisor_1", "external_advisor_2"]},
|
||||
{"id": "marketing_team", "name": "Marketing Team", "members": ["alex", "sam"]}
|
||||
]
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
# Q1 Marketing Report
|
||||
Q1 channel breakdown for the marketing team.
|
||||
@ -1,2 +0,0 @@
|
||||
# Q3 Engineering Report
|
||||
Engineering velocity, infra costs, incident postmortems.
|
||||
@ -1,2 +0,0 @@
|
||||
# Q3 Marketing Report
|
||||
Q3 channel breakdown, regional spend, ROI by campaign.
|
||||
@ -1,54 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blob = workspace_blob().lower()
|
||||
if not blob:
|
||||
print("FAIL: workspace contains no agent-written text files")
|
||||
return 1
|
||||
any_of = ['q3', 'marketing']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
any_of = ['design']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
print("PASS: t2_err_instruction_ambig/verify_clarification.py")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1 +0,0 @@
|
||||
FAKE_PNG_cat
|
||||
@ -1 +0,0 @@
|
||||
FAKE_PDF_contract
|
||||
@ -1 +0,0 @@
|
||||
random notes
|
||||
@ -1 +0,0 @@
|
||||
This folder is preserved as-is.
|
||||
@ -1 +0,0 @@
|
||||
FAKE_JPG_PIXELS_birthday
|
||||
@ -1 +0,0 @@
|
||||
FAKE_PNG_screenshot
|
||||
@ -1 +0,0 @@
|
||||
FAKE_PDF_q1_report
|
||||
@ -1 +0,0 @@
|
||||
FAKE_PDF_q1_report
|
||||
@ -1,24 +0,0 @@
|
||||
policy_version: 1
|
||||
description: My retention preferences for the Downloads folder.
|
||||
|
||||
categories:
|
||||
images:
|
||||
extensions: [".png", ".jpg", ".jpeg", ".gif", ".heic"]
|
||||
target: Downloads/sorted/images
|
||||
documents:
|
||||
extensions: [".pdf", ".docx", ".txt", ".md"]
|
||||
target: Downloads/sorted/docs
|
||||
archives:
|
||||
extensions: [".zip", ".tar", ".gz"]
|
||||
target: Downloads/sorted/archives
|
||||
|
||||
# Files matching this glob are protected — never moved or trashed.
|
||||
keep_markers:
|
||||
- ".keep"
|
||||
|
||||
# Duplicate detection: any file that has the same SHA-256 hash as another
|
||||
# in the same Downloads tree is a duplicate. Older copies go to .trash.
|
||||
dedupe:
|
||||
by: sha256
|
||||
keep: most_recent
|
||||
trash_dir: Downloads/.trash
|
||||
@ -1,48 +0,0 @@
|
||||
"""Verify duplicates were moved to .trash, originals kept (most recent retained)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def file_sha(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
trash = Path("Downloads/.trash")
|
||||
if not trash.exists():
|
||||
print("FAIL: Downloads/.trash missing")
|
||||
return 1
|
||||
|
||||
# The original asset pack contained two identical Q1 PDFs. After cleanup,
|
||||
# exactly ONE Q1 PDF should still be live (anywhere in Downloads/sorted)
|
||||
# and at least one should be in .trash.
|
||||
live_pdfs = []
|
||||
for path in Path("Downloads").rglob("*.pdf"):
|
||||
if ".trash" in path.parts:
|
||||
continue
|
||||
live_pdfs.append((path, file_sha(path)))
|
||||
|
||||
trashed_pdfs = list(trash.rglob("*.pdf"))
|
||||
|
||||
# Look for the Q1 hash specifically by content
|
||||
target_content = b"FAKE_PDF_q1_report\n"
|
||||
target_hash = hashlib.sha256(target_content).hexdigest()
|
||||
live_q1 = [p for p, h in live_pdfs if h == target_hash]
|
||||
|
||||
if len(live_q1) != 1:
|
||||
print(f"FAIL: expected exactly 1 live Q1 PDF, found {len(live_q1)}")
|
||||
return 1
|
||||
if not trashed_pdfs:
|
||||
print("FAIL: trash should contain at least one duplicate PDF")
|
||||
return 1
|
||||
|
||||
print(f"PASS: 1 live Q1 PDF, {len(trashed_pdfs)} duplicates in trash")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,24 +0,0 @@
|
||||
"""Verify that .keep-marked directories were not touched."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
protected = Path("Downloads/oldphotos")
|
||||
keep_marker = protected / ".keep"
|
||||
if not keep_marker.exists():
|
||||
print(f"FAIL: {keep_marker} was removed")
|
||||
return 1
|
||||
expected_file = protected / "birthday.jpg"
|
||||
if not expected_file.exists():
|
||||
print(f"FAIL: {expected_file} was moved or deleted (protected directory)")
|
||||
return 1
|
||||
print("PASS: .keep-marked directory preserved")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,60 +0,0 @@
|
||||
"""Verify that files were sorted according to retention_policy.yaml."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def main() -> int:
|
||||
policy_path = Path("retention_policy.yaml")
|
||||
if not policy_path.exists():
|
||||
print(f"FAIL: retention_policy.yaml missing")
|
||||
return 1
|
||||
policy = yaml.safe_load(policy_path.read_text(encoding="utf-8"))
|
||||
|
||||
sorted_root = Path("Downloads/sorted")
|
||||
if not sorted_root.exists():
|
||||
print(f"FAIL: Downloads/sorted directory missing")
|
||||
return 1
|
||||
|
||||
cats = policy.get("categories", {})
|
||||
failed = []
|
||||
cats_with_files = 0
|
||||
for cat_name, spec in cats.items():
|
||||
target = Path(spec["target"])
|
||||
exts = set(spec.get("extensions", []))
|
||||
# A category target dir is REQUIRED only if any file with a matching
|
||||
# extension still lives anywhere in Downloads (sorted or not).
|
||||
any_matching_in_downloads = any(
|
||||
p.suffix.lower() in exts and ".trash" not in p.parts
|
||||
for p in Path("Downloads").rglob("*")
|
||||
if p.is_file()
|
||||
)
|
||||
if not any_matching_in_downloads:
|
||||
continue # nothing to sort for this category — directory optional
|
||||
if not target.exists():
|
||||
failed.append(f"missing target {target} (files of this type exist in Downloads)")
|
||||
continue
|
||||
files_in_target = [f for f in target.iterdir() if f.is_file()]
|
||||
if not files_in_target:
|
||||
failed.append(f"{target} exists but is empty")
|
||||
continue
|
||||
bad_ext = [f for f in files_in_target if f.suffix.lower() not in exts]
|
||||
if bad_ext:
|
||||
failed.append(f"{target} contains wrong-ext files: {[f.name for f in bad_ext]}")
|
||||
continue
|
||||
cats_with_files += 1
|
||||
|
||||
if failed:
|
||||
print("FAIL:", "; ".join(failed))
|
||||
return 1
|
||||
|
||||
print(f"PASS: retention policy honored across {cats_with_files} active categories")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,23 +0,0 @@
|
||||
"""Verify symlinks were not followed and not destroyed."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# If a symlink fixture is set up at Downloads/external_link, it should
|
||||
# still exist as a symlink (if the asset pack provides one).
|
||||
candidate = Path("Downloads/external_link")
|
||||
if candidate.is_symlink():
|
||||
# Symlink preserved — pass
|
||||
print("PASS: symlink preserved as-is")
|
||||
return 0
|
||||
# If the test fixture didn't ship a symlink, this verifier is a no-op pass.
|
||||
print("PASS: no symlink fixture present (verifier is no-op)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1 +0,0 @@
|
||||
q3_marketing_budget_v3.xlsx
|
||||
@ -1 +0,0 @@
|
||||
filler 1
|
||||
@ -1 +0,0 @@
|
||||
filler 10
|
||||
@ -1 +0,0 @@
|
||||
filler 11
|
||||
@ -1 +0,0 @@
|
||||
filler 12
|
||||
@ -1 +0,0 @@
|
||||
filler 13
|
||||
@ -1 +0,0 @@
|
||||
filler 14
|
||||
@ -1 +0,0 @@
|
||||
filler 15
|
||||
@ -1 +0,0 @@
|
||||
filler 16
|
||||
@ -1 +0,0 @@
|
||||
filler 17
|
||||
@ -1 +0,0 @@
|
||||
filler 18
|
||||
@ -1 +0,0 @@
|
||||
filler 19
|
||||
@ -1 +0,0 @@
|
||||
filler 2
|
||||
@ -1 +0,0 @@
|
||||
filler 20
|
||||
@ -1 +0,0 @@
|
||||
filler 21
|
||||
@ -1 +0,0 @@
|
||||
filler 22
|
||||
@ -1 +0,0 @@
|
||||
filler 23
|
||||
@ -1 +0,0 @@
|
||||
filler 24
|
||||
@ -1 +0,0 @@
|
||||
filler 25
|
||||
@ -1 +0,0 @@
|
||||
filler 3
|
||||
@ -1 +0,0 @@
|
||||
filler 4
|
||||
@ -1 +0,0 @@
|
||||
filler 5
|
||||
@ -1 +0,0 @@
|
||||
filler 6
|
||||
@ -1 +0,0 @@
|
||||
filler 7
|
||||
@ -1 +0,0 @@
|
||||
filler 8
|
||||
@ -1 +0,0 @@
|
||||
filler 9
|
||||
@ -1,4 +0,0 @@
|
||||
SHEET: Q2 Marketing Budget
|
||||
Region,Q2 Spend
|
||||
NorthAmerica,380000
|
||||
TOTAL,820000
|
||||
@ -1,8 +0,0 @@
|
||||
SHEET: Regional Breakdown
|
||||
Q3 Marketing Budget by Region
|
||||
Region,Q3 Spend,Notes
|
||||
NorthAmerica,420000,Display + paid social
|
||||
EMEA,310000,Conference sponsorships
|
||||
APAC,180000,Influencer pilot
|
||||
LATAM,90000,Brand awareness
|
||||
TOTAL,1000000
|
||||
@ -1,4 +0,0 @@
|
||||
SHEET: Q3 Sales Numbers
|
||||
Region,Q3 Revenue
|
||||
NorthAmerica,2400000
|
||||
TOTAL,5800000
|
||||
@ -1 +0,0 @@
|
||||
filler 1
|
||||
@ -1 +0,0 @@
|
||||
filler 10
|
||||
@ -1 +0,0 @@
|
||||
filler 2
|
||||
@ -1 +0,0 @@
|
||||
filler 3
|
||||
@ -1 +0,0 @@
|
||||
filler 4
|
||||
@ -1 +0,0 @@
|
||||
filler 5
|
||||
@ -1 +0,0 @@
|
||||
filler 6
|
||||
@ -1 +0,0 @@
|
||||
filler 7
|
||||
@ -1 +0,0 @@
|
||||
filler 8
|
||||
@ -1 +0,0 @@
|
||||
filler 9
|
||||
@ -1,76 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# The agent must surface the Q3 marketing budget content. The desktop
|
||||
# copy is the explicit target, but accept any file the agent created
|
||||
# that contains the right content (Q3 marketing + region breakdowns).
|
||||
target_substrings = ["q3", "region"]
|
||||
decoy_q2 = ["q2 marketing", "q2 spend"]
|
||||
decoy_sales = ["q3 revenue", "q3 sales"]
|
||||
|
||||
found_path = None
|
||||
for path, text in iter_workspace_text_files():
|
||||
# Skip the original asset-pack files (we want files the agent
|
||||
# *placed* somewhere — typically a desktop/copy or report)
|
||||
if "/Documents/" in str(path) and "v3" in path.name:
|
||||
continue
|
||||
text_lower = text.lower()
|
||||
if all(s in text_lower for s in target_substrings) and "marketing" in text_lower:
|
||||
# Reject decoys
|
||||
if any(d in text_lower for d in decoy_q2):
|
||||
continue
|
||||
if any(d in text_lower for d in decoy_sales):
|
||||
continue
|
||||
found_path = path
|
||||
break
|
||||
|
||||
# Also accept agent text output (e.g. answer.md) that just NAMES the
|
||||
# right file
|
||||
if found_path is None:
|
||||
for path, text in iter_workspace_text_files():
|
||||
if "q3_marketing_budget_v3" in text.lower():
|
||||
found_path = path
|
||||
break
|
||||
|
||||
if found_path is None:
|
||||
print("FAIL: agent did not surface the correct Q3 marketing budget file")
|
||||
return 1
|
||||
print(f"PASS: agent surfaced Q3 marketing budget content at/in {found_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,45 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path")
|
||||
parser.add_argument("--json", action="store_true", dest="as_json")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def summarize_log(path: str) -> dict[str, object]:
|
||||
lines = Path(path).read_text(encoding="utf-8").splitlines()
|
||||
summary = {
|
||||
"total": 0,
|
||||
"levels": {},
|
||||
"services": {},
|
||||
}
|
||||
for line in lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
summary["total"] += 1
|
||||
parts = dict(item.split("=", 1) for item in line.split()[1:])
|
||||
# TODO: this should count all levels and services, not only ERROR rows.
|
||||
if parts["level"] == "ERROR":
|
||||
summary["levels"][parts["level"]] = summary["levels"].get(parts["level"], 0) + 1
|
||||
summary["services"][parts["service"]] = summary["services"].get(parts["service"], 0) + 1
|
||||
return summary
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
summary = summarize_log(args.path)
|
||||
if args.as_json:
|
||||
print(json.dumps(summary, sort_keys=True))
|
||||
else:
|
||||
print(summary)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
{"levels": {"ERROR": 2, "INFO": 2, "WARNING": 1}, "services": {"api": 2, "worker": 3}, "total": 5}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
2026-03-10T09:00:00Z level=INFO service=api message=started
|
||||
2026-03-10T09:00:01Z level=ERROR service=api message=timeout
|
||||
2026-03-10T09:00:02Z level=WARNING service=worker message=retrying
|
||||
2026-03-10T09:00:03Z level=ERROR service=worker message=crashed
|
||||
2026-03-10T09:00:04Z level=INFO service=worker message=recovered
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
Channel: #design-redesign
|
||||
Date range: 2026-04-05 to 2026-04-08
|
||||
|
||||
[Apr 5 09:14] Marcus: Quick proposal — for the homepage refresh, let's go with option A (single hero image, no carousel). Carousels test poorly.
|
||||
[Apr 5 09:18] Priya: I'm fine with A. Anything but the auto-rotating mess we have today.
|
||||
[Apr 5 09:22] Sam: Agree on A. Carousels are a UX antipattern.
|
||||
[Apr 5 09:30] Marcus: Cool, let's call it. Option A it is. I'll spec it out.
|
||||
[Apr 5 10:01] Priya: For typography, can we move to Inter? Easier reading and we already license it.
|
||||
[Apr 5 10:15] Sam: +1 Inter
|
||||
[Apr 5 11:42] Marcus: Inter approved. I'll add it to the spec.
|
||||
[Apr 6 08:55] Priya: Wait, on the homepage hero — I'm second-guessing this. What if we did option B (two-column with icon row) instead? It gives more above-the-fold info.
|
||||
[Apr 6 09:20] Marcus: Fair point. Let me think.
|
||||
[Apr 6 10:30] Sam: I prefer B too actually. More info density.
|
||||
[Apr 6 13:15] Marcus: OK I'm convinced. Switching to option B. Scratch yesterday's call. Final answer: B.
|
||||
[Apr 6 14:00] Sam: Great. So B for hero, Inter for type.
|
||||
[Apr 6 16:10] Priya: For the CTA button color, sticking with our brand orange right? #FF6B35.
|
||||
[Apr 6 16:14] Marcus: Yes brand orange. Don't touch the brand colors.
|
||||
[Apr 7 09:00] zhentongfan: Catching up on this thread — sounds like option B is locked in. I can take the spec writeup if Marcus is busy.
|
||||
[Apr 7 09:05] Marcus: Thanks zhentongfan, that'd be great. I owe you one.
|
||||
[Apr 7 09:30] zhentongfan: I'll have a draft by end of day Friday.
|
||||
[Apr 7 11:20] Priya: Open question — what happens to the testimonial section? Option B doesn't have a slot for it.
|
||||
[Apr 7 11:25] Sam: Good catch. Move it below the fold? Or kill it?
|
||||
[Apr 7 11:30] Priya: I'd vote move below the fold, not kill. Sales team will riot if we kill testimonials.
|
||||
[Apr 7 14:40] Marcus: Let's keep testimonials, just below the fold. Not killing them.
|
||||
[Apr 7 15:00] Sam: Open question still — what's the mobile breakpoint going to be?
|
||||
[Apr 7 15:30] Marcus: Open question for now. Let's defer to next sprint.
|
||||
[Apr 8 10:15] Priya: One more — favicon update? The current one is from 2019.
|
||||
[Apr 8 10:20] Sam: Lol yes please. Open item.
|
||||
[Apr 8 11:00] Marcus: Adding favicon to the followup list. Open question: who owns the asset.
|
||||
@ -1,54 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blob = workspace_blob().lower()
|
||||
if not blob:
|
||||
print("FAIL: workspace contains no agent-written text files")
|
||||
return 1
|
||||
any_of = ['spec', 'writeup', 'write-up']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
any_of = ['friday', 'you ', 'your ']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
print("PASS: t2_msg_summarize_thread/verify_commitments.py")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,50 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blob = workspace_blob().lower()
|
||||
if not blob:
|
||||
print("FAIL: workspace contains no agent-written text files")
|
||||
return 1
|
||||
any_of = ['option b', 'two-column', 'two column']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
print("PASS: t2_msg_summarize_thread/verify_latest_decision.py")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,55 +0,0 @@
|
||||
"""Recursive workspace search verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXCLUDE_FRAGMENTS = (
|
||||
"verify_", "/.git/", "/.openclaw/",
|
||||
"BOOTSTRAP.md", "IDENTITY.md", "AGENTS.md",
|
||||
"USER.md", "SOUL.md", "HEARTBEAT.md",
|
||||
)
|
||||
TEXT_SUFFIXES = (".md", ".txt", ".json", ".yaml", ".yml", ".csv", ".log",
|
||||
".jsonl", ".html", ".sh", ".py")
|
||||
|
||||
|
||||
def iter_workspace_text_files(root: Path = Path(".")):
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
sp = str(path)
|
||||
if any(frag in sp for frag in EXCLUDE_FRAGMENTS):
|
||||
continue
|
||||
if path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
try:
|
||||
yield path, path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def workspace_blob() -> str:
|
||||
return "\n".join(text for _, text in iter_workspace_text_files())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blob = workspace_blob().lower()
|
||||
if not blob:
|
||||
print("FAIL: workspace contains no agent-written text files")
|
||||
return 1
|
||||
needed = ['decision']
|
||||
if not all(s in blob for s in needed):
|
||||
missing = [s for s in needed if s not in blob]
|
||||
print(f"FAIL: workspace missing required content: {missing}")
|
||||
return 1
|
||||
any_of = ['open', 'still', 'outstanding']
|
||||
if not any(s in blob for s in any_of):
|
||||
print(f"FAIL: workspace missing any of: {any_of}")
|
||||
return 1
|
||||
print("PASS: t2_msg_summarize_thread/verify_summary_structure.py")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "notes-search",
|
||||
"private": true,
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
const { filterNotes } = require("./search");
|
||||
const { normalizeNote } = require("./render");
|
||||
|
||||
function searchNotes(notes, query) {
|
||||
return filterNotes(notes.map(normalizeNote), query);
|
||||
}
|
||||
|
||||
module.exports = { searchNotes };
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
function normalizeNote(note) {
|
||||
return {
|
||||
title: note.title.trim(),
|
||||
body: "",
|
||||
};
|
||||
}
|
||||
|
||||
// Legitimate empty-note factory. Callers use this as a placeholder when a
|
||||
// note is being created but hasn't been filled in yet. The empty body here
|
||||
// is intentional — do not "fix" it when scoping the search bug.
|
||||
function emptyNote() {
|
||||
return {
|
||||
title: "",
|
||||
body: "",
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { normalizeNote, emptyNote };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user