okstra 0.139.0 → 0.141.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/cli.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +4 -2
- package/runtime/prompts/coding-preflight/clean-code.md +55 -0
- package/runtime/prompts/coding-preflight/overview.md +3 -2
- package/runtime/prompts/launch.template.md +7 -5
- package/runtime/prompts/lead/convergence.md +22 -0
- package/runtime/prompts/lead/plan-body-verification.md +45 -6
- package/runtime/prompts/lead/report-writer.md +2 -0
- package/runtime/prompts/profiles/_implementation-diff-review.md +3 -2
- package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -0
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +6 -4
- package/runtime/prompts/profiles/improvement-discovery.md +2 -1
- package/runtime/prompts/profiles/requirements-discovery.md +2 -1
- package/runtime/python/okstra_ctl/build_tools.py +95 -0
- package/runtime/python/okstra_ctl/incremental_scope.py +95 -0
- package/runtime/python/okstra_ctl/render.py +26 -11
- package/runtime/python/okstra_ctl/render_final_report.py +24 -1
- package/runtime/python/okstra_ctl/rollup.py +49 -0
- package/runtime/python/okstra_ctl/scope_provenance.py +98 -1
- package/runtime/python/okstra_ctl/stage_citations.py +47 -0
- package/runtime/python/okstra_ctl/workflow.py +7 -4
- package/runtime/schemas/final-report-v1.0.schema.json +60 -1
- package/runtime/skills/okstra-brief-gen/SKILL.md +38 -11
- package/runtime/skills/okstra-code-review/SKILL.md +1 -1
- package/runtime/skills/okstra-rollup/SKILL.md +3 -2
- package/runtime/templates/reports/brief.template.md +42 -11
- package/runtime/templates/reports/fan-out-unit.template.md +9 -4
- package/runtime/templates/reports/final-report.template.md +20 -0
- package/runtime/templates/reports/final-verification-input.template.md +4 -2
- package/runtime/templates/reports/i18n/en.json +11 -4
- package/runtime/templates/reports/i18n/ko.json +11 -4
- package/runtime/templates/reports/improvement-discovery-input.template.md +2 -2
- package/runtime/validators/validate-brief.py +171 -26
- package/runtime/validators/validate-run.py +450 -78
- package/runtime/validators/validate_fanout.py +19 -10
- package/runtime/validators/validate_improvement_report.py +17 -4
|
@@ -37,6 +37,11 @@ Checks performed per brief file:
|
|
|
37
37
|
columns and relation/direction values from the okstra-brief-gen contract.
|
|
38
38
|
13. The requirement/objective section `## Desired Outcome` (required in every
|
|
39
39
|
brief variant) exists and its body is not blank. `_(none)_` stays valid.
|
|
40
|
+
14. The end-state sections `## Expected Behavior` (EB-NNN) / `## Preserved
|
|
41
|
+
Behavior` (PB-NNN) / `## Expected Outcome` (EO-NNN): each item carries a
|
|
42
|
+
3-digit id and an `— verify: <how>` observation method, EB/EO are rejected
|
|
43
|
+
in the codebase-scan variant, and `## External Gates` — the destination for
|
|
44
|
+
an item with no observation method — is required in every variant.
|
|
40
45
|
|
|
41
46
|
Exit code 0 on PASS, 1 on FAIL.
|
|
42
47
|
"""
|
|
@@ -426,42 +431,182 @@ def check_requirement_section(text: str, errors: list[str]) -> None:
|
|
|
426
431
|
)
|
|
427
432
|
|
|
428
433
|
|
|
429
|
-
#
|
|
430
|
-
#
|
|
431
|
-
#
|
|
432
|
-
#
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
# The brief pins the end state once; every later phase maps to it by id rather
|
|
435
|
+
# than restating a goal of its own. Splitting behavior from outcome is what lets
|
|
436
|
+
# a refactor say "nothing observable changes" without leaving the upper bound
|
|
437
|
+
# blank — `Preserved Behavior` carries the bound that `Expected Behavior` can't.
|
|
438
|
+
_END_STATE_SECTIONS = (
|
|
439
|
+
("Expected Behavior", "EB"),
|
|
440
|
+
("Preserved Behavior", "PB"),
|
|
441
|
+
("Expected Outcome", "EO"),
|
|
442
|
+
)
|
|
443
|
+
# The destination for a must-pass point nobody can hand an observation method.
|
|
444
|
+
# It is enforced here rather than in ALWAYS_REQUIRED_SECTIONS so the failure can
|
|
445
|
+
# still say WHY the section exists: a generic "required section missing" gives
|
|
446
|
+
# the reporter no way to tell a gate apart from an end state.
|
|
447
|
+
_GATE_SECTION = "External Gates"
|
|
448
|
+
_END_STATE_ID_RE = re.compile(r"^(?P<id>(?:EB|PB|EO)-\d{3})\b")
|
|
449
|
+
# Mirrors the `[—-]` tolerance in scripts/okstra_ctl/scope_provenance.py so a
|
|
450
|
+
# reporter who cannot type an em dash is not silently rejected.
|
|
451
|
+
_VERIFY_RE = re.compile(r"[—-]{1,2}\s*verify:\s*(?P<how>\S.*)$")
|
|
452
|
+
_SCAN_ONLY_PREFIXES = frozenset({"EB", "EO"})
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _has_bullet(body: str) -> bool:
|
|
456
|
+
return any(line.strip().startswith("- ") for line in body.splitlines())
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _declares_nothing(body: str) -> bool:
|
|
460
|
+
"""True when the body is the contract's explicit empty marker and nothing else.
|
|
461
|
+
|
|
462
|
+
`_(none)_` is written bare in every other brief section — `is_placeholder`
|
|
463
|
+
accepts both the bare and the `- ` form, and the template tells the reporter
|
|
464
|
+
to "use _(none)_ if none". Demanding a dash here alone would reject the house
|
|
465
|
+
form, and the message that fires would tell the author to write the very
|
|
466
|
+
thing they wrote.
|
|
467
|
+
"""
|
|
468
|
+
lines = [line.strip() for line in body.splitlines() if line.strip()]
|
|
469
|
+
return bool(lines) and all(is_placeholder(line) for line in lines)
|
|
436
470
|
|
|
437
471
|
|
|
438
|
-
def
|
|
439
|
-
|
|
472
|
+
def _check_gate_section(text: str, errors: list[str]) -> None:
|
|
473
|
+
if not has_section_heading(text, _GATE_SECTION):
|
|
474
|
+
errors.append(
|
|
475
|
+
f"required section '## {_GATE_SECTION}' is missing — it is where a "
|
|
476
|
+
"must-pass point that a person or live infrastructure owns goes. "
|
|
477
|
+
"Without it, an item okstra cannot satisfy has nowhere to land but "
|
|
478
|
+
"the end-state sections, and the plan is then forced to build a "
|
|
479
|
+
"stage for work no phase can do"
|
|
480
|
+
)
|
|
481
|
+
return
|
|
482
|
+
if not section_body(text, _GATE_SECTION).strip():
|
|
483
|
+
errors.append(
|
|
484
|
+
f"required section '## {_GATE_SECTION}' has an empty body "
|
|
485
|
+
"(use _(none)_ only for a deliberately-empty section)"
|
|
486
|
+
)
|
|
487
|
+
|
|
440
488
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
for heading in SCOPE_SPLIT_SECTIONS:
|
|
448
|
-
if not has_section_heading(text, heading):
|
|
489
|
+
def _check_end_state_items(
|
|
490
|
+
text: str, heading: str, prefix: str, seen: set[str], errors: list[str]
|
|
491
|
+
) -> None:
|
|
492
|
+
for bullet in meaningful_bullets(text, heading):
|
|
493
|
+
match = _END_STATE_ID_RE.match(bullet)
|
|
494
|
+
if match is None or not match.group("id").startswith(f"{prefix}-"):
|
|
449
495
|
errors.append(
|
|
450
|
-
f"
|
|
451
|
-
"
|
|
452
|
-
"person/infrastructure-owned gates)"
|
|
496
|
+
f"'## {heading}' item {bullet!r} must start with a `{prefix}-NNN` id "
|
|
497
|
+
"(3 digits) — later phases map to the brief by id, not by heading"
|
|
453
498
|
)
|
|
454
499
|
continue
|
|
455
|
-
|
|
500
|
+
item_id = match.group("id")
|
|
501
|
+
if item_id in seen:
|
|
502
|
+
errors.append(f"duplicate end-state id {item_id!r}")
|
|
503
|
+
seen.add(item_id)
|
|
504
|
+
if not _VERIFY_RE.search(bullet):
|
|
456
505
|
errors.append(
|
|
457
|
-
f"
|
|
458
|
-
"
|
|
506
|
+
f"end-state item {item_id!r} has no observation method — append "
|
|
507
|
+
"`— verify: <command or observation point>`. An item nobody can "
|
|
508
|
+
"observe is not an end state. It has two destinations, both of "
|
|
509
|
+
f"which keep it in the brief: '## {_GATE_SECTION}' when a person "
|
|
510
|
+
"or live infrastructure owns it, or '## Open Questions' as a "
|
|
511
|
+
"`general:` row when the observation method is what is still "
|
|
512
|
+
"unknown. Deleting it is not one of them."
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _check_one_end_state_section(
|
|
517
|
+
text: str, heading: str, prefix: str, scope: str, seen: set[str], errors: list[str]
|
|
518
|
+
) -> None:
|
|
519
|
+
"""One end-state section: whether it may exist here, its body shape, its items.
|
|
520
|
+
|
|
521
|
+
A codebase-scan brief is authored before anything has been found, so it can
|
|
522
|
+
only pin what must NOT change. Declaring an expected behavior there would be
|
|
523
|
+
an invented requirement, which is why EB/EO are rejected rather than treated
|
|
524
|
+
as optional — and why `_(none)_` is rejected for the one section that
|
|
525
|
+
survives there: it is the whole upper bound of that variant.
|
|
526
|
+
"""
|
|
527
|
+
if scope == "codebase" and prefix in _SCAN_ONLY_PREFIXES:
|
|
528
|
+
if has_section_heading(text, heading):
|
|
529
|
+
errors.append(
|
|
530
|
+
f"section '## {heading}' is not allowed in a codebase-scan brief "
|
|
531
|
+
"(the scan has not run yet, so its outcome cannot be declared) — "
|
|
532
|
+
"use '## Preserved Behavior' for what must not change"
|
|
533
|
+
)
|
|
534
|
+
return
|
|
535
|
+
if not has_section_heading(text, heading):
|
|
536
|
+
errors.append(
|
|
537
|
+
f"required section '## {heading}' is missing (the brief pins the "
|
|
538
|
+
"end state that every later phase maps to)"
|
|
539
|
+
)
|
|
540
|
+
return
|
|
541
|
+
body = section_body(text, heading)
|
|
542
|
+
if not body.strip():
|
|
543
|
+
errors.append(
|
|
544
|
+
f"required section '## {heading}' has an empty body "
|
|
545
|
+
"(use _(none)_ only for a deliberately-empty section)"
|
|
546
|
+
)
|
|
547
|
+
return
|
|
548
|
+
if _declares_nothing(body):
|
|
549
|
+
# In a scan this is the only section left, so an empty marker here is
|
|
550
|
+
# not "deliberately empty" — it is a brief with no upper bound at all,
|
|
551
|
+
# which is what improvement-discovery reads as its bound.
|
|
552
|
+
if scope == "codebase":
|
|
553
|
+
errors.append(
|
|
554
|
+
f"'## {heading}' is _(none)_ in a codebase-scan brief. It is the "
|
|
555
|
+
"only end-state section this variant carries, and "
|
|
556
|
+
"improvement-discovery treats it as the bound on what a "
|
|
557
|
+
"candidate may change — an empty one bounds nothing. State at "
|
|
558
|
+
"least one behaviour no candidate from this scan may break"
|
|
459
559
|
)
|
|
560
|
+
return
|
|
561
|
+
# Only `- ` lines are read as items, so prose here is silently dropped
|
|
562
|
+
# rather than rejected — the reporter believes they pinned an end state
|
|
563
|
+
# and every downstream phase sees a section that declared nothing.
|
|
564
|
+
if not _has_bullet(body):
|
|
565
|
+
errors.append(
|
|
566
|
+
f"'## {heading}' has a body but no `- ` item — prose in this "
|
|
567
|
+
"section is dropped, not read. Every end state is a "
|
|
568
|
+
f"`- {prefix}-NNN <condition> — verify: <how>` bullet; write "
|
|
569
|
+
"_(none)_ on its own line if the section is deliberately empty"
|
|
570
|
+
)
|
|
571
|
+
return
|
|
572
|
+
_check_end_state_items(text, heading, prefix, seen, errors)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _check_end_state_declared(text: str, errors: list[str]) -> None:
|
|
576
|
+
"""A reporter-input brief declares at least one observable end state."""
|
|
577
|
+
if meaningful_bullets(text, "Expected Behavior") or meaningful_bullets(
|
|
578
|
+
text, "Expected Outcome"
|
|
579
|
+
):
|
|
580
|
+
return
|
|
581
|
+
errors.append(
|
|
582
|
+
"'## Expected Behavior' and '## Expected Outcome' are both empty — a brief "
|
|
583
|
+
"must declare at least one observable end state. A pure bugfix may leave "
|
|
584
|
+
"Expected Outcome as _(none)_ and a pure refactor may leave Expected "
|
|
585
|
+
"Behavior as _(none)_, but not both. If every must-pass point you have is "
|
|
586
|
+
f"owned by a person ('## {_GATE_SECTION}') or still lacks an observation "
|
|
587
|
+
"method ('## Open Questions'), this brief is not ready to start a run — "
|
|
588
|
+
"answer the open question first rather than emptying the end state"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def check_end_state_sections(text: str, scope: str, errors: list[str]) -> None:
|
|
593
|
+
"""The brief's end state: expected behavior, preserved behavior, expected outcome.
|
|
594
|
+
|
|
595
|
+
`## External Gates` is checked here too, in every variant: it is where an
|
|
596
|
+
item with no observation method goes, so the admissible set and its overflow
|
|
597
|
+
have to be required by the same check or the overflow silently disappears.
|
|
598
|
+
"""
|
|
599
|
+
_check_gate_section(text, errors)
|
|
600
|
+
seen: set[str] = set()
|
|
601
|
+
for heading, prefix in _END_STATE_SECTIONS:
|
|
602
|
+
_check_one_end_state_section(text, heading, prefix, scope, seen, errors)
|
|
603
|
+
if scope != "codebase":
|
|
604
|
+
_check_end_state_declared(text, errors)
|
|
460
605
|
|
|
461
606
|
|
|
462
607
|
# The variant-independent rows of the skill's "Required sections by variant"
|
|
463
|
-
# table (okstra-brief-gen SKILL.md). Desired Outcome and the
|
|
464
|
-
#
|
|
608
|
+
# table (okstra-brief-gen SKILL.md). Desired Outcome and the end-state
|
|
609
|
+
# sections / External Gates have dedicated checks with richer messages;
|
|
465
610
|
# Scan Scope / Priority Lenses are codebase-only (check_codebase_scope);
|
|
466
611
|
# Source Material / Problem / Symptom are omitted by the codebase variant, so
|
|
467
612
|
# they are enforced only off it.
|
|
@@ -563,7 +708,7 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
563
708
|
check_related_task_graph(text, errors)
|
|
564
709
|
|
|
565
710
|
check_requirement_section(text, errors)
|
|
566
|
-
|
|
711
|
+
check_end_state_sections(text, scope, errors)
|
|
567
712
|
check_variant_required_sections(text, scope, errors)
|
|
568
713
|
|
|
569
714
|
# 2. brief-id matches filename stem
|