Skip to content

Commit 5ac4b54

Browse files
authored
Merge pull request #27 from anombyte93/walkthrough/atlas-cli-surfaces
walkthrough: CLI surface honesty + discoverability fixes (validate-prd, watcher, set-status)
2 parents f2b9f33 + 38d34e3 commit 5ac4b54

7 files changed

Lines changed: 95 additions & 24 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ SHARING_TEMPLATES.md
3131
.taskmaster/
3232
.atlas-ai/
3333

34+
# walkthrough RAG runlog store — local-only by owner decision (2026-06-17)
35+
.i-walkthroughs/
36+
3437
# Logs
3538
logs
3639
*.log

prd_taskmaster/cli.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ def build_parser() -> argparse.ArgumentParser:
176176

177177
# engine-preflight (batched Phase 1: preflight + taskmaster + providers + capabilities)
178178
p = sub.add_parser("engine-preflight", help="One-call Phase 1: all probes + summary")
179-
p.add_argument("--no-configure", action="store_true")
179+
p.add_argument(
180+
"--no-configure", action="store_true",
181+
help="Skip auto-configuring providers; read-only probe (default configures providers on an existing .taskmaster project)",
182+
)
180183

181184
# detect-taskmaster
182185
sub.add_parser("detect-taskmaster", help="Find MCP or CLI taskmaster")
@@ -329,7 +332,7 @@ def build_parser() -> argparse.ArgumentParser:
329332

330333
# economy-report
331334
p = sub.add_parser("economy-report", help="Summarize .atlas-ai/telemetry.jsonl per (op_class, model)")
332-
p.add_argument("--input", default=None)
335+
p.add_argument("--input", default=None, help="Telemetry JSONL path (default: .atlas-ai/telemetry.jsonl)")
333336

334337
# context-pack
335338
p = sub.add_parser("context-pack", help="Extract AST-based Python signature context")
@@ -357,17 +360,20 @@ def build_parser() -> argparse.ArgumentParser:
357360

358361
# next-task
359362
p = sub.add_parser("next-task", help="Select the next TaskMaster-compatible task")
360-
p.add_argument("--tag")
363+
p.add_argument("--tag", help="Tasks.json tag context to read (default: the active/master tag)")
361364

362365
# claim-task
363366
p = sub.add_parser("claim-task", help="Atomically select and claim the next task")
364-
p.add_argument("--tag")
367+
p.add_argument("--tag", help="Tasks.json tag context to read (default: the active/master tag)")
365368

