okstra 0.171.0 → 0.173.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 +8 -6
- package/docs/architecture/storage-model.md +11 -0
- package/docs/architecture.md +29 -14
- package/docs/cli.md +40 -7
- package/docs/for-ai/skills/okstra-user-response.md +2 -2
- package/docs/performance-improvement-plan-v2.md +6 -5
- package/docs/project-structure-overview.md +24 -14
- package/docs/task-process/README.md +5 -3
- package/docs/task-process/error-analysis.md +2 -2
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation-option-selection.md +70 -0
- package/docs/task-process/implementation-planning.md +23 -15
- package/docs/task-process/requirements-discovery.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +30 -6
- 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 +3 -0
- package/runtime/bin/okstra.sh +2 -0
- package/runtime/prompts/duties/direction-selection-worker.md +44 -0
- package/runtime/prompts/duties/planning-worker.md +12 -4
- package/runtime/prompts/launch.template.md +4 -0
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +5 -5
- package/runtime/prompts/lead/okstra-lead-contract.md +42 -17
- package/runtime/prompts/lead/plan-body-verification.md +42 -14
- package/runtime/prompts/lead/report-writer.md +38 -15
- package/runtime/prompts/lead/team-contract.md +2 -0
- package/runtime/prompts/profiles/_clarification-recommendation.md +3 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -2
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/error-analysis.md +3 -3
- package/runtime/prompts/profiles/final-verification.md +3 -3
- package/runtime/prompts/profiles/forbidden-actions.json +7 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +35 -0
- package/runtime/prompts/profiles/implementation-planning.md +56 -37
- package/runtime/prompts/profiles/implementation.md +2 -1
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +1 -1
- package/runtime/python/okstra_ctl/agent_activity.py +306 -0
- package/runtime/python/okstra_ctl/agent_invocation.py +1 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +6 -0
- package/runtime/python/okstra_ctl/clarification_items.py +37 -20
- package/runtime/python/okstra_ctl/exact_coverage.py +128 -0
- package/runtime/python/okstra_ctl/fix_cycles.py +3 -1
- package/runtime/python/okstra_ctl/implementation_direction.py +836 -0
- package/runtime/python/okstra_ctl/implementation_options.py +479 -0
- package/runtime/python/okstra_ctl/lead_events.py +47 -4
- package/runtime/python/okstra_ctl/plan_items.py +51 -3
- package/runtime/python/okstra_ctl/render.py +12 -3
- package/runtime/python/okstra_ctl/render_final_report.py +1 -0
- package/runtime/python/okstra_ctl/report_contract.py +45 -13
- package/runtime/python/okstra_ctl/report_finalize.py +51 -14
- package/runtime/python/okstra_ctl/report_html/common.py +5 -3
- package/runtime/python/okstra_ctl/report_html/render.py +4 -2
- package/runtime/python/okstra_ctl/report_html/router.py +4 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_option_selection.py +32 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +42 -11
- package/runtime/python/okstra_ctl/report_translation.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +148 -12
- package/runtime/python/okstra_ctl/run.py +350 -2
- package/runtime/python/okstra_ctl/scope_provenance.py +15 -9
- package/runtime/python/okstra_ctl/user_response.py +75 -0
- package/runtime/python/okstra_ctl/wizard.py +144 -0
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +150 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
- package/runtime/python/okstra_ctl/workflow.py +29 -7
- package/runtime/schemas/final-report-v2.0.schema.json +1623 -143
- package/runtime/skills/okstra-user-response/SKILL.md +2 -2
- package/runtime/templates/reports/final-report-v2.template.md +12 -0
- package/runtime/templates/reports/final-verification-input.template.md +1 -1
- package/runtime/templates/reports/html/assets/base.css +7 -0
- package/runtime/templates/reports/html/base.template.html +3 -2
- package/runtime/templates/reports/html/i18n/en.json +27 -2
- package/runtime/templates/reports/html/i18n/ko.json +27 -2
- package/runtime/templates/reports/html/macros/forms.html +42 -4
- package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +49 -0
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +61 -2
- package/runtime/templates/reports/i18n/en.json +17 -0
- package/runtime/templates/reports/implementation-input.template.md +4 -2
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -4
- package/runtime/templates/reports/improvement-discovery-input.template.md +1 -1
- package/runtime/templates/reports/md/tasks/implementation-option-selection.template.md +13 -0
- package/runtime/templates/reports/md/tasks/implementation-planning.template.md +17 -0
- package/runtime/templates/reports/report.js +137 -21
- package/runtime/templates/reports/task-brief.template.md +9 -3
- package/runtime/templates/reports/user-response.template.md +28 -5
- package/runtime/templates/worker-prompt-preamble.md +16 -0
- package/runtime/validators/validate-implementation-plan-stages.py +106 -1
- package/runtime/validators/validate-report-views.py +2 -2
- package/runtime/validators/validate-run.py +1124 -54
- package/runtime/validators/validate_improvement_report.py +5 -1
- package/runtime/validators/validate_session_conformance.py +523 -35
- package/src/cli-registry.mjs +7 -0
- package/src/commands/execute/codex-run.mjs +1 -0
- package/src/commands/execute/render-bundle.mjs +1 -0
- package/src/commands/report/agent-activity.mjs +21 -0
|
@@ -9,6 +9,7 @@ PUBLIC_REPORT_TASK_TYPES = (
|
|
|
9
9
|
"requirements-discovery",
|
|
10
10
|
"improvement-discovery",
|
|
11
11
|
"error-analysis",
|
|
12
|
+
"implementation-option-selection",
|
|
12
13
|
"project-analysis",
|
|
13
14
|
"feature-analysis",
|
|
14
15
|
"change-impact-analysis",
|
|
@@ -22,6 +23,7 @@ TASK_TYPE_DATA_PROPERTY = {
|
|
|
22
23
|
"requirements-discovery": "requirementsDiscovery",
|
|
23
24
|
"improvement-discovery": "improvementDiscovery",
|
|
24
25
|
"error-analysis": "errorAnalysis",
|
|
26
|
+
"implementation-option-selection": "implementationOptionSelection",
|
|
25
27
|
"project-analysis": "projectAnalysis",
|
|
26
28
|
"feature-analysis": "featureAnalysis",
|
|
27
29
|
"change-impact-analysis": "changeImpactAnalysis",
|
|
@@ -63,6 +65,12 @@ TASK_TYPE_REQUIRED_HUMAN_FIELDS = {
|
|
|
63
65
|
"errorAnalysis.causeCandidates.falsifyingEvidenceChecked",
|
|
64
66
|
"errorAnalysis.causeCandidates.disproveWith",
|
|
65
67
|
),
|
|
68
|
+
"implementation-option-selection": (
|
|
69
|
+
"implementationOptionSelection.decisionContext.originalRequirementIds",
|
|
70
|
+
"implementationOptionSelection.rankedOptions.requirementCoverage",
|
|
71
|
+
"implementationOptionSelection.rankedOptions.coverageSummary",
|
|
72
|
+
"implementationOptionSelection.recommendedOptionId",
|
|
73
|
+
),
|
|
66
74
|
"project-analysis": (
|
|
67
75
|
"projectAnalysis.components",
|
|
68
76
|
"projectAnalysis.entryPoints",
|
|
@@ -89,20 +97,12 @@ TASK_TYPE_REQUIRED_HUMAN_FIELDS = {
|
|
|
89
97
|
"changeImpactAnalysis.securityAndPerformanceImpact",
|
|
90
98
|
"changeImpactAnalysis.planningInputs",
|
|
91
99
|
),
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# requirement-coverage section, and both still render in the markdown report
|
|
97
|
-
# (`final-report.template.md` §"Validation Checklist" / §"Rollback
|
|
98
|
-
# Strategy"). When that split landed the template dropped the sections and
|
|
99
|
-
# this list kept demanding them, so `validate-report-views.py` failed every
|
|
100
|
-
# implementation-planning report with nothing a report could do about it.
|
|
100
|
+
# Legacy plans render option comparison fields; selected-direction plans
|
|
101
|
+
# render the selected reference, its realization, and exact coverage. The
|
|
102
|
+
# task-level marker is the only field common to both HTML branches. Each
|
|
103
|
+
# branch's required details are covered by its schema and renderer tests.
|
|
101
104
|
"implementation-planning": (
|
|
102
|
-
"implementationPlanning
|
|
103
|
-
"implementationPlanning.tradeoffMatrix",
|
|
104
|
-
"implementationPlanning.recommendedOption",
|
|
105
|
-
"implementationPlanning.stageMap",
|
|
105
|
+
"implementationPlanning",
|
|
106
106
|
),
|
|
107
107
|
"implementation": (
|
|
108
108
|
"implementation.diffSummary",
|
|
@@ -124,6 +124,38 @@ TASK_TYPE_REQUIRED_HUMAN_FIELDS = {
|
|
|
124
124
|
),
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
IMPLEMENTATION_PLANNING_LEGACY_REQUIRED_HUMAN_FIELDS = (
|
|
128
|
+
"implementationPlanning.optionCandidates",
|
|
129
|
+
"implementationPlanning.tradeoffMatrix",
|
|
130
|
+
"implementationPlanning.recommendedOption",
|
|
131
|
+
"implementationPlanning.stageMap",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
IMPLEMENTATION_PLANNING_PLAN_READY_REQUIRED_HUMAN_FIELDS = (
|
|
135
|
+
"implementationPlanning.selectedDirectionRef",
|
|
136
|
+
"implementationPlanning.directionRealization",
|
|
137
|
+
"implementationPlanning.coverageSummary",
|
|
138
|
+
"implementationPlanning.stageMap",
|
|
139
|
+
"implementationPlanning.requirementCoverage",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
IMPLEMENTATION_PLANNING_INVALIDATED_REQUIRED_HUMAN_FIELDS = (
|
|
143
|
+
"implementationPlanning.selectedDirectionRef",
|
|
144
|
+
"implementationPlanning.directionInvalidation",
|
|
145
|
+
"implementationPlanning.routing",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def required_human_fields_for_report(task_type: str, data: dict) -> tuple[str, ...]:
|
|
150
|
+
if task_type != "implementation-planning":
|
|
151
|
+
return TASK_TYPE_REQUIRED_HUMAN_FIELDS.get(task_type, ())
|
|
152
|
+
planning = data.get("implementationPlanning", {})
|
|
153
|
+
if planning.get("planningContract") != "selected-direction":
|
|
154
|
+
return IMPLEMENTATION_PLANNING_LEGACY_REQUIRED_HUMAN_FIELDS
|
|
155
|
+
if planning.get("outcome") == "direction-invalidated":
|
|
156
|
+
return IMPLEMENTATION_PLANNING_INVALIDATED_REQUIRED_HUMAN_FIELDS
|
|
157
|
+
return IMPLEMENTATION_PLANNING_PLAN_READY_REQUIRED_HUMAN_FIELDS
|
|
158
|
+
|
|
127
159
|
|
|
128
160
|
def report_property_for(task_type: str) -> str:
|
|
129
161
|
try:
|
|
@@ -1,10 +1,10 @@
|
|
|
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
|
-
rendering, follow-up task spawning, and run validation.
|
|
6
|
-
load-bearing — rendering before substitution ships `--` token
|
|
7
|
-
validating before rendering trips the report-views contract.
|
|
4
|
+
six ordered steps: activity projection, English-SSOT verification, usage
|
|
5
|
+
substitution, html view rendering, follow-up task spawning, and run validation.
|
|
6
|
+
The order is load-bearing — rendering before substitution ships `--` token
|
|
7
|
+
cells, and validating before rendering trips the report-views contract.
|
|
8
8
|
|
|
9
9
|
The translation sidecar is NOT one of these steps. `render-views` overlays it,
|
|
10
10
|
so a non-English run dispatches the translator before this sequence starts —
|
|
@@ -25,11 +25,13 @@ from dataclasses import dataclass
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Any, Callable, Mapping, Sequence
|
|
27
27
|
|
|
28
|
+
from .agent_activity import ActivityProjectionError, project_agent_activity
|
|
29
|
+
from .dispatch_state import DispatchError, link_agent_dispatch_result
|
|
28
30
|
from .final_report_paths import final_report_data_path, final_report_markdown_path
|
|
29
31
|
from .paths import task_dir, task_manifest_file
|
|
30
|
-
from .dispatch_state import DispatchError, link_agent_dispatch_result
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
STEP_PROJECT_ACTIVITY = "project-activity"
|
|
33
35
|
STEP_CHECK_SOURCE = "check-source"
|
|
34
36
|
STEP_TOKEN_USAGE = "token-usage"
|
|
35
37
|
STEP_RENDER_VIEWS = "render-views"
|
|
@@ -37,6 +39,7 @@ STEP_SPAWN_FOLLOWUPS = "spawn-followups"
|
|
|
37
39
|
STEP_VALIDATE_RUN = "validate-run"
|
|
38
40
|
|
|
39
41
|
STEP_ORDER = (
|
|
42
|
+
STEP_PROJECT_ACTIVITY,
|
|
40
43
|
# First, because everything after it derives from the data.json: rendering
|
|
41
44
|
# a Korean SSOT into English chrome, spawning follow-ups from it, and
|
|
42
45
|
# validating it all succeed on a record the next phase cannot read.
|
|
@@ -235,6 +238,16 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
|
|
|
235
238
|
"""Assemble the ordered Phase 7 argv list. Order is contractual."""
|
|
236
239
|
markdown_path = ctx.markdown_path
|
|
237
240
|
return [
|
|
241
|
+
(
|
|
242
|
+
STEP_PROJECT_ACTIVITY,
|
|
243
|
+
[
|
|
244
|
+
"<in-process>",
|
|
245
|
+
"agent-activity",
|
|
246
|
+
"project",
|
|
247
|
+
str(ctx.manifest_path),
|
|
248
|
+
str(ctx.data_path),
|
|
249
|
+
],
|
|
250
|
+
),
|
|
238
251
|
(
|
|
239
252
|
STEP_CHECK_SOURCE,
|
|
240
253
|
[
|
|
@@ -349,8 +362,8 @@ def run_finalize(
|
|
|
349
362
|
|
|
350
363
|
``only`` restricts the run to the named steps, preserving contractual
|
|
351
364
|
order. The last step (`validate-run`) is the one that usually fails, and
|
|
352
|
-
re-running the whole sequence to retry it repeats
|
|
353
|
-
at full token and wall-clock cost.
|
|
365
|
+
re-running the whole sequence to retry it repeats every preceding idempotent
|
|
366
|
+
step at full token and wall-clock cost.
|
|
354
367
|
"""
|
|
355
368
|
steps: list[dict[str, Any]] = []
|
|
356
369
|
try:
|
|
@@ -372,6 +385,29 @@ def run_finalize(
|
|
|
372
385
|
for name, command in commands:
|
|
373
386
|
if before_step is not None:
|
|
374
387
|
before_step(name)
|
|
388
|
+
if name == STEP_PROJECT_ACTIVITY:
|
|
389
|
+
try:
|
|
390
|
+
rows = project_agent_activity(
|
|
391
|
+
ctx.project_root,
|
|
392
|
+
ctx.manifest_path,
|
|
393
|
+
ctx.data_path,
|
|
394
|
+
)
|
|
395
|
+
except ActivityProjectionError as exc:
|
|
396
|
+
result = subprocess.CompletedProcess(
|
|
397
|
+
command,
|
|
398
|
+
1,
|
|
399
|
+
"",
|
|
400
|
+
str(exc),
|
|
401
|
+
)
|
|
402
|
+
else:
|
|
403
|
+
result = subprocess.CompletedProcess(
|
|
404
|
+
command,
|
|
405
|
+
0,
|
|
406
|
+
json.dumps({"count": len(rows)}),
|
|
407
|
+
"",
|
|
408
|
+
)
|
|
409
|
+
else:
|
|
410
|
+
result = None
|
|
375
411
|
if name == STEP_VALIDATE_RUN:
|
|
376
412
|
try:
|
|
377
413
|
_link_lead_result_for_validation(ctx)
|
|
@@ -381,12 +417,13 @@ def run_finalize(
|
|
|
381
417
|
"reason": f"lead result linkage failed: {exc}",
|
|
382
418
|
"steps": steps,
|
|
383
419
|
}
|
|
384
|
-
result
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
420
|
+
if result is None:
|
|
421
|
+
result = subprocess.run(
|
|
422
|
+
command,
|
|
423
|
+
cwd=ctx.project_root,
|
|
424
|
+
text=True,
|
|
425
|
+
capture_output=True,
|
|
426
|
+
)
|
|
390
427
|
steps.append(step_payload(name, command, result))
|
|
391
428
|
if result.returncode != 0:
|
|
392
429
|
return {
|
|
@@ -489,7 +526,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
489
526
|
help=(
|
|
490
527
|
"run only these steps (repeatable, contractual order preserved). "
|
|
491
528
|
"Use `--only validate-run` to retry the step that usually fails "
|
|
492
|
-
"without repeating the
|
|
529
|
+
"without repeating the preceding idempotent steps."
|
|
493
530
|
),
|
|
494
531
|
)
|
|
495
532
|
return parser
|
|
@@ -85,6 +85,7 @@ def _own_section_ids(data: dict, omitted_fields: tuple[str, ...] = ()) -> set[st
|
|
|
85
85
|
|
|
86
86
|
found: set[str] = set()
|
|
87
87
|
_collect_ids(data.get("clarificationItems", []), found)
|
|
88
|
+
_collect_ids(data.get("agentActivity", []), found)
|
|
88
89
|
property_name = TASK_TYPE_DATA_PROPERTY.get(data.get("header", {}).get("taskType", ""))
|
|
89
90
|
if property_name:
|
|
90
91
|
block = data.get(property_name, {})
|
|
@@ -120,9 +121,10 @@ def evidence_index(data: dict) -> dict[str, object]:
|
|
|
120
121
|
|
|
121
122
|
def _collect_ids(value: object, found: set[str]) -> None:
|
|
122
123
|
if isinstance(value, dict):
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
for key in ("id", "activityId"):
|
|
125
|
+
row_id = value.get(key)
|
|
126
|
+
if isinstance(row_id, str) and row_id:
|
|
127
|
+
found.add(row_id)
|
|
126
128
|
for nested in value.values():
|
|
127
129
|
_collect_ids(nested, found)
|
|
128
130
|
elif isinstance(value, list):
|
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
import okstra_vendor # noqa: F401 # registers vendored dependency aliases
|
|
10
10
|
from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape
|
|
11
11
|
|
|
12
|
-
from ..final_report_paths import translation_sidecar_path
|
|
12
|
+
from ..final_report_paths import final_report_data_path, translation_sidecar_path
|
|
13
13
|
from ..final_report_schema import load_schema_for_data, validate
|
|
14
14
|
from ..i18n import HTML_DICTIONARY_REL, load_dictionary, make_jinja_global
|
|
15
15
|
from ..report_translation import overlay
|
|
@@ -142,11 +142,13 @@ def render_v2_html_view(
|
|
|
142
142
|
env.filters["paragraphs"] = lambda value: paragraphs(value, anchors)
|
|
143
143
|
response_js = (root / "report.js").read_text(encoding="utf-8")
|
|
144
144
|
base_js = (root / "html/assets/base.js").read_text(encoding="utf-8")
|
|
145
|
+
source_data = final_report_data_path(Path(run_meta.source_report)).as_posix()
|
|
145
146
|
context = {
|
|
146
147
|
**view.context,
|
|
147
148
|
"runMeta": run_meta,
|
|
148
149
|
"reportMeta": _report_meta(data, run_meta),
|
|
149
150
|
"taskType": view.task_type,
|
|
151
|
+
"sourceData": source_data,
|
|
150
152
|
"dataSha256": _sha256(data_path),
|
|
151
153
|
"markdownSha256": _sha256(markdown_path),
|
|
152
154
|
"clarificationItems": data.get("clarificationItems", []),
|
|
@@ -161,7 +163,7 @@ def render_v2_html_view(
|
|
|
161
163
|
output_path.write_text(
|
|
162
164
|
inject_report_index(document, label=translate("base.contents")), encoding="utf-8"
|
|
163
165
|
)
|
|
164
|
-
if context["clarificationItems"]:
|
|
166
|
+
if context["clarificationItems"] or context.get("directionSelection"):
|
|
165
167
|
# The footer tells the reader to drop the exported file here, so the
|
|
166
168
|
# directory has to exist before they go looking for it.
|
|
167
169
|
user_responses_dir_for_report(data_path).mkdir(parents=True, exist_ok=True)
|
|
@@ -8,6 +8,9 @@ from .view_models.error_analysis import build_error_analysis_view
|
|
|
8
8
|
from .view_models.feature_analysis import build_feature_analysis_view
|
|
9
9
|
from .view_models.final_verification import build_final_verification_view
|
|
10
10
|
from .view_models.improvement_discovery import build_improvement_discovery_view
|
|
11
|
+
from .view_models.implementation_option_selection import (
|
|
12
|
+
build_implementation_option_selection_view,
|
|
13
|
+
)
|
|
11
14
|
from .view_models.implementation import build_implementation_view
|
|
12
15
|
from .view_models.implementation_planning import build_implementation_planning_view
|
|
13
16
|
from .view_models.project_analysis import build_project_analysis_view
|
|
@@ -23,6 +26,7 @@ VIEW_BUILDERS = {
|
|
|
23
26
|
"requirements-discovery": build_requirements_discovery_view,
|
|
24
27
|
"improvement-discovery": build_improvement_discovery_view,
|
|
25
28
|
"error-analysis": build_error_analysis_view,
|
|
29
|
+
"implementation-option-selection": build_implementation_option_selection_view,
|
|
26
30
|
"project-analysis": build_project_analysis_view,
|
|
27
31
|
"feature-analysis": build_feature_analysis_view,
|
|
28
32
|
"change-impact-analysis": build_change_impact_analysis_view,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Human-first implementation-direction comparison view."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from ..common import evidence_index
|
|
5
|
+
from ..models import HumanReportView
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_implementation_option_selection_view(data: dict) -> HumanReportView:
|
|
9
|
+
selection = data["implementationOptionSelection"]
|
|
10
|
+
context = {
|
|
11
|
+
"humanSummary": data["humanSummary"],
|
|
12
|
+
"selection": selection,
|
|
13
|
+
"directionSelection": (
|
|
14
|
+
selection if selection["mode"] == "candidate-comparison" else None
|
|
15
|
+
),
|
|
16
|
+
"narrative": selection["userNarrative"],
|
|
17
|
+
"recommendedOption": next(
|
|
18
|
+
(
|
|
19
|
+
option
|
|
20
|
+
for option in selection["rankedOptions"]
|
|
21
|
+
if option["id"] == selection["recommendedOptionId"]
|
|
22
|
+
),
|
|
23
|
+
None,
|
|
24
|
+
),
|
|
25
|
+
"evidenceIndex": evidence_index(data),
|
|
26
|
+
}
|
|
27
|
+
return HumanReportView(
|
|
28
|
+
task_type="implementation-option-selection",
|
|
29
|
+
template_name="html/tasks/implementation-option-selection.template.html",
|
|
30
|
+
context=context,
|
|
31
|
+
figures=(),
|
|
32
|
+
)
|
|
@@ -15,6 +15,8 @@ class PlanApprovalState:
|
|
|
15
15
|
recommended_option: str
|
|
16
16
|
disabled_reason: str
|
|
17
17
|
blocker_ids: tuple[str, ...]
|
|
18
|
+
show_option_selector: bool = True
|
|
19
|
+
reentry_command: str = ""
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def resolve_recommended_option(rec_name: str, names: tuple[str, ...]) -> str:
|
|
@@ -32,6 +34,26 @@ def plan_approval_state(data: dict) -> PlanApprovalState | None:
|
|
|
32
34
|
planning = data.get("implementationPlanning")
|
|
33
35
|
if not isinstance(planning, dict):
|
|
34
36
|
return None
|
|
37
|
+
blockers = tuple(
|
|
38
|
+
row["id"]
|
|
39
|
+
for row in data.get("clarificationItems", [])
|
|
40
|
+
if row.get("blocks") == "approval"
|
|
41
|
+
and row.get("status") in {"open", "answered"}
|
|
42
|
+
)
|
|
43
|
+
reason = f"{len(blockers)} approval blocker(s) unresolved" if blockers else ""
|
|
44
|
+
if planning.get("planningContract") == "selected-direction":
|
|
45
|
+
if planning.get("outcome") == "direction-invalidated" and not reason:
|
|
46
|
+
reason = "selected direction invalidated"
|
|
47
|
+
task_key = (data.get("header") or {}).get("taskKey", "")
|
|
48
|
+
reentry_command = (
|
|
49
|
+
f"/okstra-run task-key={task_key} "
|
|
50
|
+
"task-type=implementation-option-selection"
|
|
51
|
+
if planning.get("outcome") == "direction-invalidated" and task_key
|
|
52
|
+
else ""
|
|
53
|
+
)
|
|
54
|
+
return PlanApprovalState(
|
|
55
|
+
(), "", reason, blockers, False, reentry_command
|
|
56
|
+
)
|
|
35
57
|
names = tuple(
|
|
36
58
|
row["name"]
|
|
37
59
|
for row in planning.get("optionCandidates", [])
|
|
@@ -40,13 +62,6 @@ def plan_approval_state(data: dict) -> PlanApprovalState | None:
|
|
|
40
62
|
if not names:
|
|
41
63
|
return None
|
|
42
64
|
recommended = planning.get("recommendedOption", {}).get("name", "")
|
|
43
|
-
blockers = tuple(
|
|
44
|
-
row["id"]
|
|
45
|
-
for row in data.get("clarificationItems", [])
|
|
46
|
-
if row.get("blocks") == "approval"
|
|
47
|
-
and row.get("status") in {"open", "answered"}
|
|
48
|
-
)
|
|
49
|
-
reason = f"{len(blockers)} approval blocker(s) unresolved" if blockers else ""
|
|
50
65
|
return PlanApprovalState(
|
|
51
66
|
names,
|
|
52
67
|
resolve_recommended_option(recommended, names),
|
|
@@ -89,26 +104,42 @@ _OMITTED_FIELDS = (
|
|
|
89
104
|
|
|
90
105
|
def build_implementation_planning_view(data: dict) -> HumanReportView:
|
|
91
106
|
planning = data["implementationPlanning"]
|
|
92
|
-
figure = _stage_figure(planning)
|
|
107
|
+
figure = _stage_figure(planning) if planning.get("stageMap") else None
|
|
93
108
|
approval = plan_approval_state(data)
|
|
109
|
+
activities = tuple(
|
|
110
|
+
row for row in data.get("agentActivity", []) if isinstance(row, dict)
|
|
111
|
+
)
|
|
112
|
+
decision_cards = tuple(
|
|
113
|
+
row
|
|
114
|
+
for row in data.get("clarificationItems", [])
|
|
115
|
+
if isinstance(row, dict)
|
|
116
|
+
and row.get("blocks") == "approval"
|
|
117
|
+
and isinstance(row.get("approvalContext"), dict)
|
|
118
|
+
)
|
|
94
119
|
context = {
|
|
95
120
|
"humanSummary": data["humanSummary"],
|
|
96
121
|
"planning": planning,
|
|
97
|
-
"narrative": planning
|
|
122
|
+
"narrative": planning.get("userNarrative", {}),
|
|
98
123
|
"stageFigure": figure,
|
|
99
124
|
"approval": approval,
|
|
100
125
|
"openDecisions": [
|
|
101
126
|
row
|
|
102
127
|
for row in data.get("clarificationItems", [])
|
|
103
|
-
if row
|
|
128
|
+
if isinstance(row, dict)
|
|
129
|
+
and row.get("blocks") == "approval"
|
|
104
130
|
and row.get("status") in {"open", "answered"}
|
|
105
131
|
],
|
|
132
|
+
"agentActivities": activities,
|
|
133
|
+
"activityById": {
|
|
134
|
+
row["activityId"]: row for row in activities if row.get("activityId")
|
|
135
|
+
},
|
|
136
|
+
"decisionCards": decision_cards,
|
|
106
137
|
"evidenceIndex": evidence_index(data),
|
|
107
138
|
}
|
|
108
139
|
return HumanReportView(
|
|
109
140
|
"implementation-planning",
|
|
110
141
|
"html/tasks/implementation-planning.template.html",
|
|
111
142
|
context,
|
|
112
|
-
(figure,),
|
|
143
|
+
(figure,) if figure is not None else (),
|
|
113
144
|
_OMITTED_FIELDS,
|
|
114
145
|
)
|
|
@@ -125,8 +125,10 @@ PROSE_KEYS = frozenset({
|
|
|
125
125
|
"topic",
|
|
126
126
|
"trigger",
|
|
127
127
|
"triggerSignal",
|
|
128
|
+
"unblockCondition",
|
|
128
129
|
"userChoice",
|
|
129
130
|
"userInput",
|
|
131
|
+
"userText",
|
|
130
132
|
"verification",
|
|
131
133
|
"verificationMethod",
|
|
132
134
|
"verificationSignal",
|
|
@@ -138,6 +140,8 @@ PROSE_KEYS = frozenset({
|
|
|
138
140
|
# that must stay verbatim to be evidence at all.
|
|
139
141
|
STRUCTURAL_KEYS = frozenset({
|
|
140
142
|
"action", # implementation.py: _FILE_ACTIONS lookup key
|
|
143
|
+
"activityId",
|
|
144
|
+
"activityIds",
|
|
141
145
|
"adapter",
|
|
142
146
|
"affectedId",
|
|
143
147
|
"affectedTarget",
|
|
@@ -152,12 +156,15 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
152
156
|
"capturedHeadSha",
|
|
153
157
|
"carriedForwardFromSeq",
|
|
154
158
|
"carryJson",
|
|
159
|
+
"checkRefs",
|
|
155
160
|
"clarificationId",
|
|
161
|
+
"classification",
|
|
156
162
|
"claudeCode",
|
|
157
163
|
"collectorBranch",
|
|
158
164
|
"command",
|
|
159
165
|
"commandOrObservation",
|
|
160
166
|
"commandOrTest",
|
|
167
|
+
"commands",
|
|
161
168
|
"commitListQuote",
|
|
162
169
|
"complexity",
|
|
163
170
|
"componentId",
|
|
@@ -165,14 +172,18 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
165
172
|
"conformanceTests",
|
|
166
173
|
"coveredBy",
|
|
167
174
|
"createdAt",
|
|
175
|
+
"cwd",
|
|
168
176
|
"cycle",
|
|
169
177
|
"dataStore",
|
|
170
178
|
"date",
|
|
171
179
|
"dependsOn",
|
|
172
180
|
"diffSummaryQuote",
|
|
173
181
|
"direction",
|
|
182
|
+
"disposition",
|
|
174
183
|
"dispatches",
|
|
175
184
|
"executorWorktreePath",
|
|
185
|
+
"evidenceRefs",
|
|
186
|
+
"exitCode",
|
|
176
187
|
"existingPrUrl",
|
|
177
188
|
"externalSystem",
|
|
178
189
|
"featureId",
|
|
@@ -215,6 +226,7 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
215
226
|
"ownerComponentId",
|
|
216
227
|
"path",
|
|
217
228
|
"planFile",
|
|
229
|
+
"planItemIds",
|
|
218
230
|
"planStep",
|
|
219
231
|
"prepItemId",
|
|
220
232
|
"project",
|
|
@@ -223,11 +235,13 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
223
235
|
"rawStat",
|
|
224
236
|
"readBoundary",
|
|
225
237
|
"readOnlyCommandLog",
|
|
238
|
+
"recommendedDisposition",
|
|
226
239
|
"relatedIds",
|
|
227
240
|
"relation",
|
|
228
241
|
"replacedInTest",
|
|
229
242
|
"replacedWith",
|
|
230
243
|
"reportPath",
|
|
244
|
+
"resultPath",
|
|
231
245
|
"requestKind",
|
|
232
246
|
"requestPath",
|
|
233
247
|
"requestVerbatim", # the user's own words, quoted as evidence
|