okstra 0.71.2 → 0.73.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/docs/kr/architecture.md +16 -2
- package/docs/kr/cli.md +8 -2
- package/docs/kr/performance-improvement-plan-v2.md +27 -7
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +1290 -0
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +94 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +6 -1
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +9 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +17 -0
- package/runtime/python/okstra_ctl/clarification_items.py +26 -0
- package/runtime/python/okstra_ctl/context_cost.py +32 -5
- package/runtime/python/okstra_ctl/fix_cycles.py +172 -0
- package/runtime/python/okstra_ctl/render.py +28 -4
- package/runtime/python/okstra_ctl/report_views.py +63 -6
- package/runtime/python/okstra_ctl/run.py +97 -4
- package/runtime/python/okstra_ctl/run_context.py +15 -9
- package/runtime/python/okstra_ctl/wizard.py +64 -4
- package/runtime/python/okstra_token_usage/claude.py +22 -0
- package/runtime/python/okstra_token_usage/collect.py +46 -0
- package/runtime/python/okstra_token_usage/cursor.py +3 -1
- package/runtime/schemas/final-report-v1.0.schema.json +27 -1
- package/runtime/skills/okstra-brief/SKILL.md +8 -0
- package/runtime/skills/okstra-convergence/SKILL.md +8 -4
- package/runtime/skills/okstra-inspect/SKILL.md +20 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +4 -2
- package/runtime/skills/okstra-run/SKILL.md +2 -1
- package/runtime/templates/project-docs/task-index.template.md +1 -0
- package/runtime/templates/reports/final-report.template.md +17 -3
- package/runtime/templates/reports/report.js +23 -2
- package/runtime/validators/validate-run.py +41 -0
- package/src/render-bundle.mjs +4 -1
|
@@ -18,7 +18,9 @@ from pathlib import Path
|
|
|
18
18
|
|
|
19
19
|
from okstra_project.dirs import okstra_home
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# v2: usage 이벤트에 `p`(lead PROGRESS 마커) 키 추가 — v1 캐시는 이미 스캔한
|
|
22
|
+
# 구간의 마커가 없으므로 폐기하고 전체 재스캔한다(fail-open).
|
|
23
|
+
CACHE_SCHEMA_VERSION = 2
|
|
22
24
|
IDENTITY_PREFIX_BYTES = 256
|
|
23
25
|
MAX_NEEDLES = 16
|
|
24
26
|
|
|
@@ -625,6 +625,31 @@
|
|
|
625
625
|
}
|
|
626
626
|
},
|
|
627
627
|
|
|
628
|
+
"fixCycle": {
|
|
629
|
+
"type": "object",
|
|
630
|
+
"description": "RENDER_IF fixCycle present — the post-release bug-fix cycle this run belongs to.",
|
|
631
|
+
"required": ["cycle", "targetReport", "symptom"],
|
|
632
|
+
"additionalProperties": false,
|
|
633
|
+
"properties": {
|
|
634
|
+
"cycle": { "type": "string", "pattern": "^fc-[0-9]{2,}$" },
|
|
635
|
+
"targetReport": { "type": "string" },
|
|
636
|
+
"symptom": { "type": "string" },
|
|
637
|
+
"runs": {
|
|
638
|
+
"type": "array",
|
|
639
|
+
"items": {
|
|
640
|
+
"type": "object",
|
|
641
|
+
"required": ["taskType", "runSeq"],
|
|
642
|
+
"additionalProperties": false,
|
|
643
|
+
"properties": {
|
|
644
|
+
"taskType": { "type": "string" },
|
|
645
|
+
"runSeq": { "type": "integer" },
|
|
646
|
+
"runManifest": { "type": "string" }
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
|
|
628
653
|
"clarificationItems": {
|
|
629
654
|
"type": "array",
|
|
630
655
|
"items": { "$ref": "#/$defs/ClarificationRow" }
|
|
@@ -1217,9 +1242,10 @@
|
|
|
1217
1242
|
|
|
1218
1243
|
"ValidationCheckRow": {
|
|
1219
1244
|
"type": "object",
|
|
1220
|
-
"required": ["phase", "ticketId", "check", "commandOrObservation", "expectedOutcome"],
|
|
1245
|
+
"required": ["id", "phase", "ticketId", "check", "commandOrObservation", "expectedOutcome"],
|
|
1221
1246
|
"additionalProperties": false,
|
|
1222
1247
|
"properties": {
|
|
1248
|
+
"id": { "type": "string", "pattern": "^VC-\\d{3,}$" },
|
|
1223
1249
|
"phase": { "enum": ["pre", "mid", "post"] },
|
|
1224
1250
|
"ticketId": { "$ref": "#/$defs/TicketId" },
|
|
1225
1251
|
"check": { "type": "string", "minLength": 1 },
|
|
@@ -431,6 +431,9 @@ never error:
|
|
|
431
431
|
1. **okstra-internal (authoritative)** — always check first:
|
|
432
432
|
- `<PROJECT_ROOT>/.okstra/glossary.md` if present
|
|
433
433
|
- `<PROJECT_ROOT>/.okstra/decisions/` titles if present
|
|
434
|
+
- Fix history (when the brief targets an existing task):
|
|
435
|
+
`<PROJECT_ROOT>/.okstra/tasks/<task-group>/<task-id>/history/fix-cycles.jsonl`
|
|
436
|
+
— read every `opened`/`closed` row.
|
|
434
437
|
2. **Explicit source material only** — if the reporter cited a path outside
|
|
435
438
|
okstra's subtree, read it as source evidence only; do not treat it as
|
|
436
439
|
okstra memory.
|
|
@@ -602,6 +605,11 @@ Required sections:
|
|
|
602
605
|
- **Related Artifacts** — files, URLs, issues, prior task-keys.
|
|
603
606
|
- **Open Questions** — anything the user already flagged as undecided
|
|
604
607
|
(becomes raw material for `requirements-discovery`).
|
|
608
|
+
- **Task Continuity Notes** — if the target task's
|
|
609
|
+
`history/fix-cycles.jsonl` exists, cite each cycle here as one line —
|
|
610
|
+
`fix-cycle fc-NN (open|closed): <symptom> (target: <target_report>)`.
|
|
611
|
+
An open cycle means the new brief continues a bug-fix in progress; say
|
|
612
|
+
so explicitly.
|
|
605
613
|
|
|
606
614
|
Sections **deliberately omitted** (do NOT add them, do NOT prompt for them):
|
|
607
615
|
|
|
@@ -519,14 +519,18 @@ Schema rules:
|
|
|
519
519
|
Runs only when `convergence.critic.enabled == true` (set by `--critic <provider>` or the okstra-run `critic_pick` step; default off). Applies to the three finding-producing phases (`requirements-discovery`, `error-analysis`, `implementation-planning`); for `final-verification` the critic runs in a different mode — see §"Acceptance critic pass (final-verification)". This pass targets **coverage** (missed findings), distinct from convergence which targets **agreement quality**.
|
|
520
520
|
|
|
521
521
|
### When
|
|
522
|
-
|
|
522
|
+
|
|
523
|
+
The critic input is the Round 0 consolidated finding list. Reverify rounds only classify findings — they never add or remove them (in-round queue insertions are forbidden, see §"Convergence State Artifact" `carriedForwardCount`) — so the critic dispatch MUST NOT wait for classification to finish:
|
|
524
|
+
|
|
525
|
+
- **Dispatch**: immediately after Round 0 grouping, CONCURRENTLY with the first reverify round's dispatches. When the verification queue is empty after Round 0 (no reverify round runs), dispatch right after grouping. Concurrent dispatch to the same provider is safe — the critic result path (`<provider>-critic-...`) never collides with a reverify result path.
|
|
526
|
+
- **Gap verification + merge**: only after BOTH the finding-convergence loop has exited AND the critic result is collected, and BEFORE the Phase 6 report-writer dispatch. If the loop exited `aborted-non-result`, do NOT dispatch a gap-verification round — surface the gaps as unverified `clarification` items per §"Gap verification" and record that fact.
|
|
523
527
|
|
|
524
528
|
### Dispatch (reused worker)
|
|
525
529
|
Dispatch ONE pass to the `config.critic.provider`'s existing subagent (`claude-worker` / `codex-worker` / `gemini-worker`) with `model = config.critic.modelExecutionValue` — no new agent type. If `config.critic.modelExecutionValue` is null/empty (model could not be resolved), skip the critic pass and record `critic-skipped: model-unresolved` in the convergence state rather than dispatching with no model. Result path: `runs/<task-type>/worker-results/<provider>-critic-<task-type>-<seq>.md`. The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
|
|
526
530
|
|
|
527
531
|
```
|
|
528
|
-
You are the coverage critic for <task-key>. Below are the findings
|
|
529
|
-
|
|
532
|
+
You are the coverage critic for <task-key>. Below are the consolidated findings
|
|
533
|
+
the workers produced. Your ONLY job is to name what is MISSING:
|
|
530
534
|
- files / directories / execution paths nobody inspected,
|
|
531
535
|
- requirements or acceptance points with zero findings,
|
|
532
536
|
- claims raised but never verified.
|
|
@@ -543,7 +547,7 @@ Each critic gap enters the verification queue as a finding with `originWorker =
|
|
|
543
547
|
|
|
544
548
|
## Acceptance critic pass (final-verification)
|
|
545
549
|
|
|
546
|
-
The `final-verification` phase reuses the SAME reused-worker dispatch as §"Coverage critic pass" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; default off; same model-unresolved skip rule). Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
|
|
550
|
+
The `final-verification` phase reuses the SAME reused-worker dispatch AND the same dispatch timing as §"Coverage critic pass" §"When" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; default off; same model-unresolved skip rule) — the delivered work the critic inspects is likewise fixed before the reverify round starts. Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
|
|
547
551
|
|
|
548
552
|
### Prompt
|
|
549
553
|
|
|
@@ -425,6 +425,15 @@ Display rule for `workerId` vs `agent`:
|
|
|
425
425
|
|
|
426
426
|
Never write `claude (claude)` — the parenthesized agent is shown only when it adds information.
|
|
427
427
|
|
|
428
|
+
**C. Per-run phase breakdown (when `phaseTimeline` is present):**
|
|
429
|
+
|
|
430
|
+
team-state written by collector ≥ v0.73 carries a `phaseTimeline` block — the Phase 7 collector reconstructs lead operating-phase boundaries from the lead session's `PROGRESS: phase-*` checkpoint lines (`{source: "lead-progress-markers", phases: [{phase, firstAt, lastAt, markerCount, wallMsToNext}]}`).
|
|
431
|
+
|
|
432
|
+
- When the user asks for a per-phase / per-run breakdown ("단계별", "phase별", "어느 단계가 오래"), render one table per requested run: `| Phase | Start | Wall to next |` using `firstAt` and `wallMsToNext`.
|
|
433
|
+
- `wallMsToNext` is `null` on the last phase (the run-end signal is not a marker) — render `--`.
|
|
434
|
+
- `phases: []` means the lead emitted no markers (run predates the contract, or markers were skipped) — say "phase timeline 측정 불가(마커 없음)" rather than estimating from other fields.
|
|
435
|
+
- Older team-state files have no `phaseTimeline` key at all — omit the section silently.
|
|
436
|
+
|
|
428
437
|
**Timestamp parsing:** for `startedAt` / `endedAt`, normalize ISO-8601: replace trailing `Z` with `+00:00`, accept explicit offsets as-is, parse via `datetime.fromisoformat(s.replace("Z", "+00:00"))`. Strings without an offset are assumed UTC. Mixed-form comparisons must be done as `datetime` objects, never raw strings.
|
|
429
438
|
|
|
430
439
|
### time.4 — Format output
|
|
@@ -458,6 +467,17 @@ Never write `claude (claude)` — the parenthesized agent is shown only when it
|
|
|
458
467
|
| report-writer | 2 | 00:01:00 | 00:00:30 |
|
|
459
468
|
|
|
460
469
|
> Unavailable: 1 run (implementation / 2026-04-30_03-03-48) — team-state has no durationMs (Phase 7 not reached)
|
|
470
|
+
|
|
471
|
+
### Phase breakdown — requirements-discovery (run 002)
|
|
472
|
+
|
|
473
|
+
| Phase | Start (UTC) | Wall to next |
|
|
474
|
+
|------------------------|------------------|--------------|
|
|
475
|
+
| phase-1-intake | 2026-06-11 09:00 | 00:04:00 |
|
|
476
|
+
| phase-2-prompts | 2026-06-11 09:04 | 00:02:10 |
|
|
477
|
+
| phase-4-dispatch | 2026-06-11 09:06 | 00:18:30 |
|
|
478
|
+
| phase-5.5-convergence | 2026-06-11 09:25 | 00:21:00 |
|
|
479
|
+
| phase-6-synthesis | 2026-06-11 09:46 | 00:15:00 |
|
|
480
|
+
| phase-7-persist | 2026-06-11 10:01 | -- |
|
|
461
481
|
```
|
|
462
482
|
|
|
463
483
|
**Rules:**
|
|
@@ -105,7 +105,7 @@ The four steps below MUST execute in this exact order. Reordering them is the re
|
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
Output (idempotent — re-running overwrites):
|
|
108
|
-
- `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view, **generated only when the report has at least one §5 `C-*` clarification row**. Those rows with `Status` ∈ {`open`, `answered`} embed form widgets (`<select>` for enum-style decisions, `<input>` for material / data-point kinds, `<textarea>` fallback); an `Export user response` button serialises form values to a markdown sidecar (schema in [`templates/reports/user-response.template.md`](../../templates/reports/user-response.template.md))
|
|
108
|
+
- `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view, **generated only when the report has at least one §5 `C-*` clarification row**. Those rows with `Status` ∈ {`open`, `answered`} embed form widgets (`<select>` for enum-style decisions, `<input>` for material / data-point kinds, `<textarea>` fallback); an `Export user response` button serialises form values to a markdown sidecar (schema in [`templates/reports/user-response.template.md`](../../templates/reports/user-response.template.md)) and downloads it as `user-response-<task-type>-<seq>.md`; the user saves it to `runs/<task-type>/user-responses/`, and `--resume-clarification` auto-appends every sidecar found there to the next run's `clarification-response.md` (`clarification_items.clarification_response_with_sidecars`). The original final-report MD is **never** mutated by user input — the sidecar is the single write target.
|
|
109
109
|
- When the report has **no** `C-*` clarification rows, the html carries no interactive forms (it would only duplicate the MD), so the renderer prints `html: skipped (...)` and writes nothing. This is the expected state for clarification-free runs — `validators/validate-report-views.py` treats "no C-* rows + no html" as a pass, not a missing artifact.
|
|
110
110
|
|
|
111
111
|
Must run AFTER step 1 (so token placeholders are substituted in any rendered html) and BEFORE step 2 (so the html artifact, when generated, exists for the validator step that checks it).
|
|
@@ -276,9 +276,11 @@ Section numbering follows `templates/reports/final-report.template.md` exactly
|
|
|
276
276
|
3. **Evidence and Detailed Analysis** — primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
|
|
277
277
|
4. **Missing Information and Risks** — uncertain / "I don't know" items. `implementation-planning` adds §5.5 (see heading contract below); `release-handoff` adds §5.6.
|
|
278
278
|
5. **Clarification Items** — single unified `C-*` table; column schema (4 columns with the short fields stacked in one record-meta cell), ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (SSOT). The deprecated `5.5.9 Open Questions` / `1.1 추가 자료 요청` / `1.2 사용자 확인 질문` sub-sections are removed; the validator fails reports that reintroduce them.
|
|
279
|
-
6. **Recommended Next Steps** — prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 → Phase 7 execution sequence" above).
|
|
279
|
+
6. **Recommended Next Steps** — prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 → Phase 7 execution sequence" above). **Approval-gate consistency:** when §1 carries any `Blocks: approval` row with `Status` ∈ {open, answered}, the Verdict Card `Next Step` and the first recommended step MUST point to the clarification rerun (`resume-clarification` of the SAME task-type) — never to "frontmatter `approved: true` 플립 → `implementation` 직행". Run-prep enforces this gate (`run.py _validate_approved_plan` fail-closes on those rows and on a blocking data.json `gateResult`), so a direct-implementation next-step is an instruction the reader cannot actually follow.
|
|
280
280
|
7. **Follow-up Tasks** — auto-spawn-eligible table. Each row drives `okstra-spawn-followups.py`; see template §7 for the row schema.
|
|
281
281
|
|
|
282
|
+
**§5.10 Fix History (data-presence gated).** When the run-manifest carries a `fixCycleId`, fill the data.json `fixCycle` block (`cycle` / `targetReport` / `symptom` / `runs`). Read the values from the task root's `history/fix-cycles.jsonl`: `cycle` MUST equal `fixCycleId`, `targetReport` / `symptom` come from that cycle's `opened` row, and `runs` lists its attached `run` rows (`taskType` / `runSeq` / `runManifest`). The validator (`validators/validate-run.py` → `_validate_fix_cycle`) fails the run when the block is missing or `fixCycle.cycle` does not match `fixCycleId`. When the run-manifest has no `fixCycleId`, OMIT the `fixCycle` block entirely — the renderer omits §5.10.
|
|
283
|
+
|
|
282
284
|
### Writing Guidelines
|
|
283
285
|
|
|
284
286
|
- Write in Markdown. **Prefer tables over prose bullet lists** for any section that enumerates multiple items with the same shape (evidence rows, risks, options, dependencies, rollback steps, follow-ups, open questions). Bullets are reserved for short, single-line standalone statements (e.g., "- 추가 정보 요청 없음."). When the template provides a table form, do NOT degrade it back to bullets in the rendered report.
|
|
@@ -178,7 +178,8 @@ okstra render-bundle \
|
|
|
178
178
|
--report-writer-model "<args.report-writer-model>" \
|
|
179
179
|
--related-tasks "<args.related-tasks>" \
|
|
180
180
|
--clarification-response "<args.clarification-response>" \
|
|
181
|
-
--pr-template-path "<args.pr-template-path>"
|
|
181
|
+
--pr-template-path "<args.pr-template-path>" \
|
|
182
|
+
--fix-cycle "<args.fix-cycle>"
|
|
182
183
|
```
|
|
183
184
|
|
|
184
185
|
`render-bundle` auto-supplies `--workspace-root` and forces `--render-only`. Stdout prints `okstra task root:`, `okstra instruction-set:`, and the full rendered lead prompt. Parse the labelled lines for `TASK_ROOT` and `INSTRUCTION_SET_PATH`. Also watch for an optional `okstra concurrent-run stages:` label line — present only when a concurrent run is detected (see "동시-run 감지 분기" below).
|
|
@@ -37,6 +37,7 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
37
37
|
- Next Recommended Phase: `{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}`
|
|
38
38
|
- Awaiting Approval: `{{WORKFLOW_AWAITING_APPROVAL}}`
|
|
39
39
|
- Routing Status: `{{WORKFLOW_ROUTING_STATUS}}`
|
|
40
|
+
- Fix cycles: {{FIX_CYCLES_SUMMARY}}
|
|
40
41
|
|
|
41
42
|
## Phase States
|
|
42
43
|
|
|
@@ -202,10 +202,10 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
|
|
|
202
202
|
|
|
203
203
|
### Validation Checklist{% if t("sectionAside.validationChecklist") != "Validation Checklist" %} ({{ t("sectionAside.validationChecklist") }}){% endif %}
|
|
204
204
|
|
|
205
|
-
| Phase | Ticket ID | Check | Command / Observation | Expected outcome |
|
|
206
|
-
|
|
205
|
+
| ID | Phase | Ticket ID | Check | Command / Observation | Expected outcome |
|
|
206
|
+
|----|-------|-----------|-------|------------------------|-------------------|
|
|
207
207
|
{% for row in implementationPlanning.validationChecklist -%}
|
|
208
|
-
| {{ row.phase }} | `{{ row.ticketId }}` | {{ row.check }} | `{{ row.commandOrObservation }}` | {{ row.expectedOutcome }} |
|
|
208
|
+
| {{ row.id }} | {{ row.phase }} | `{{ row.ticketId }}` | {{ row.check }} | `{{ row.commandOrObservation }}` | {{ row.expectedOutcome }} |
|
|
209
209
|
{% endfor %}
|
|
210
210
|
|
|
211
211
|
### Rollback Strategy{% if t("sectionAside.rollbackStrategy") != "Rollback Strategy" %} ({{ t("sectionAside.rollbackStrategy") }}){% endif %}
|
|
@@ -556,6 +556,20 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
|
|
|
556
556
|
| Cand ID | Lens | Title | Scope | Severity | Effort | Consensus | Source workers | Recommended next-phase | Evidence |
|
|
557
557
|
|---------|------|-------|-------|----------|--------|-----------|----------------|------------------------|----------|
|
|
558
558
|
|
|
559
|
+
{% endif %}
|
|
560
|
+
{% if fixCycle and fixCycle.cycle %}
|
|
561
|
+
## 5.10 Fix History
|
|
562
|
+
|
|
563
|
+
> This run belongs to a post-release bug-fix cycle registered in `history/fix-cycles.jsonl`.
|
|
564
|
+
|
|
565
|
+
- Cycle: `{{ fixCycle.cycle }}` — {{ fixCycle.symptom }}
|
|
566
|
+
- Target report: `{{ fixCycle.targetReport }}`
|
|
567
|
+
{% if fixCycle.runs %}
|
|
568
|
+
- Runs in this cycle so far:
|
|
569
|
+
{% for r in fixCycle.runs %}
|
|
570
|
+
- {{ r.taskType }} seq {{ r.runSeq }}{% if r.runManifest %} (`{{ r.runManifest }}`){% endif %}
|
|
571
|
+
{% endfor %}
|
|
572
|
+
{% endif %}
|
|
559
573
|
{% endif %}
|
|
560
574
|
## 6. Cross Verification Results
|
|
561
575
|
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
* (everything else / fallback).
|
|
10
10
|
* 2. Serialise the entries into markdown whose bytes are IDENTICAL
|
|
11
11
|
* to scripts/okstra_ctl/report_views.py serialize_user_response.
|
|
12
|
-
* 3. Write the result to <pre id="user-response-output"
|
|
13
|
-
* [Copy] button
|
|
12
|
+
* 3. Write the result to <pre id="user-response-output">, offer a
|
|
13
|
+
* [Copy] button, and download it as
|
|
14
|
+
* user-response-<task-type>-<seq>.md — the user drops that file in
|
|
15
|
+
* runs/<task-type>/user-responses/ and --resume-clarification
|
|
16
|
+
* auto-appends it to the next run's clarification-response.md.
|
|
14
17
|
*
|
|
15
18
|
* The byte-identity contract is enforced by tests/test_report_views.py
|
|
16
19
|
* which spawns Node to execute buildUserResponseMarkdown and diffs the
|
|
@@ -143,12 +146,30 @@
|
|
|
143
146
|
return head + chunks;
|
|
144
147
|
}
|
|
145
148
|
|
|
149
|
+
function downloadUserResponse(md, runMeta) {
|
|
150
|
+
var name =
|
|
151
|
+
"user-response-" + (runMeta["task-type"] || "report") + "-" +
|
|
152
|
+
(runMeta["seq"] || "000") + ".md";
|
|
153
|
+
var blob = new Blob([md], { type: "text/markdown" });
|
|
154
|
+
var url = URL.createObjectURL(blob);
|
|
155
|
+
var a = document.createElement("a");
|
|
156
|
+
a.href = url;
|
|
157
|
+
a.download = name;
|
|
158
|
+
document.body.appendChild(a);
|
|
159
|
+
a.click();
|
|
160
|
+
document.body.removeChild(a);
|
|
161
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 0);
|
|
162
|
+
}
|
|
163
|
+
|
|
146
164
|
function exportUserResponse() {
|
|
147
165
|
var runMeta = readRunMeta();
|
|
148
166
|
var entries = collectEntries();
|
|
149
167
|
var md = buildUserResponseMarkdown(runMeta, entries, isoNowUtc());
|
|
150
168
|
var out = document.getElementById("user-response-output");
|
|
151
169
|
if (out) out.textContent = md;
|
|
170
|
+
// Nothing answered yet — show the empty serialisation as feedback but
|
|
171
|
+
// don't download a useless file.
|
|
172
|
+
if (entries.length > 0) downloadUserResponse(md, runMeta);
|
|
152
173
|
return md;
|
|
153
174
|
}
|
|
154
175
|
|
|
@@ -1369,6 +1369,19 @@ def _data_path_for(report_path: Path) -> Path:
|
|
|
1369
1369
|
return report_path.with_suffix(".data.json")
|
|
1370
1370
|
|
|
1371
1371
|
|
|
1372
|
+
def _load_final_report_data(report_path: Path) -> dict:
|
|
1373
|
+
"""Best-effort parse of the final-report data.json sibling. Returns {} when
|
|
1374
|
+
absent or unparseable — those conditions are already surfaced as failures by
|
|
1375
|
+
validate_final_report_data; this loader only feeds cross-field checks."""
|
|
1376
|
+
data_path = _data_path_for(report_path)
|
|
1377
|
+
if not data_path.is_file():
|
|
1378
|
+
return {}
|
|
1379
|
+
try:
|
|
1380
|
+
return json.loads(data_path.read_text(encoding="utf-8"))
|
|
1381
|
+
except (OSError, json.JSONDecodeError):
|
|
1382
|
+
return {}
|
|
1383
|
+
|
|
1384
|
+
|
|
1372
1385
|
def validate_final_report_data(report_path: Path, failures: list[str]) -> None:
|
|
1373
1386
|
"""Validate the final-report data.json against the v1.0 schema.
|
|
1374
1387
|
|
|
@@ -1748,6 +1761,31 @@ def _validate_improvement_discovery(
|
|
|
1748
1761
|
failures.append(f"improvement-discovery: {err}")
|
|
1749
1762
|
|
|
1750
1763
|
|
|
1764
|
+
def _validate_fix_cycle(run_manifest: dict, data: dict, failures: list[str]) -> None:
|
|
1765
|
+
"""Enforce: when the run-manifest carries a fixCycleId, the final-report
|
|
1766
|
+
data.json MUST contain a fixCycle block whose ``cycle`` matches it.
|
|
1767
|
+
|
|
1768
|
+
Direction is one-way: a fixCycle block present without a run-manifest
|
|
1769
|
+
fixCycleId is NOT rejected — same posture as the schema-optional block.
|
|
1770
|
+
This check owns only the run→report direction (no missing/mismatched
|
|
1771
|
+
block when the run is attached), never the reverse."""
|
|
1772
|
+
cycle_id = (run_manifest or {}).get("fixCycleId", "")
|
|
1773
|
+
if not cycle_id:
|
|
1774
|
+
return
|
|
1775
|
+
block = (data or {}).get("fixCycle")
|
|
1776
|
+
if not isinstance(block, dict):
|
|
1777
|
+
failures.append(
|
|
1778
|
+
f"fix-cycle: run-manifest fixCycleId={cycle_id} but data.json has "
|
|
1779
|
+
"no fixCycle block"
|
|
1780
|
+
)
|
|
1781
|
+
return
|
|
1782
|
+
if block.get("cycle") != cycle_id:
|
|
1783
|
+
failures.append(
|
|
1784
|
+
f"fix-cycle: data.json fixCycle.cycle={block.get('cycle')!r} does "
|
|
1785
|
+
f"not match run-manifest fixCycleId={cycle_id!r}"
|
|
1786
|
+
)
|
|
1787
|
+
|
|
1788
|
+
|
|
1751
1789
|
def _validate_session_conformance(
|
|
1752
1790
|
team_state: dict,
|
|
1753
1791
|
team_state_path: Path,
|
|
@@ -2157,6 +2195,9 @@ def main() -> int:
|
|
|
2157
2195
|
# safety net for hand-edited or pre-v1.0 reports.
|
|
2158
2196
|
task_type = effective_run_task_type(run_manifest, task_manifest)
|
|
2159
2197
|
validate_final_report_data(report_path, failures)
|
|
2198
|
+
_validate_fix_cycle(
|
|
2199
|
+
run_manifest, _load_final_report_data(report_path), failures
|
|
2200
|
+
)
|
|
2160
2201
|
validate_report(report_path, contract["required_agent_status_entries"], failures)
|
|
2161
2202
|
validate_team_state_usage(team_state, failures)
|
|
2162
2203
|
|
package/src/render-bundle.mjs
CHANGED
|
@@ -18,11 +18,14 @@ Usage:
|
|
|
18
18
|
[--gemini-model <m>] [--report-writer-model <m>] \\
|
|
19
19
|
[--related-tasks <list>] [--base-ref <ref>] \\
|
|
20
20
|
[--clarification-response <path>] [--work-category <cat>] \\
|
|
21
|
-
[--stage <auto|N>] [--stages <csv>] [--pr-template-path <path>]
|
|
21
|
+
[--stage <auto|N>] [--stages <csv>] [--pr-template-path <path>] \\
|
|
22
|
+
[--fix-cycle <yes|no>]
|
|
22
23
|
|
|
23
24
|
--stage implementation / final-verification only
|
|
24
25
|
--stages release-handoff only: PR stage bundle csv (empty = whole-task)
|
|
25
26
|
--pr-template-path release-handoff only
|
|
27
|
+
--fix-cycle entry phase only: record this re-entry of a done task as a
|
|
28
|
+
bug-fix cycle (yes opens/continues the cycle)
|
|
26
29
|
|
|
27
30
|
release-handoff takes NO --task-brief (briefs belong to entry phases) —
|
|
28
31
|
prepare generates the run's input document from the cited verification
|