366369
# set-status
367370
p = sub.add_parser("set-status", help="Set a task or subtask status")
368-
p.add_argument("--id", required=True)
369-
p.add_argument("--status", required=True)
370-
p.add_argument("--tag")
371+
p.add_argument("--id", required=True, help="Task or subtask id (e.g. 7 or 1.2)")
372+
p.add_argument(
373+
"--status", required=True,
374+
help="Task status; one of: pending, in-progress, done, review, deferred, cancelled, blocked",
375+
)
376+
p.add_argument("--tag", help="Tasks.json tag context to write (default: the active/master tag)")
371377
p.add_argument(
372378
"--evidence-ref",
373379
default=None,

prd_taskmaster/tournament/watcher.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,12 +429,15 @@ def permit_enforce_slash(
429429
f"permitted: {len(confirmed)} confirmed slash(es), "
430430
f"concordance={concordance:.3f} over {observations} prior decisions"
431431
)
432-
elif not to_slash:
433-
reason = "blocked: no to-be-slashed submissions to confirm"
432+
# Surface the most-serious finding FIRST: a discrepancy (incl. a cheating
433+
# winner) or an abstain must never be masked by the benign "nothing to
434+
# slash" message when to_slash happens to be empty.
434435
elif discrepancies:
435436
reason = f"blocked: {len(discrepancies)} discrepancy(ies) in the job"
436437
elif abstained:
437438
reason = f"blocked: {len(abstained)} submission(s) in the job could not be independently verified (abstain)"
439+
elif not to_slash:
440+
reason = "blocked: no to-be-slashed submissions to confirm"
438441
elif not track_record_ok:
439442
reason = (
440443
f"blocked: thin track record — {observations}/{MIN_OBSERVATIONS} prior "

prd_taskmaster/validation.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,28 @@ def run_validate_prd(input_path: str) -> dict:
123123
reqs_section = get_section_content(text, "Functional Requirements")
124124
if not reqs_section:
125125
reqs_section = get_section_content(text, "Requirements")
126-
vague_in_reqs = VAGUE_PATTERN.findall(reqs_section)
127-
checks.append({
128-
"id": 6,
129-
"category": "required",
130-
"name": "Functional requirements are testable",
131-
"passed": len(vague_in_reqs) == 0,
132-
"detail": f"Vague terms found: {vague_in_reqs}" if vague_in_reqs else "All requirements are specific",
133-
"points": 5,
134-
})
126+
if not reqs_section.strip():
127+
# Fail-closed: an ABSENT requirements section must not vacuously pass and
128+
# claim "all requirements are specific" — functional requirements aren't
129+
# optional, so their absence is itself a testability failure.
130+
checks.append({
131+
"id": 6,
132+
"category": "required",
133+
"name": "Functional requirements are testable",
134+
"passed": False,
135+
"detail": "No requirements section found",
136+
"points": 5,
137+
})
138+
else:
139+
vague_in_reqs = VAGUE_PATTERN.findall(reqs_section)
140+
checks.append({
141+
"id": 6,
142+
"category": "required",
143+
"name": "Functional requirements are testable",
144+
"passed": len(vague_in_reqs) == 0,
145+
"detail": f"Vague terms found: {vague_in_reqs}" if vague_in_reqs else "All requirements are specific",
146+
"points": 5,
147+
})
135148

136149
# Check 7: Each requirement has priority (Must/Should/Could or P0/P1/P2)
137150
has_priority = bool(re.search(

tests/core/test_cli.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,23 @@ def test_native_expand_and_rate_no_key_return_agent_action_json(tmp_path, monkey
390390
)
391391
assert rate["ok"] is False
392392
assert rate["agent_action_required"]["op"] == "rate"
393+
394+
395+
def test_cli_flags_have_discoverable_help():
396+
"""Walkthrough fix: previously-undocumented flags must carry help text;
397+
set-status --status must reveal its valid vocabulary."""
398+
from prd_taskmaster.cli import build_parser
399+
p = build_parser()
400+
subs = p._subparsers._group_actions[0].choices
401+
402+
def flag_help(cmd, flag):
403+
for a in subs[cmd]._actions:
404+
if flag in a.option_strings:
405+
return a.help
406+
return None
407+
408+
assert flag_help("set-status", "--status"), "set-status --status needs help"
409+
assert "in-progress" in (flag_help("set-status", "--status") or ""), "status vocab must be discoverable"
410+
assert flag_help("engine-preflight", "--no-configure"), "--no-configure needs help"
411+
assert flag_help("next-task", "--tag"), "next-task --tag needs help"
412+
assert flag_help("economy-report", "--input"), "economy-report --input needs help"

tests/core/test_tournament_watcher.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,21 @@ def test_permit_blocks_on_abstained_winner(tmp_path):
323323
assert "ex-win" in out["abstained"]
324324

325325

326+
def test_permit_reason_surfaces_winner_discrepancy_over_empty_slash(tmp_path):
327+
# A cheating winner (recorded PASS, watcher DISCREPANCY) with NO to-be-slashed
328+
# subs must surface the discrepancy in the reason, not a benign 'no to-be-slashed'.
329+
ledger = tmp_path / "watcher.jsonl"
330+
_seed_ledger(ledger, decisions=watcher.MIN_OBSERVATIONS, confirmed=watcher.MIN_OBSERVATIONS)
331+
record = {"job_id": "job-1", "submissions": [
332+
_v("ex-win", agreement="DISCREPANCY", slash_grounds=True, recorded_passes_both=True),
333+
]}
334+
out = watcher.permit_enforce_slash(record, ledger_path=ledger)
335+
assert out["permitted"] is False
336+
assert "ex-win" in out["discrepancies"]
337+
assert "discrepanc" in out["reason"].lower()
338+
assert "no to-be-slashed" not in out["reason"].lower()
339+
340+
326341
def test_permit_blocks_on_empty_to_slash(tmp_path):
327342
# No to-be-slashed submissions → never a blanket green-light.
328343
ledger = tmp_path / "watcher.jsonl"

tests/core/test_validation.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,23 @@ def test_validate_empty_prd(self, tmp_project):
405405
empty_prd.write_text("")
406406
out = run_validate_prd(str(empty_prd))
407407
assert out["grade"] == "NEEDS_WORK"
408-
# 3 checks pass vacuously on empty PRD:
409-
# ch 5: no stories found → pass
410-
# ch 6: no vague reqs → pass
411-
# ch 10: no NFR section → pass
412-
assert out["checks_passed"] == 3
413-
assert out["score"] == 13 # 5 + 5 + 3
408+
# 2 checks pass on absent-but-OPTIONAL sections; ch 6 (functional
409+
# requirements) now FAILS-closed on an absent requirements section rather
410+
# than vacuously claiming "all requirements are specific".
411+
# ch 5: no stories found → pass (user stories optional)
412+
# ch 10: no NFR section → pass (NFRs optional)
413+
assert out["checks_passed"] == 2
414+
assert out["score"] == 8 # 5 (ch5) + 3 (ch10)
415+
416+
def test_check6_fails_closed_on_absent_requirements(self, tmp_project):
417+
"""Check 6 must not vacuously pass (and assert specificity) when there is
418+
no requirements section at all — functional requirements aren't optional."""
419+
prd = tmp_project / ".taskmaster" / "docs" / "prd.md"
420+
prd.write_text("# PRD: Thing\n\n## Overview\nA thing.\n") # no requirements section
421+
out = run_validate_prd(str(prd))
422+
check6 = next(c for c in out["checks"] if c["id"] == 6)
423+
assert check6["passed"] is False
424+
assert "no requirements" in check6["detail"].lower()
414425

415426
def test_validate_grade_boundaries(self, tmp_project):
416427
"""Verify grade boundary calculations match documented thresholds."""

0 commit comments

Comments
 (0)