okstra 0.150.0 → 0.151.1
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/docs/cli.md +2 -2
- package/docs/project-structure-overview.md +1 -1
- package/package.json +3 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -1
- package/runtime/agents/workers/claude-worker.md +2 -1
- package/runtime/agents/workers/codex-worker.md +2 -1
- package/runtime/agents/workers/grok-worker.md +2 -1
- package/runtime/agents/workers/kimi-worker.md +2 -1
- package/runtime/agents/workers/report-writer-worker.md +9 -1
- package/runtime/bin/okstra-report-translate.py +56 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/convergence.md +16 -4
- package/runtime/prompts/lead/plan-body-verification.md +1 -1
- package/runtime/prompts/lead/report-writer.md +28 -16
- package/runtime/prompts/lead/team-contract.md +2 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +8 -7
- package/runtime/python/okstra_ctl/analysis_packet.py +1 -0
- package/runtime/python/okstra_ctl/convergence.py +15 -1
- package/runtime/python/okstra_ctl/dispatch_state.py +5 -1
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +7 -0
- package/runtime/python/okstra_ctl/report_finalize.py +27 -4
- package/runtime/python/okstra_ctl/report_translation.py +29 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +4 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +41 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +3 -0
- package/runtime/templates/implementation-worker-preamble.md +12 -3
- package/runtime/templates/report-writer-prompt-preamble.md +5 -1
- package/runtime/templates/worker-prompt-preamble.md +12 -3
- package/runtime/validators/validate-run.py +142 -0
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- **External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
|
|
21
21
|
- no new defects introduced — the diff does not break previously-working behaviour and adds no new bug (logic/off-by-one, null/empty handling, resource leaks, broken error paths)
|
|
22
22
|
- scope conformance — the delivered diff stays within the approved plan's scope; flag out-of-scope edits, unrelated file changes, leftover debug/commented-out code, and unintended deletions
|
|
23
|
-
- project review-rule packs (when
|
|
23
|
+
- project review-rule packs (when brief-cited) — apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path; read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Apply the cited rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none cited`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
|
|
24
24
|
- Residual-tracked — note as Residual Risk unless severe enough to block:
|
|
25
25
|
- unresolved edge cases
|
|
26
26
|
- regression risk in adjacent code paths not directly changed
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
- **Follow established patterns**: in existing codebases, conform to current conventions. Targeted cleanup of a file you are already modifying is acceptable; unrelated refactors are not.
|
|
43
43
|
- **Variation-point extraction (OCP)**: when the same behavior is served by two or more resources / implementations — stated in the brief, or foreseeable from a sibling task or the code you inspected — the plan MUST record it in `variationPointAnalysis` and include an option that extracts the variation point behind an interface (a port, or a strategy the next implementation plugs into), scored against the non-extracted option in the trade-off matrix. Penalize an option that branches on resource identity inside a service (one `if` / `switch` arm per implementation): adding the next implementation then means editing that same call site again, which is the closed-for-extension shape this principle exists to catch. This does not contradict YAGNI below: YAGNI drops *speculative* variation (a second implementation nobody named), while a behavior with two implementations already on the table is a present fact, not a forecast. **Enforced:** the `variationPointAnalysis` bullet under `Required deliverable shape` names the schema / validator / `P-Var-*` enforcement points.
|
|
44
44
|
- **YAGNI ruthlessly**: drop features, abstractions, and configuration knobs that do not serve the stated requirement.
|
|
45
|
-
- **Project review-rule preflight**:
|
|
45
|
+
- **Project review-rule preflight**: apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path. Read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
|
|
46
46
|
- Expected output emphasis:
|
|
47
47
|
- feasible plan options
|
|
48
48
|
- dependency and risk visibility
|
|
@@ -80,11 +80,12 @@
|
|
|
80
80
|
- admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
|
|
81
81
|
- NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything inside an option the plan does not recommend, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
|
|
82
82
|
- A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
|
|
83
|
-
-
|
|
84
|
-
- The
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
83
|
+
- Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
|
|
84
|
+
- The plan lives in `data.json` under `implementationPlanning`, and `schemas/final-report-v2.0.schema.json` requires every one of these keys: `optionCandidates`, `tradeoffMatrix`, `recommendedOption`, `stageMap`, `stages`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `planBodyVerification`, `crossProjectDependencies`, `decisionDrafts`, `skippedAdrCandidates`, `variationPointAnalysis`, `userNarrative`. A missing block fails schema validation; there is nothing to satisfy by naming a heading. (Approval is not a body section — it is the YAML frontmatter `approved` field.)
|
|
85
|
+
- Each `stages[]` entry requires `stage`, `title`, `sliceValue`, `acceptance`, `carryIn`, `stepwiseExecution` (1–6 rows), `exitContract`, and `stageValidation`. Each `stageMap[]` row requires `stage`, `title`, `dependsOn`, `stepCount`, `exitContractSummary`.
|
|
86
|
+
- Beyond the schema, `validators/validate-run.py` reads the same data.json for `_validate_planning_conformance_declared`, `_validate_end_state_coverage`, `_validate_requirement_provenance`, `_validate_stage_has_requirement`, and `_validate_plan_body_state_file`. These run for every planning report regardless of schema version.
|
|
87
|
+
- **Do not chase English heading substrings.** `PLANNING_REQUIRED_SECTIONS` and the structural Stage Map scan (`validators/validate-implementation-plan-stages.py`) live inside `validate_phase_boundary`, which returns immediately when `schemaVersion == "2.0"` — they gate historical v1 Markdown only. The v2 AI-handoff template renders nine headings and serialises the plan as JSON beneath them, so those substrings cannot appear, and a report is not defective for lacking them.
|
|
88
|
+
- *Guideline, not gated:* write each `stepwiseExecution` row's `action` with a `RED:` / `GREEN:` prefix (or state a TDD exemption in `stageValidation`), and name the success / boundary / failure cases in `acceptance`. The v1 scan checked those tokens literally; v2 requires only that the fields be non-empty, so this is now review-enforced rather than validator-enforced.
|
|
88
89
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
89
90
|
- at least two implementation options. **Each option must include**:
|
|
90
91
|
- **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
|
|
@@ -188,7 +189,7 @@
|
|
|
188
189
|
3. **Internal consistency** — option file lists, trade-off matrix, and recommended step list must agree on file paths, names, and signatures. A symbol called `clearLayers()` in the matrix and `clearFullLayers()` in the steps is a bug.
|
|
189
190
|
4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
|
|
190
191
|
5. **Scope check** — if the recommended plan now spans multiple independent subsystems, recommend splitting into separate planning runs rather than shipping an oversized plan. Then walk the plan in the expansion direction: for every stage, name the Requirement Coverage row that demanded it, and for every requirement row, read its `Source` cell as a skeptic — does the cited brief heading actually exist, and does a `derived:` rationale state a real technical consequence rather than a preference? Move anything that fails to a `Blocks=approval` clarification row.
|
|
191
|
-
6. **Review-rule preflight check** — if a project review rule pack
|
|
192
|
+
6. **Review-rule preflight check** — if the task brief cites a project review rule pack under `Source Material` or `Reporter Confirmations`, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
|
|
192
193
|
7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose verdicts make it `majority-disagree`, set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
|
|
193
194
|
8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
|
|
194
195
|
9. **Cross-project dependency check** — confirm you have not missed a dependency on another repo / another top-level deployable module / a published package. If `dependencyMigrationRisk` has a `kind: cross-project` row, confirm a matching `direction: upstream-precondition` `XP-NNN` row exists in `crossProjectDependencies`, and re-read as a reviewer whether its `requiredWork` is the concrete work the other side must actually build rather than an abstract phrase ("other side's work done") — validator S only checks existence, so concreteness is the self-review's responsibility. Confirm cross-repo work is split into a separate run + XP row instead of being crammed into one task's stages, and that the cross-project substance is not duplicated in `§3 Recommended Next Steps` but lives only in `§5.4 Cross-Project Dependencies`.
|
|
@@ -123,7 +123,21 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
123
123
|
subparsers = parser.add_subparsers(dest="operation", required=True)
|
|
124
124
|
|
|
125
125
|
example = subparsers.add_parser(
|
|
126
|
-
"example",
|
|
126
|
+
"example",
|
|
127
|
+
help="print a deterministic input artifact example",
|
|
128
|
+
description=(
|
|
129
|
+
"Print one deterministic valid example as JSON.\n\n"
|
|
130
|
+
"`groups` feeds `seed --groups` and `round-results` feeds "
|
|
131
|
+
"`apply-round --results`, but `critic-results` is the critic "
|
|
132
|
+
"worker's own result document — NOT the `apply-critic-gaps "
|
|
133
|
+
"--results` input. That input is the coverage batch the lead "
|
|
134
|
+
"assembles from those candidates plus each analyser's vote "
|
|
135
|
+
'({schemaVersion, taskKey, mode: "coverage", provider, '
|
|
136
|
+
"modelExecutionValue, dispatches[], gaps[]}); see "
|
|
137
|
+
'prompts/lead/convergence.md §"Coverage critic". Feeding this '
|
|
138
|
+
"example straight into `apply-critic-gaps` is rejected, by design."
|
|
139
|
+
),
|
|
140
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
127
141
|
)
|
|
128
142
|
example.add_argument(
|
|
129
143
|
"--kind",
|
|
@@ -275,7 +275,11 @@ def validate_initial_prompts(
|
|
|
275
275
|
PromptRecord(job.worker_id, job.dispatch_kind, job.prompt_path)
|
|
276
276
|
for job in jobs
|
|
277
277
|
]
|
|
278
|
-
errors = validate_initial_prompt_records(
|
|
278
|
+
errors = validate_initial_prompt_records(
|
|
279
|
+
manifest=manifest,
|
|
280
|
+
records=records,
|
|
281
|
+
require_evidence_ledger=True,
|
|
282
|
+
)
|
|
279
283
|
if errors:
|
|
280
284
|
task_type = require_string(manifest, "taskType")
|
|
281
285
|
raise DispatchError(f"{task_type} prompt contract: " + "; ".join(errors))
|
|
@@ -521,6 +521,7 @@ def _validate_prepublication_set(
|
|
|
521
521
|
errors = validate_initial_prompt_records(
|
|
522
522
|
manifest=context.manifest,
|
|
523
523
|
records=[record],
|
|
524
|
+
require_evidence_ledger=True,
|
|
524
525
|
)
|
|
525
526
|
if errors:
|
|
526
527
|
raise InitialPromptMaterializationError(
|
|
@@ -530,6 +531,7 @@ def _validate_prepublication_set(
|
|
|
530
531
|
errors = validate_initial_prompt_records(
|
|
531
532
|
manifest=context.manifest,
|
|
532
533
|
records=records,
|
|
534
|
+
require_evidence_ledger=True,
|
|
533
535
|
)
|
|
534
536
|
if errors:
|
|
535
537
|
raise InitialPromptMaterializationError(
|
|
@@ -577,6 +579,7 @@ def _validate_existing_prompt(
|
|
|
577
579
|
basic_errors = validate_initial_prompt_records(
|
|
578
580
|
manifest=context.manifest,
|
|
579
581
|
records=[basic_record],
|
|
582
|
+
require_evidence_ledger=True,
|
|
580
583
|
)
|
|
581
584
|
if basic_errors:
|
|
582
585
|
raise InitialPromptMaterializationError(
|
|
@@ -593,6 +596,7 @@ def _validate_existing_prompt(
|
|
|
593
596
|
expected_errors = validate_initial_prompt_records(
|
|
594
597
|
manifest=context.manifest,
|
|
595
598
|
records=[expected_record],
|
|
599
|
+
require_evidence_ledger=True,
|
|
596
600
|
)
|
|
597
601
|
if expected_errors:
|
|
598
602
|
reason = _existing_metadata_reason(expected_errors)
|
|
@@ -765,6 +769,7 @@ def _validate_published_set(
|
|
|
765
769
|
errors = validate_initial_prompt_records(
|
|
766
770
|
manifest=context.manifest,
|
|
767
771
|
records=[record],
|
|
772
|
+
require_evidence_ledger=True,
|
|
768
773
|
)
|
|
769
774
|
if errors:
|
|
770
775
|
raise InitialPromptMaterializationError(
|
|
@@ -774,6 +779,7 @@ def _validate_published_set(
|
|
|
774
779
|
errors = validate_initial_prompt_records(
|
|
775
780
|
manifest=context.manifest,
|
|
776
781
|
records=records,
|
|
782
|
+
require_evidence_ledger=True,
|
|
777
783
|
)
|
|
778
784
|
if errors:
|
|
779
785
|
raise InitialPromptMaterializationError(
|
|
@@ -983,6 +989,7 @@ def _validate_existing_record_subset(
|
|
|
983
989
|
errors = validate_initial_prompt_records(
|
|
984
990
|
manifest=manifest,
|
|
985
991
|
records=records,
|
|
992
|
+
require_evidence_ledger=True,
|
|
986
993
|
)
|
|
987
994
|
if errors:
|
|
988
995
|
raise InitialPromptMaterializationError(
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"""Phase 7 report post-processing — the single reference point.
|
|
2
2
|
|
|
3
3
|
Phase 7 turns a Phase 6 final-report data.json into shippable artifacts through
|
|
4
|
-
|
|
5
|
-
spawning, and run validation. The order is
|
|
6
|
-
substitution ships `--` token cells, and
|
|
7
|
-
report-views contract.
|
|
4
|
+
five ordered steps: English-SSOT verification, usage substitution, html view
|
|
5
|
+
rendering, follow-up task spawning, and run validation. The order is
|
|
6
|
+
load-bearing — rendering before substitution ships `--` token cells, and
|
|
7
|
+
validating before rendering trips the report-views contract.
|
|
8
|
+
|
|
9
|
+
The translation sidecar is NOT one of these steps. `render-views` overlays it,
|
|
10
|
+
so a non-English run dispatches the translator before this sequence starts —
|
|
11
|
+
after verifying the data.json is English, which is why `check-source` is also
|
|
12
|
+
available as a standalone command.
|
|
8
13
|
|
|
9
14
|
Every lead adapter drives Phase 7 through this module: the Codex adapter calls
|
|
10
15
|
it in-process (``codex_dispatch``), and a Claude-led run reaches the same code
|
|
@@ -24,12 +29,17 @@ from .final_report_paths import final_report_data_path, final_report_markdown_pa
|
|
|
24
29
|
from .paths import task_dir, task_manifest_file
|
|
25
30
|
|
|
26
31
|
|
|
32
|
+
STEP_CHECK_SOURCE = "check-source"
|
|
27
33
|
STEP_TOKEN_USAGE = "token-usage"
|
|
28
34
|
STEP_RENDER_VIEWS = "render-views"
|
|
29
35
|
STEP_SPAWN_FOLLOWUPS = "spawn-followups"
|
|
30
36
|
STEP_VALIDATE_RUN = "validate-run"
|
|
31
37
|
|
|
32
38
|
STEP_ORDER = (
|
|
39
|
+
# First, because everything after it derives from the data.json: rendering
|
|
40
|
+
# a Korean SSOT into English chrome, spawning follow-ups from it, and
|
|
41
|
+
# validating it all succeed on a record the next phase cannot read.
|
|
42
|
+
STEP_CHECK_SOURCE,
|
|
33
43
|
STEP_TOKEN_USAGE,
|
|
34
44
|
STEP_RENDER_VIEWS,
|
|
35
45
|
STEP_SPAWN_FOLLOWUPS,
|
|
@@ -205,6 +215,19 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
|
|
|
205
215
|
"""Assemble the ordered Phase 7 argv list. Order is contractual."""
|
|
206
216
|
markdown_path = ctx.markdown_path
|
|
207
217
|
return [
|
|
218
|
+
(
|
|
219
|
+
STEP_CHECK_SOURCE,
|
|
220
|
+
[
|
|
221
|
+
sys.executable,
|
|
222
|
+
str(
|
|
223
|
+
resolve_workspace_script(
|
|
224
|
+
ctx.workspace_root, "okstra-report-translate.py"
|
|
225
|
+
)
|
|
226
|
+
),
|
|
227
|
+
"check-source",
|
|
228
|
+
str(ctx.data_path),
|
|
229
|
+
],
|
|
230
|
+
),
|
|
208
231
|
(
|
|
209
232
|
STEP_TOKEN_USAGE,
|
|
210
233
|
[
|
|
@@ -393,6 +393,35 @@ def overlay(
|
|
|
393
393
|
return out, OverlayReport(applied, untranslated, tuple(sorted(unresolved)))
|
|
394
394
|
|
|
395
395
|
|
|
396
|
+
# Above this share of Hangul, the prose was authored in Korean rather than
|
|
397
|
+
# quoting some. Measured across real reports: Korean-authored ones sit at
|
|
398
|
+
# 35-38%, English ones that quote a Korean brief or a worker's Korean phrase
|
|
399
|
+
# reach 8% at most. The gap is wide enough that no threshold inside it is
|
|
400
|
+
# delicate. Only the strings `extract` offers are counted — a verbatim quote of
|
|
401
|
+
# the user's request is structural and never reaches this.
|
|
402
|
+
HANGUL_PROSE_LIMIT = 0.20
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def hangul_share(data: Mapping[str, Any]) -> tuple[float, int]:
|
|
406
|
+
"""Return the Hangul share of the report's authored prose, and its length."""
|
|
407
|
+
text = "".join(extract(data).values())
|
|
408
|
+
if not text:
|
|
409
|
+
return 0.0, 0
|
|
410
|
+
hangul = sum(1 for char in text if "가" <= char <= "힣")
|
|
411
|
+
return hangul / len(text), len(text)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def prose_is_english(data: Mapping[str, Any]) -> bool:
|
|
415
|
+
"""Whether the data.json was authored in English, as the SSOT contract requires.
|
|
416
|
+
|
|
417
|
+
The report language names what the human HTML renders in, not what the
|
|
418
|
+
worker writes. A worker that authors Korean anyway hands every later phase,
|
|
419
|
+
validator and agent a record in a language they do not read.
|
|
420
|
+
"""
|
|
421
|
+
share, _ = hangul_share(data)
|
|
422
|
+
return share < HANGUL_PROSE_LIMIT
|
|
423
|
+
|
|
424
|
+
|
|
396
425
|
def _schema_string_keys(schema: Mapping[str, Any], *, enums: bool) -> set[str]:
|
|
397
426
|
found: set[str] = set()
|
|
398
427
|
|
|
@@ -128,6 +128,7 @@ def report_writer_input_lines(
|
|
|
128
128
|
team_state: Mapping[str, Any],
|
|
129
129
|
) -> list[str]:
|
|
130
130
|
inputs = [
|
|
131
|
+
("Analysis packet", instruction_path(manifest, active_context, "analysisPacketPath")),
|
|
131
132
|
("Task brief", instruction_path(manifest, active_context, "taskBriefPath")),
|
|
132
133
|
("Analysis profile", instruction_path(manifest, active_context, "analysisProfilePath")),
|
|
133
134
|
("Analysis material", instruction_path(manifest, active_context, "analysisMaterialPath")),
|
|
@@ -193,8 +194,9 @@ def run_path(
|
|
|
193
194
|
|
|
194
195
|
def mcp_pointer_line() -> str:
|
|
195
196
|
return (
|
|
196
|
-
'**MCP servers:** follow the
|
|
197
|
-
"section
|
|
197
|
+
'**MCP servers:** follow the analysis packet\'s "Available MCP Servers" '
|
|
198
|
+
"section. If the section is absent or says none, treat MCP as unavailable "
|
|
199
|
+
"for this run; never infer tools from host configuration."
|
|
198
200
|
)
|
|
199
201
|
|
|
200
202
|
|
|
@@ -11,6 +11,7 @@ from .worker_prompt_policy import (
|
|
|
11
11
|
PromptPlan,
|
|
12
12
|
resolve_prompt_plan_for_manifest,
|
|
13
13
|
)
|
|
14
|
+
from .worker_prompt_headers import EVIDENCE_LEDGER_HEADER
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
MAX_FINAL_VERIFICATION_DIRECTIVE_LINES = 40
|
|
@@ -24,6 +25,7 @@ FORBIDDEN_ACTIONS_HEADER = "**Forbidden actions:**"
|
|
|
24
25
|
|
|
25
26
|
_DIRECTIVE_HEADING = "## Run-specific directive"
|
|
26
27
|
_WORKER_ERROR_CONTRACT_HEADER = "**Worker Error Contract Path:**"
|
|
28
|
+
_EVIDENCE_LEDGER_HEADER_PREFIX = "**Evidence ledger:**"
|
|
27
29
|
_PRIMARY_PACKET_RE = re.compile(
|
|
28
30
|
r"(?im)^-\s+Primary analysis packet:\s+`[^`\n]*analysis-packet\.md`\s*$"
|
|
29
31
|
)
|
|
@@ -45,6 +47,7 @@ _NON_BODY_PREFIXES = (
|
|
|
45
47
|
"**Audit sidecar path:**",
|
|
46
48
|
"Assigned worker prompt history path:",
|
|
47
49
|
"**Worker Preamble Path:**",
|
|
50
|
+
_EVIDENCE_LEDGER_HEADER_PREFIX,
|
|
48
51
|
*ERRORS_PATH_HEADERS,
|
|
49
52
|
"**Read scope:**",
|
|
50
53
|
"**File write mode:**",
|
|
@@ -239,8 +242,13 @@ def validate_initial_prompt_records(
|
|
|
239
242
|
*,
|
|
240
243
|
manifest: Mapping[str, Any],
|
|
241
244
|
records: Sequence[PromptRecord],
|
|
245
|
+
require_evidence_ledger: bool = False,
|
|
242
246
|
) -> list[str]:
|
|
243
|
-
"""Validate prompt audiences and compare their normalized equality groups.
|
|
247
|
+
"""Validate prompt audiences and compare their normalized equality groups.
|
|
248
|
+
|
|
249
|
+
Newly published prompts opt into the evidence-ledger requirement. Persisted
|
|
250
|
+
historical prompts still validate under the contract they were written with.
|
|
251
|
+
"""
|
|
244
252
|
errors: list[str] = []
|
|
245
253
|
equality_groups: dict[str, dict[str, str]] = {}
|
|
246
254
|
for record in records:
|
|
@@ -262,6 +270,15 @@ def validate_initial_prompt_records(
|
|
|
262
270
|
f"{record.worker_id}: {error}"
|
|
263
271
|
for error in _validate_record_metadata(text, record)
|
|
264
272
|
)
|
|
273
|
+
errors.extend(
|
|
274
|
+
f"{record.worker_id}: {error}"
|
|
275
|
+
for error in _validate_evidence_ledger_header(
|
|
276
|
+
text,
|
|
277
|
+
plan,
|
|
278
|
+
record.dispatch_kind,
|
|
279
|
+
required=require_evidence_ledger,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
265
282
|
if plan.equality_group:
|
|
266
283
|
group = equality_groups.setdefault(plan.equality_group, {})
|
|
267
284
|
group[record.worker_id] = text
|
|
@@ -289,6 +306,29 @@ def _validate_record_metadata(text: str, record: PromptRecord) -> list[str]:
|
|
|
289
306
|
return errors
|
|
290
307
|
|
|
291
308
|
|
|
309
|
+
def _validate_evidence_ledger_header(
|
|
310
|
+
text: str,
|
|
311
|
+
plan: PromptPlan,
|
|
312
|
+
dispatch_kind: str,
|
|
313
|
+
*,
|
|
314
|
+
required: bool,
|
|
315
|
+
) -> list[str]:
|
|
316
|
+
values = _header_values(text, _EVIDENCE_LEDGER_HEADER_PREFIX)
|
|
317
|
+
if dispatch_kind != "initial":
|
|
318
|
+
return []
|
|
319
|
+
if plan.audience == "report-writer":
|
|
320
|
+
if values:
|
|
321
|
+
return ["Evidence ledger header is forbidden for report-writer"]
|
|
322
|
+
return []
|
|
323
|
+
if not values:
|
|
324
|
+
if required:
|
|
325
|
+
return [f"exactly one `{EVIDENCE_LEDGER_HEADER}` header is required"]
|
|
326
|
+
return []
|
|
327
|
+
if values != ["required-v1"]:
|
|
328
|
+
return [f"exactly one `{EVIDENCE_LEDGER_HEADER}` header is required"]
|
|
329
|
+
return []
|
|
330
|
+
|
|
331
|
+
|
|
292
332
|
def _validate_delivery_mode(
|
|
293
333
|
values: list[str],
|
|
294
334
|
expected: str | None,
|
|
@@ -36,6 +36,7 @@ READ_SCOPE_HEADER = (
|
|
|
36
36
|
"un-enumerated file seems essential, record it under *Missing Information "
|
|
37
37
|
"or Assumptions* instead of reading it."
|
|
38
38
|
)
|
|
39
|
+
EVIDENCE_LEDGER_HEADER = "**Evidence ledger:** required-v1"
|
|
39
40
|
|
|
40
41
|
# `agy`'s write tool validates the target against the Gemini artifact store
|
|
41
42
|
# whenever the model attaches ArtifactMetadata, and rejects every path outside
|
|
@@ -101,6 +102,8 @@ def worker_prompt_headers(
|
|
|
101
102
|
headers.append(
|
|
102
103
|
f"**Coding preflight pack:** {_coding_preflight_pack_path(active_context)}"
|
|
103
104
|
)
|
|
105
|
+
if dispatch_kind == "initial" and plan.audience != "report-writer":
|
|
106
|
+
headers.append(EVIDENCE_LEDGER_HEADER)
|
|
104
107
|
headers.extend([
|
|
105
108
|
f"**Errors log path:** {errors_log_path}",
|
|
106
109
|
f"**Errors sidecar path:** {errors_sidecar_path}",
|
|
@@ -25,6 +25,14 @@ Work like a senior engineer who owns this result, not a commentator on it.
|
|
|
25
25
|
- Only the executor may mutate source files. Verifiers remain read-only except for okstra result/audit artifacts and project-declared QA commands.
|
|
26
26
|
- Execute the role sidecar's pre-write, post-write, QA, and return gates without substituting analysis Sections 1–6 or report-authoring instructions.
|
|
27
27
|
|
|
28
|
+
## Evidence read ledger
|
|
29
|
+
|
|
30
|
+
When `**Evidence ledger:** required-v1` is present, append one canonical row to the audit sidecar immediately after opening every file used as claim evidence:
|
|
31
|
+
|
|
32
|
+
- Evidence read: `<project-relative path without a line suffix>`
|
|
33
|
+
|
|
34
|
+
Every file citation in the result MUST use backticks and a line suffix, for example `src/config/env.ts:1-22`. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
|
|
35
|
+
|
|
28
36
|
## Anchor headers
|
|
29
37
|
|
|
30
38
|
Every initial implementation prompt begins with these generated common anchors in this exact order, before its implementation-specific body:
|
|
@@ -36,9 +44,10 @@ Every initial implementation prompt begins with these generated common anchors i
|
|
|
36
44
|
5. `**Worker Preamble Path:** <absolute-path>`
|
|
37
45
|
6. `**Worker Error Contract Path:** <absolute-path>`
|
|
38
46
|
7. `**Coding preflight pack:** <absolute-path>`
|
|
39
|
-
8. `**
|
|
40
|
-
9. `**Errors
|
|
41
|
-
10. `**
|
|
47
|
+
8. `**Evidence ledger:** required-v1`
|
|
48
|
+
9. `**Errors log path:** <absolute-path>`
|
|
49
|
+
10. `**Errors sidecar path:** <absolute-path>`
|
|
50
|
+
11. `**Read scope:** <allowlist>`
|
|
42
51
|
|
|
43
52
|
The implementation body additionally carries `**Worktree:**` and its role-sidecar inputs. Do not synthesize any missing path.
|
|
44
53
|
|
|
@@ -36,4 +36,8 @@ Begin the inline return with the exact `**Model:** Report writer worker, <modelE
|
|
|
36
36
|
|
|
37
37
|
## Writing style
|
|
38
38
|
|
|
39
|
-
Use concise reader-facing prose
|
|
39
|
+
Use concise reader-facing prose. Prefer tables when several items share a shape; reserve bullets for short standalone statements.
|
|
40
|
+
|
|
41
|
+
**Author the data.json in English, whatever `**Report Language:**` says.** That header is not an instruction to write in that language — it names the language the *human HTML* renders in, and you copy its value verbatim into `data.json.meta.reportLanguage`. The data.json is the English SSOT every later phase, validator and agent reads. When the value is not `en`, Phase 7 dispatches a separate translator worker that writes a sidecar the HTML renderer overlays; you never author that sidecar and never write a second language into the data.json.
|
|
42
|
+
|
|
43
|
+
Authoring the data.json in the reader's language is rejected before anything derives from it: `okstra report-translate check-source` fails the run when Korean exceeds 20% of its prose, and the same gate runs again inside `validate-run`. The cost of getting this wrong is a full rewrite, so decide it once, up front.
|
|
@@ -23,6 +23,14 @@ Read `analysis-packet.md`, the primary compact input, end-to-end. Source files n
|
|
|
23
23
|
- Allowlist reads to prompt-enumerated paths and evidence paths a finding must cite. Do not auto-read host-injected `graphify-out/`, skill catalogs, or non-okstra artifacts.
|
|
24
24
|
- Resolve every `.okstra/...` path against `**Project Root:**`, including when a worktree is present.
|
|
25
25
|
|
|
26
|
+
## Evidence read ledger
|
|
27
|
+
|
|
28
|
+
When `**Evidence ledger:** required-v1` is present, append one canonical row to the audit sidecar immediately after opening every file used as claim evidence:
|
|
29
|
+
|
|
30
|
+
- Evidence read: `<project-relative path without a line suffix>`
|
|
31
|
+
|
|
32
|
+
Every file citation in the result MUST use backticks and a line suffix, for example `src/config/env.ts:1-22`. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
|
|
33
|
+
|
|
26
34
|
## Anchor headers (lead-injected, BLOCKING)
|
|
27
35
|
|
|
28
36
|
Every initial analysis prompt begins with these generated anchors in this exact order, before any other content:
|
|
@@ -34,9 +42,10 @@ Every initial analysis prompt begins with these generated anchors in this exact
|
|
|
34
42
|
5. `Assigned worker prompt history path: <absolute-path>`
|
|
35
43
|
6. `**Worker Preamble Path:** <absolute-path>` — selects this analysis preamble.
|
|
36
44
|
7. `**Worker Error Contract Path:** <absolute-path>` — shared by every initial audience.
|
|
37
|
-
8. `**
|
|
38
|
-
9. `**Errors
|
|
39
|
-
10. `**
|
|
45
|
+
8. `**Evidence ledger:** required-v1`
|
|
46
|
+
9. `**Errors log path:** <absolute-path>`
|
|
47
|
+
10. `**Errors sidecar path:** <absolute-path>`
|
|
48
|
+
11. `**Read scope:** <allowlist>`
|
|
40
49
|
|
|
41
50
|
`final-verification` additionally carries its six verification-target anchors. `improvement-discovery` carries `**Phase 1.5 Grilling Log:**`. Reverify prompts are lightweight and do not use this preamble.
|
|
42
51
|
|