okstra 0.149.0 → 0.151.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/architecture.md +1 -1
- 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/report-writer-worker.md +8 -0
- package/runtime/agents/workers/translator-worker.md +67 -0
- package/runtime/bin/okstra-render-final-report.py +0 -11
- package/runtime/bin/okstra-report-translate.py +191 -0
- package/runtime/prompts/lead/adapters/claude-code.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -0
- package/runtime/prompts/lead/plan-body-verification.md +1 -1
- package/runtime/prompts/lead/report-writer.md +16 -15
- package/runtime/prompts/lead/team-contract.md +2 -2
- package/runtime/prompts/wizard/prompts.ko.json +17 -1
- package/runtime/python/okstra_ctl/analysis_inputs.py +24 -9
- package/runtime/python/okstra_ctl/analysis_packet.py +23 -1
- package/runtime/python/okstra_ctl/clarification_items.py +241 -44
- package/runtime/python/okstra_ctl/codex_dispatch.py +2 -1
- package/runtime/python/okstra_ctl/convergence.py +15 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +2 -2
- package/runtime/python/okstra_ctl/dispatch_state.py +12 -1
- package/runtime/python/okstra_ctl/final_report_paths.py +22 -1
- package/runtime/python/okstra_ctl/i18n.py +12 -7
- package/runtime/python/okstra_ctl/render_final_report.py +18 -17
- package/runtime/python/okstra_ctl/report_finalize.py +18 -0
- package/runtime/python/okstra_ctl/report_html/filters.py +15 -77
- package/runtime/python/okstra_ctl/report_html/render.py +44 -2
- package/runtime/python/okstra_ctl/report_translation.py +469 -0
- package/runtime/python/okstra_ctl/report_views.py +23 -9
- package/runtime/python/okstra_ctl/run.py +1 -1
- package/runtime/python/okstra_ctl/user_response.py +11 -6
- package/runtime/python/okstra_ctl/wizard.py +100 -25
- package/runtime/python/okstra_ctl/worker_liveness.py +130 -36
- package/runtime/templates/reports/html/base.template.html +12 -12
- package/runtime/templates/reports/html/i18n/en.json +395 -0
- package/runtime/templates/reports/html/i18n/ko.json +395 -0
- package/runtime/templates/reports/html/macros/forms.html +16 -16
- package/runtime/templates/reports/html/macros/visualizations.html +2 -2
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +17 -17
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +16 -16
- package/runtime/templates/reports/html/tasks/final-verification.template.html +12 -12
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +37 -37
- package/runtime/templates/reports/html/tasks/implementation.template.html +18 -18
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +7 -7
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +29 -29
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +13 -13
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +14 -14
- package/runtime/templates/reports/report.js +8 -5
- package/runtime/validators/validate-report-views.py +1 -1
- package/runtime/validators/validate-run.py +59 -31
- package/src/cli-registry.mjs +11 -0
- package/src/commands/inspect/worker-liveness.mjs +9 -7
- package/src/commands/report/translate.mjs +31 -0
- package/src/lib/helper-scripts.mjs +1 -0
- package/runtime/templates/reports/i18n/ko.json +0 -273
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="project-overview">
|
|
7
|
-
<h2>
|
|
7
|
+
<h2>{{ t('tasks.project-analysis.project-overview') }}</h2>
|
|
8
8
|
{{ render_narrative(narrative.projectOverview, "projectAnalysis.userNarrative.projectOverview") }}
|
|
9
9
|
<div class="summary-grid">
|
|
10
10
|
{% for reason in humanSummary.whyItMatters %}{{ summary_card("", reason, "important") }}{% endfor %}
|
|
@@ -12,63 +12,63 @@
|
|
|
12
12
|
</section>
|
|
13
13
|
|
|
14
14
|
<section data-report-section="architecture" data-report-field="projectAnalysis.components">
|
|
15
|
-
<h2>
|
|
15
|
+
<h2>{{ t('tasks.project-analysis.architecture-and-dependencies') }}</h2>
|
|
16
16
|
{{ render_narrative(narrative.architectureExplanation, "projectAnalysis.userNarrative.architectureExplanation") }}
|
|
17
17
|
{{ figure(architectureFigure, anchor_nodes=true) }}
|
|
18
18
|
</section>
|
|
19
19
|
|
|
20
20
|
<section data-report-section="infrastructure">
|
|
21
|
-
<h2>
|
|
21
|
+
<h2>{{ t('tasks.project-analysis.infrastructure') }}</h2>
|
|
22
22
|
{{ figure(infrastructureFigure) }}
|
|
23
23
|
</section>
|
|
24
24
|
|
|
25
25
|
{% if project.get("techStack") %}
|
|
26
26
|
<section data-report-section="tech-stack" data-report-field="projectAnalysis.techStack">
|
|
27
|
-
<h2>
|
|
28
|
-
<h3>
|
|
29
|
-
<table><thead><tr><th>
|
|
30
|
-
{% if project.techStack.get("frameworks") %}<h3>
|
|
31
|
-
<table><thead><tr><th>
|
|
32
|
-
{% if project.techStack.get("toolchain") %}<h3>
|
|
33
|
-
<table><thead><tr><th>
|
|
27
|
+
<h2>{{ t('tasks.project-analysis.tech-stack') }}</h2>
|
|
28
|
+
<h3>{{ t('tasks.project-analysis.language') }}</h3>
|
|
29
|
+
<table><thead><tr><th>{{ t('tasks.project-analysis.language') }}</th><th>{{ t('tasks.project-analysis.version') }}</th><th>{{ t('tasks.project-analysis.version-source') }}</th><th>{{ t('tasks.project-analysis.files') }}</th><th>{{ t('tasks.project-analysis.lines') }}</th></tr></thead><tbody>{% for row in project.techStack.languages %}<tr><td>{{ row.name | inline_code }}</td><td><code>{{ row.version }}</code></td><td>{{ row.versionSource | enum_label("versionSource") }}</td><td>{{ row.get("fileCount") | default("--") }}</td><td>{{ row.get("lineCount") | default("--") }}</td></tr>{% endfor %}</tbody></table>
|
|
30
|
+
{% if project.techStack.get("frameworks") %}<h3>{{ t('tasks.project-analysis.framework') }}</h3>
|
|
31
|
+
<table><thead><tr><th>{{ t('tasks.project-analysis.framework') }}</th><th>{{ t('tasks.project-analysis.role') }}</th><th>{{ t('tasks.project-analysis.evidence') }}</th></tr></thead><tbody>{% for row in project.techStack.frameworks %}<tr>{{ row_key(pairs=[("Name", row.name), ("Version", row.version), ("Version source", row.versionSource | enum_label("versionSource"))]) }}<td>{{ row.role | inline_code }}</td><td>{{ row.get("currentCodeEvidence") | default([]) | code_evidence }}</td></tr>{% endfor %}</tbody></table>{% endif %}
|
|
32
|
+
{% if project.techStack.get("toolchain") %}<h3>{{ t('tasks.project-analysis.tool') }}</h3>
|
|
33
|
+
<table><thead><tr><th>{{ t('tasks.project-analysis.tool') }}</th><th>{{ t('tasks.project-analysis.command') }}</th></tr></thead><tbody>{% for row in project.techStack.toolchain %}<tr>{{ row_key(pairs=[("Name", row.name), ("Kind", row.kind | enum_label("toolKind")), ("Version", row.get("version"))]) }}<td>{% if row.get("command") %}<code>{{ row.command }}</code>{% else %}--{% endif %}</td></tr>{% endfor %}</tbody></table>{% endif %}
|
|
34
34
|
</section>
|
|
35
35
|
{% endif %}
|
|
36
36
|
|
|
37
37
|
{% if project.get("internalInterfaces") %}
|
|
38
38
|
<section data-report-section="internal-interfaces" data-report-field="projectAnalysis.internalInterfaces">
|
|
39
|
-
<h2>
|
|
40
|
-
<table><thead><tr><th>
|
|
39
|
+
<h2>{{ t('tasks.project-analysis.internal-interfaces') }}</h2>
|
|
40
|
+
<table><thead><tr><th>{{ t('tasks.project-analysis.interface') }}</th><th>{{ t('tasks.project-analysis.signature') }}</th><th>{{ t('tasks.project-analysis.consumers') }}</th><th>{{ t('tasks.project-analysis.evidence') }}</th></tr></thead><tbody>{% for row in project.internalInterfaces %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[("ID", row.id), ("Name", row.name), ("Kind", row.kind | enum_label("interfaceKind")), ("Owner", row.ownerComponentId)]) }}<td><code>{{ row.signature }}</code></td><td>{{ (row.get("consumers") | default([])) | join(", ") | inline_code }}</td><td>{{ row.get("currentCodeEvidence") | default([]) | code_evidence }}</td></tr>{% endfor %}</tbody></table>
|
|
41
41
|
</section>
|
|
42
42
|
{% endif %}
|
|
43
43
|
|
|
44
44
|
{% if project.get("workflows") %}
|
|
45
45
|
<section data-report-section="workflows" data-report-field="projectAnalysis.workflows">
|
|
46
|
-
<h2>
|
|
46
|
+
<h2>{{ t('tasks.project-analysis.workflows') }}</h2>
|
|
47
47
|
{{ figure(workflowFigure) }}
|
|
48
48
|
{% for flow in project.workflows %}
|
|
49
49
|
<article class="workflow" id="id-{{ flow.id }}">
|
|
50
50
|
<p class="eyebrow">{{ flow.id }}</p>
|
|
51
51
|
<h3>{{ flow.name | inline_code }}</h3>
|
|
52
|
-
<p><strong>
|
|
53
|
-
<table><thead><tr><th>
|
|
52
|
+
<p><strong>{{ t('tasks.project-analysis.starts-with') }}</strong> — {{ flow.trigger | inline_code }}{% if flow.get("outcome") %} · <strong>{{ t('tasks.project-analysis.result') }}</strong> — {{ flow.outcome | inline_code }}{% endif %}</p>
|
|
53
|
+
<table><thead><tr><th>{{ t('tasks.project-analysis.order') }}</th><th>{{ t('tasks.project-analysis.action') }}</th><th>{{ t('tasks.project-analysis.component') }}</th><th>{{ t('tasks.project-analysis.external-system') }}</th></tr></thead><tbody>{% for step in flow.steps %}<tr><td>{{ step.order }}</td><td>{{ step.action | inline_code }}</td><td>{{ step.componentId | inline_code }}</td><td>{% if step.get("externalSystem") %}{{ step.externalSystem | inline_code }}{% else %}--{% endif %}</td></tr>{% endfor %}</tbody></table>
|
|
54
54
|
</article>
|
|
55
55
|
{% endfor %}
|
|
56
56
|
</section>
|
|
57
57
|
{% endif %}
|
|
58
58
|
|
|
59
59
|
<section data-report-section="boundaries">
|
|
60
|
-
<h2>
|
|
61
|
-
<p class="scan-coverage" data-report-field="projectAnalysis.scanCoverage"><strong>{{ project.scanCoverage.scannedPathCount }}</strong> paths
|
|
62
|
-
<h3>
|
|
63
|
-
<table data-report-field="projectAnalysis.entryPoints"><thead><tr><th>
|
|
64
|
-
<h3>
|
|
65
|
-
<table data-report-field="projectAnalysis.dataStores"><thead><tr><th>
|
|
66
|
-
<h3>
|
|
67
|
-
<table data-report-field="projectAnalysis.externalSystems"><thead><tr><th>
|
|
60
|
+
<h2>{{ t('tasks.project-analysis.system-boundaries') }}</h2>
|
|
61
|
+
<p class="scan-coverage" data-report-field="projectAnalysis.scanCoverage"><strong>{{ project.scanCoverage.scannedPathCount }}</strong> {{ t('tasks.project-analysis.paths-read') }} <strong>{{ project.scanCoverage.unscannedPathCount }}</strong> left unread — {{ project.scanCoverage.reason | inline_code }}</p>
|
|
62
|
+
<h3>{{ t('tasks.project-analysis.entry-point') }}</h3>
|
|
63
|
+
<table data-report-field="projectAnalysis.entryPoints"><thead><tr><th>{{ t('tasks.project-analysis.entry-point') }}</th><th>{{ t('tasks.project-analysis.path') }}</th><th>{{ t('tasks.project-analysis.role') }}</th></tr></thead><tbody>{% for row in project.entryPoints %}<tr>{{ row_key(pairs=[("Symbol", row.symbol), ("Kind", row.kind)]) }}<td><code>{{ row.path }}</code></td><td>{{ row.role | inline_code }}</td></tr>{% endfor %}</tbody></table>
|
|
64
|
+
<h3>{{ t('tasks.project-analysis.data-stores') }}</h3>
|
|
65
|
+
<table data-report-field="projectAnalysis.dataStores"><thead><tr><th>{{ t('tasks.project-analysis.store') }}</th><th>{{ t('tasks.project-analysis.reads') }}</th><th>{{ t('tasks.project-analysis.writes') }}</th><th>{{ t('tasks.project-analysis.evidence') }}</th></tr></thead><tbody>{% for row in project.dataStores %}<tr>{{ row_key(pairs=[("Name", row.name), ("Owner", row.ownerComponentId)]) }}<td>{{ row.readBoundary | inline_code }}</td><td>{{ row.writeBoundary | inline_code }}</td><td>{{ row.currentCodeEvidence | code_evidence }}</td></tr>{% endfor %}</tbody></table>
|
|
66
|
+
<h3>{{ t('tasks.project-analysis.external-system') }}</h3>
|
|
67
|
+
<table data-report-field="projectAnalysis.externalSystems"><thead><tr><th>{{ t('tasks.project-analysis.external-system') }}</th><th>{{ t('tasks.project-analysis.evidence') }}</th></tr></thead><tbody>{% for row in project.externalSystems %}<tr>{{ row_key(pairs=[("Name", row.name), ("Adapter", row.adapter), ("Direction", row.direction)]) }}<td>{{ row.currentCodeEvidence | code_evidence }}</td></tr>{% endfor %}</tbody></table>
|
|
68
68
|
</section>
|
|
69
69
|
|
|
70
70
|
<section data-report-section="hotspots" data-report-field="projectAnalysis.rankedHotspots">
|
|
71
|
-
<h2>
|
|
71
|
+
<h2>{{ t('tasks.project-analysis.hotspots-to-look-at-first') }}</h2>
|
|
72
72
|
{{ render_narrative(narrative.hotspotExplanation, "projectAnalysis.userNarrative.hotspotExplanation") }}
|
|
73
73
|
<div class="summary-grid">
|
|
74
74
|
{% for row in project.rankedHotspots %}
|
|
@@ -78,20 +78,20 @@
|
|
|
78
78
|
</section>
|
|
79
79
|
|
|
80
80
|
<section data-report-section="quality-and-tests" data-report-field="projectAnalysis.qualityCoverage">
|
|
81
|
-
<h2>
|
|
81
|
+
<h2>{{ t('tasks.project-analysis.quality-and-test-coverage') }}</h2>
|
|
82
82
|
{{ render_narrative(narrative.qualityExplanation, "projectAnalysis.userNarrative.qualityExplanation") }}
|
|
83
|
-
<table><thead><tr><th>
|
|
83
|
+
<table><thead><tr><th>{{ t('tasks.project-analysis.area') }}</th><th>{{ t('tasks.project-analysis.detail') }}</th><th>{{ t('tasks.project-analysis.evidence') }}</th></tr></thead><tbody>{% for row in project.qualityCoverage %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[("ID", row.id), ("Area", row.area)], status_name="Status", status_raw=row.status, status_text=row.status | enum_label("coverage")) }}<td>{{ row.summary | inline_code }}</td><td>{{ row.evidenceRefs | evidence_refs }}</td></tr>{% endfor %}</tbody></table>
|
|
84
84
|
<dl class="enum-legend">{% for label, hint in "coverage" | enum_legend %}<dt>{{ label }}</dt><dd>{{ hint }}</dd>{% endfor %}</dl>
|
|
85
85
|
</section>
|
|
86
86
|
|
|
87
87
|
<section data-report-section="feature-map" data-report-field="projectAnalysis.featureIndex">
|
|
88
|
-
<h2>
|
|
88
|
+
<h2>{{ t('tasks.project-analysis.feature-map') }}</h2>
|
|
89
89
|
<div class="summary-grid">{% for row in project.featureIndex %}{{ summary_card(row.id ~ " · " ~ (row.name | inline_code), row.description, anchor=row.id) }}{% endfor %}</div>
|
|
90
90
|
</section>
|
|
91
91
|
|
|
92
92
|
<section data-report-section="user-actions">
|
|
93
|
-
<h2>
|
|
93
|
+
<h2>{{ t('tasks.project-analysis.what-to-do-next') }}</h2>
|
|
94
94
|
{{ action_list(humanSummary.actions) }}
|
|
95
|
-
{% if humanSummary.blockers %}<h3>
|
|
95
|
+
{% if humanSummary.blockers %}<h3>{{ t('tasks.project-analysis.blockers') }}</h3><ul>{% for item in humanSummary.blockers %}<li>{{ item | inline_code }}</li>{% endfor %}</ul>{% endif %}
|
|
96
96
|
</section>
|
|
97
97
|
{% endblock %}
|
|
@@ -4,46 +4,46 @@
|
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="handoff-outcome">
|
|
7
|
-
<h2>
|
|
7
|
+
<h2>{{ t('tasks.release-handoff.handoff-outcome') }}</h2>
|
|
8
8
|
{{ render_narrative(narrative.handoffOutcome, "releaseHandoff.userNarrative.handoffOutcome") }}
|
|
9
9
|
{{ figure(handoffFigure) }}
|
|
10
10
|
</section>
|
|
11
11
|
|
|
12
12
|
{% if handoff.get("handoffScope") %}
|
|
13
13
|
<section data-report-section="handoff-scope" data-report-field="releaseHandoff.handoffScope">
|
|
14
|
-
<h2>
|
|
14
|
+
<h2>{{ t('tasks.release-handoff.handoff-scope') }}</h2>
|
|
15
15
|
<p><strong>{{ handoff.handoffScope.mode | enum_label("handoffMode") }}</strong>{% if handoff.handoffScope.get("stages") %} — stages {{ handoff.handoffScope.stages | join(", ") }}{% endif %}{% if handoff.handoffScope.get("collectorBranch") %} · Collector branch <code>{{ handoff.handoffScope.collectorBranch }}</code>{% endif %}</p>
|
|
16
16
|
</section>
|
|
17
17
|
{% endif %}
|
|
18
18
|
|
|
19
19
|
<section data-report-section="user-selection" data-report-field="releaseHandoff.userSelections">
|
|
20
|
-
<h2>
|
|
21
|
-
<dl><dt>
|
|
20
|
+
<h2>{{ t('tasks.release-handoff.the-behaviour-you-chose') }}</h2>
|
|
21
|
+
<dl><dt>{{ t('tasks.release-handoff.handoff-mode') }}</dt><dd>{{ handoff.userSelections.h1 }}</dd><dt>{{ t('tasks.release-handoff.conflict-check') }}</dt><dd>{{ handoff.userSelections.h2b }}</dd><dt>{{ t('tasks.release-handoff.pr-body') }}</dt><dd>{{ handoff.userSelections.h3 }}</dd></dl>
|
|
22
22
|
</section>
|
|
23
23
|
|
|
24
24
|
<section data-report-section="branch-and-pr" data-report-field="releaseHandoff.featureBranchState">
|
|
25
|
-
<h2>
|
|
25
|
+
<h2>{{ t('tasks.release-handoff.branch-and-pr') }}</h2>
|
|
26
26
|
{{ render_narrative(narrative.branchAndPrExplanation, "releaseHandoff.userNarrative.branchAndPrExplanation") }}
|
|
27
|
-
<p><strong>
|
|
28
|
-
<p data-report-field="releaseHandoff.pullRequestOutcome"><strong>
|
|
27
|
+
<p><strong>{{ t('tasks.release-handoff.branch') }}</strong> {{ handoff.featureBranchState.branchName }}</p>
|
|
28
|
+
<p data-report-field="releaseHandoff.pullRequestOutcome"><strong>{{ t('tasks.release-handoff.pr-result') }}</strong> {{ handoff.pullRequestOutcome.kind }}{% if prUrl %} · <a href="{{ prUrl }}" rel="noopener noreferrer">{{ t('tasks.release-handoff.pull-request-open') }}</a>{% endif %}</p>
|
|
29
29
|
</section>
|
|
30
30
|
|
|
31
31
|
<section data-report-section="conflicts" data-report-field="releaseHandoff.mergeConflictProbe">
|
|
32
|
-
<h2>
|
|
32
|
+
<h2>{{ t('tasks.release-handoff.conflict-verdict') }}</h2>
|
|
33
33
|
{{ render_narrative(narrative.conflictExplanation, "releaseHandoff.userNarrative.conflictExplanation") }}
|
|
34
34
|
{{ summary_card(handoff.mergeConflictProbe.kind, "Base branch: " ~ (handoff.mergeConflictProbe.baseBranch | default('not-run'))) }}
|
|
35
35
|
{% if handoff.mergeConflictProbe.conflictingPaths %}<ul>{% for path in handoff.mergeConflictProbe.conflictingPaths %}<li>{{ path }}</li>{% endfor %}</ul>{% endif %}
|
|
36
36
|
</section>
|
|
37
37
|
|
|
38
38
|
<section data-report-section="delivered-commits" data-report-field="releaseHandoff.commitList">
|
|
39
|
-
<h2>
|
|
40
|
-
{% if handoff.commitList is mapping %}<p>
|
|
41
|
-
<table><thead><tr><th>
|
|
42
|
-
<p data-report-field="releaseHandoff.sourceVerificationReport"><strong>
|
|
39
|
+
<h2>{{ t('tasks.release-handoff.commits-delivered') }}</h2>
|
|
40
|
+
{% if handoff.commitList is mapping %}<p>{{ t('tasks.release-handoff.there-are-no-commits') }}</p>{% else %}
|
|
41
|
+
<table><thead><tr><th>{{ t('tasks.release-handoff.commit') }}</th><th>{{ t('tasks.release-handoff.subject') }}</th><th>{{ t('tasks.release-handoff.files') }}</th></tr></thead><tbody>{% for row in handoff.commitList %}<tr>{{ row_key(pairs=[("SHA", row.shortSha), ("Order", loop.index)]) }}<td>{{ row.subject | inline_code }}</td><td>{% for file in row.files %}<code>{{ file }}</code>{% if not loop.last %} {% endif %}{% endfor %}</td></tr>{% endfor %}</tbody></table>{% endif %}
|
|
42
|
+
<p data-report-field="releaseHandoff.sourceVerificationReport"><strong>{{ t('tasks.release-handoff.the-verification-report-this-rests-on') }}</strong> — <code>{{ handoff.sourceVerificationReport.path }}</code> · {{ handoff.sourceVerificationReport.verdictTokenQuote | inline_code }}</p>
|
|
43
43
|
</section>
|
|
44
44
|
|
|
45
45
|
<section data-report-section="next-action">
|
|
46
|
-
<h2>
|
|
46
|
+
<h2>{{ t('tasks.release-handoff.what-to-do-next') }}</h2>
|
|
47
47
|
{{ render_narrative(narrative.nextAction, "releaseHandoff.userNarrative.nextAction") }}
|
|
48
48
|
<p>{{ handoff.routingRecommendation | inline_code }}</p>
|
|
49
49
|
</section>
|
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="request">
|
|
7
|
-
<h2>
|
|
7
|
+
<h2>{{ t('tasks.requirements-discovery.the-request-as-understood') }}</h2>
|
|
8
8
|
{{ render_narrative(narrative.requestUnderstanding, "requirementsDiscovery.userNarrative.requestUnderstanding") }}
|
|
9
|
-
<h3>
|
|
9
|
+
<h3>{{ t('tasks.requirements-discovery.your-request-verbatim') }}</h3>
|
|
10
10
|
<p data-report-field="requirementsDiscovery.requestVerbatim">{{ requirements.requestVerbatim | inline_code }}</p>
|
|
11
|
-
<h3>
|
|
11
|
+
<h3>{{ t('tasks.requirements-discovery.how-the-system-reads-it') }}</h3>
|
|
12
12
|
<p data-report-field="requirementsDiscovery.systemInterpretation">{{ requirements.systemInterpretation | inline_code }}</p>
|
|
13
13
|
<dl data-report-field="requirementsDiscovery.classification">{% for key, value in requirements.classification.items() %}<dt>{{ key }}</dt><dd>{{ value | inline_code }}</dd>{% endfor %}</dl>
|
|
14
14
|
</section>
|
|
15
15
|
|
|
16
16
|
{% if requirements.get("rejectionCriteria") %}
|
|
17
17
|
<section data-report-section="rejection-criteria" data-report-field="requirementsDiscovery.rejectionCriteria">
|
|
18
|
-
<h2>
|
|
18
|
+
<h2>{{ t('tasks.requirements-discovery.what-would-prove-this-wrong') }}</h2>
|
|
19
19
|
{% if requirements.rejectionCriteria.source == "absent-not-material" %}
|
|
20
|
-
<p>
|
|
20
|
+
<p>{{ t('tasks.requirements-discovery.the-requester-left-no-rejection-criteria-eve') }}</p>
|
|
21
21
|
{% else %}
|
|
22
22
|
<p>{{ requirements.rejectionCriteria.get("statement") | inline_code }}</p>
|
|
23
23
|
<p class="evidence-refs">Source: {{ requirements.rejectionCriteria.source | enum_label("rejectionSource") }}{% if requirements.rejectionCriteria.get("clarificationId") %} · {{ requirements.rejectionCriteria.clarificationId | inline_code }}{% endif %}{% if requirements.rejectionCriteria.get("evidenceRefs") %} · {{ requirements.rejectionCriteria.evidenceRefs | evidence_refs }}{% endif %}</p>
|
|
@@ -27,31 +27,31 @@
|
|
|
27
27
|
|
|
28
28
|
{% if requirements.get("domainAlignment") %}
|
|
29
29
|
<section data-report-section="domain-alignment" data-report-field="requirementsDiscovery.domainAlignment">
|
|
30
|
-
<h2>
|
|
30
|
+
<h2>{{ t('tasks.requirements-discovery.terms-settled') }}</h2>
|
|
31
31
|
<p>Glossary {{ "checked" if requirements.domainAlignment.glossaryConsulted else "none" }} · Decision record {{ "checked" if requirements.domainAlignment.decisionsConsulted else "none" }}</p>
|
|
32
|
-
<table><thead><tr><th>
|
|
32
|
+
<table><thead><tr><th>{{ t('tasks.requirements-discovery.as-the-brief-put-it') }}</th><th>{{ t('tasks.requirements-discovery.settled-term') }}</th><th>{{ t('tasks.requirements-discovery.settled-by') }}</th><th>{{ t('tasks.requirements-discovery.evidence') }}</th></tr></thead><tbody>{% for row in requirements.domainAlignment.terms %}<tr><td>{{ row.raw | inline_code }}</td><td>{{ row.canonical | inline_code }}</td><td>{{ row.resolvedBy | enum_label("termSource") }}</td><td>{{ row.get("evidenceRefs") | default([]) | evidence_refs }}</td></tr>{% else %}<tr><td colspan="4">{{ t('tasks.requirements-discovery.no-term-was-ambiguous') }}</td></tr>{% endfor %}</tbody></table>
|
|
33
33
|
</section>
|
|
34
34
|
{% endif %}
|
|
35
35
|
|
|
36
36
|
<section data-report-section="requirements">
|
|
37
|
-
<h2>
|
|
37
|
+
<h2>{{ t('tasks.requirements-discovery.settled-and-unsettled-requirements') }}</h2>
|
|
38
38
|
{{ render_narrative(narrative.confirmedRequirements, "requirementsDiscovery.userNarrative.confirmedRequirements") }}
|
|
39
39
|
<div class="summary-grid" data-report-field="requirementsDiscovery.confirmedRequirements">{% for row in requirements.confirmedRequirements %}{{ summary_card(row.id ~ " · Settled", row.statement, anchor=row.id) }}{% endfor %}</div>
|
|
40
40
|
{{ render_narrative(narrative.unresolvedRequirements, "requirementsDiscovery.userNarrative.unresolvedRequirements") }}
|
|
41
|
-
<div class="summary-grid" data-report-field="requirementsDiscovery.unresolvedRequirements">{% for row in requirements.unresolvedRequirements %}{{ summary_card(row.id ~ " · Decision needed", row.statement, "important") }}{% else %}<p>
|
|
41
|
+
<div class="summary-grid" data-report-field="requirementsDiscovery.unresolvedRequirements">{% for row in requirements.unresolvedRequirements %}{{ summary_card(row.id ~ " · Decision needed", row.statement, "important") }}{% else %}<p>{{ t('tasks.requirements-discovery.nothing-is-left-unsettled') }}</p>{% endfor %}</div>
|
|
42
42
|
</section>
|
|
43
43
|
|
|
44
44
|
<section data-report-section="routing">
|
|
45
|
-
<h2>
|
|
45
|
+
<h2>{{ t('tasks.requirements-discovery.next-tasks-and-dependencies') }}</h2>
|
|
46
46
|
{{ render_narrative(narrative.routingExplanation, "requirementsDiscovery.userNarrative.routingExplanation") }}
|
|
47
|
-
<p><strong>
|
|
47
|
+
<p><strong>{{ t('tasks.requirements-discovery.next-task') }}</strong> {{ requirements.routing.nextTaskType }} · {{ requirements.routing.rationale | inline_code }}</p>
|
|
48
48
|
{{ figure(routingFigure) }}
|
|
49
|
-
{% if not requirements.taskDependencies %}<p class="empty-state">
|
|
49
|
+
{% if not requirements.taskDependencies %}<p class="empty-state">{{ t('tasks.requirements-discovery.this-requirements-task-stands-alone-nothing') }}</p>{% endif %}
|
|
50
50
|
</section>
|
|
51
51
|
|
|
52
52
|
<section data-report-section="decisions">
|
|
53
|
-
<h2>
|
|
54
|
-
<div class="summary-grid">{% for row in clarificationItems %}{{ summary_card(row.id ~ " · " ~ row.kind, row.statement, "important") }}{% else %}<p>
|
|
53
|
+
<h2>{{ t('tasks.requirements-discovery.decisions-that-are-yours') }}</h2>
|
|
54
|
+
<div class="summary-grid">{% for row in clarificationItems %}{{ summary_card(row.id ~ " · " ~ row.kind, row.statement, "important") }}{% else %}<p>{{ t('tasks.requirements-discovery.no-further-decision-is-needed') }}</p>{% endfor %}</div>
|
|
55
55
|
{{ action_list(humanSummary.actions) }}
|
|
56
56
|
</section>
|
|
57
57
|
{% endblock %}
|
|
@@ -331,10 +331,7 @@
|
|
|
331
331
|
if (/Reader Summary|Verdict Card|Clarification Items|Recommended Next Steps|Follow-up Tasks/.test(text)) {
|
|
332
332
|
return "action";
|
|
333
333
|
}
|
|
334
|
-
|
|
335
|
-
// the only audit heading the ko dictionary translates away from its English
|
|
336
|
-
// form. tests/report/test_report_views.py pins the two in sync.
|
|
337
|
-
if (/Evidence|Cross Verification|Execution Status|Token Usage|토큰 사용량|Plan Body Verification|Round History/.test(text)) {
|
|
334
|
+
if (/Evidence|Cross Verification|Execution Status|Token Usage|Plan Body Verification|Round History/.test(text)) {
|
|
338
335
|
return "audit";
|
|
339
336
|
}
|
|
340
337
|
return "detail";
|
|
@@ -356,7 +353,13 @@
|
|
|
356
353
|
// the run-metadata list and table of contents that trail it are document
|
|
357
354
|
// chrome — classifying them would hide the title in action mode, because
|
|
358
355
|
// the title text ("… Cross Verification Final Report") reads as audit.
|
|
359
|
-
|
|
356
|
+
// A section that declares its own kind wins. Heading text is a fallback:
|
|
357
|
+
// the HTML's headings render in the reader's language, so "Evidence
|
|
358
|
+
// ledger" reads as audit and "근거 대장" reads as nothing at all.
|
|
359
|
+
var declared = node.getAttribute && node.getAttribute("data-reader-kind");
|
|
360
|
+
if (declared) {
|
|
361
|
+
current = declared;
|
|
362
|
+
} else if ((node.tagName || "") === "H2") {
|
|
360
363
|
current = readerSectionKind(node.textContent || "");
|
|
361
364
|
}
|
|
362
365
|
node.setAttribute("data-reader-section", current);
|
|
@@ -227,7 +227,7 @@ def validate(report_path: Path) -> list[str]:
|
|
|
227
227
|
if not html_path.is_file():
|
|
228
228
|
if (
|
|
229
229
|
not md_ids
|
|
230
|
-
and plan_approval_context(report_path
|
|
230
|
+
and plan_approval_context(report_path) is None
|
|
231
231
|
and review_ctx is None
|
|
232
232
|
):
|
|
233
233
|
return []
|
|
@@ -61,6 +61,10 @@ from okstra_ctl.mutation_probe import ( # noqa: E402
|
|
|
61
61
|
classify_reason,
|
|
62
62
|
)
|
|
63
63
|
from okstra_ctl.self_mock_signals import selfmock_path_key # noqa: E402
|
|
64
|
+
from okstra_ctl.report_translation import ( # noqa: E402
|
|
65
|
+
HANGUL_PROSE_LIMIT,
|
|
66
|
+
hangul_share,
|
|
67
|
+
)
|
|
64
68
|
from okstra_ctl.stage_citations import cited_stage_numbers # noqa: E402
|
|
65
69
|
from okstra_ctl.workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE # noqa: E402
|
|
66
70
|
from okstra_ctl.md_table import ( # noqa: E402
|
|
@@ -6316,6 +6320,32 @@ def _validate_improvement_discovery(
|
|
|
6316
6320
|
failures.append(f"improvement-discovery: {err}")
|
|
6317
6321
|
|
|
6318
6322
|
|
|
6323
|
+
def _validate_ssot_is_english(data: dict, failures: list[str]) -> None:
|
|
6324
|
+
"""Enforce: the final-report data.json is authored in English.
|
|
6325
|
+
|
|
6326
|
+
`meta.reportLanguage` names the language the human HTML renders in, and
|
|
6327
|
+
Phase 7's translator serves it from a sidecar. The data.json itself is the
|
|
6328
|
+
record every later phase, validator and agent reads, so a worker that
|
|
6329
|
+
authors it in the reader's language instead splits the record — and does so
|
|
6330
|
+
silently, because rendering, follow-up spawning and validation all succeed
|
|
6331
|
+
on it.
|
|
6332
|
+
|
|
6333
|
+
`report-finalize` runs the same check as its first step, before anything
|
|
6334
|
+
derives from the report. This is the second gate, for a report that reached
|
|
6335
|
+
validation by some other path.
|
|
6336
|
+
"""
|
|
6337
|
+
if not data:
|
|
6338
|
+
return
|
|
6339
|
+
share, length = hangul_share(data)
|
|
6340
|
+
if length and share >= HANGUL_PROSE_LIMIT:
|
|
6341
|
+
failures.append(
|
|
6342
|
+
f"final-report data.json was authored in Korean ({share:.0%} of its "
|
|
6343
|
+
f"prose, limit {HANGUL_PROSE_LIMIT:.0%}). The data.json is the "
|
|
6344
|
+
"English SSOT; meta.reportLanguage selects the human HTML's "
|
|
6345
|
+
"language and is served by the Phase 7 translator sidecar."
|
|
6346
|
+
)
|
|
6347
|
+
|
|
6348
|
+
|
|
6319
6349
|
def _validate_fix_cycle(run_manifest: dict, data: dict, failures: list[str]) -> None:
|
|
6320
6350
|
"""Enforce: when the run-manifest carries a fixCycleId, the final-report
|
|
6321
6351
|
data.json MUST contain a fixCycle block whose ``cycle`` matches it.
|
|
@@ -7058,42 +7088,39 @@ def _rerender_report_views_after_autofix(report_path: Path) -> str:
|
|
|
7058
7088
|
"""Re-render the ``*.html`` sibling against the just-substituted MD.
|
|
7059
7089
|
Returns a short status note for the autofix message (empty on no-op,
|
|
7060
7090
|
descriptive on failure).
|
|
7091
|
+
|
|
7092
|
+
Delegates to ``scripts/okstra-render-report-views.py`` rather than calling
|
|
7093
|
+
a renderer directly: that script is the single reference point that routes
|
|
7094
|
+
schema-v2 reports to the data.json-driven task view and schema-v1 reports
|
|
7095
|
+
to the legacy markdown renderer. Rendering here would have to re-derive
|
|
7096
|
+
both the routing and the run-meta, and getting either wrong overwrites a
|
|
7097
|
+
schema-v2 report's HTML — the user's only way to answer its clarifications
|
|
7098
|
+
— with a legacy render that carries no answer controls.
|
|
7061
7099
|
"""
|
|
7100
|
+
import subprocess
|
|
7101
|
+
|
|
7062
7102
|
if not report_path.is_file():
|
|
7063
7103
|
return ""
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
scripts_dir = Path(__file__).resolve().parent.parent / "scripts"
|
|
7068
|
-
if str(scripts_dir) not in sys.path:
|
|
7069
|
-
sys.path.insert(0, str(scripts_dir))
|
|
7070
|
-
from okstra_ctl.report_views import RunMeta, render_html_view
|
|
7071
|
-
templates_dir = (
|
|
7072
|
-
Path(__file__).resolve().parent.parent / "templates" / "reports"
|
|
7073
|
-
)
|
|
7074
|
-
css = (templates_dir / "report.css").read_text(encoding="utf-8")
|
|
7075
|
-
js = (templates_dir / "report.js").read_text(encoding="utf-8")
|
|
7076
|
-
except Exception as exc: # noqa: BLE001 — best-effort
|
|
7077
|
-
return f"report-views re-render skipped ({exc})"
|
|
7078
|
-
|
|
7079
|
-
# Infer task-key / task-type / seq from path + body when possible;
|
|
7080
|
-
# fall back to placeholders so the digest comparison (the actual
|
|
7081
|
-
# contract) still works.
|
|
7082
|
-
text = report_path.read_text(encoding="utf-8")
|
|
7083
|
-
task_type_m = re.search(r"^- Task Type:\s*(\S+)", text, re.MULTILINE)
|
|
7084
|
-
task_key_m = re.search(r"^- Task Key:\s*(\S+)", text, re.MULTILINE)
|
|
7085
|
-
seq_m = re.search(r"-(\d+)\.md$", report_path.name)
|
|
7086
|
-
meta = RunMeta(
|
|
7087
|
-
task_key=task_key_m.group(1) if task_key_m else "unknown",
|
|
7088
|
-
task_type=task_type_m.group(1) if task_type_m else "unknown",
|
|
7089
|
-
seq=seq_m.group(1) if seq_m else "000",
|
|
7090
|
-
source_report=report_path.name,
|
|
7104
|
+
renderer = (
|
|
7105
|
+
Path(__file__).resolve().parent.parent
|
|
7106
|
+
/ "scripts" / "okstra-render-report-views.py"
|
|
7091
7107
|
)
|
|
7108
|
+
if not renderer.is_file():
|
|
7109
|
+
return f"report-views re-render skipped (renderer missing under {renderer.parent})"
|
|
7092
7110
|
try:
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7111
|
+
proc = subprocess.run(
|
|
7112
|
+
[sys.executable, str(renderer), str(report_path)],
|
|
7113
|
+
capture_output=True,
|
|
7114
|
+
text=True,
|
|
7115
|
+
timeout=60,
|
|
7116
|
+
)
|
|
7117
|
+
except subprocess.TimeoutExpired:
|
|
7118
|
+
return "report-views re-render failed: renderer timed out (60s)"
|
|
7119
|
+
if proc.returncode != 0:
|
|
7120
|
+
detail = (proc.stderr or proc.stdout).strip().splitlines()
|
|
7121
|
+
return f"report-views re-render failed: {detail[-1] if detail else 'unknown error'}"
|
|
7122
|
+
emitted = (proc.stdout or "").strip().splitlines()
|
|
7123
|
+
if emitted and emitted[-1].startswith("html: skipped"):
|
|
7097
7124
|
return "report-views skipped (no §1 clarification rows)"
|
|
7098
7125
|
return "report-views re-rendered"
|
|
7099
7126
|
|
|
@@ -7209,6 +7236,7 @@ def main() -> int:
|
|
|
7209
7236
|
),
|
|
7210
7237
|
)
|
|
7211
7238
|
validation_data = report_data if isinstance(report_data, Mapping) else {}
|
|
7239
|
+
_validate_ssot_is_english(validation_data, failures)
|
|
7212
7240
|
_validate_fix_cycle(run_manifest, validation_data, failures)
|
|
7213
7241
|
validate_report(
|
|
7214
7242
|
report_path,
|
package/src/cli-registry.mjs
CHANGED
|
@@ -333,6 +333,17 @@ export const COMMAND_REGISTRY = [
|
|
|
333
333
|
category: "introspection",
|
|
334
334
|
summary: ["Render slim AI + self-contained HTML views of a final report"],
|
|
335
335
|
},
|
|
336
|
+
{
|
|
337
|
+
name: "report-translate",
|
|
338
|
+
module: "./commands/report/translate.mjs",
|
|
339
|
+
export: "run",
|
|
340
|
+
category: "introspection",
|
|
341
|
+
summary: [
|
|
342
|
+
"Build and verify the translation sidecar the HTML",
|
|
343
|
+
"renderer overlays when the report language is not",
|
|
344
|
+
"English (extract | check).",
|
|
345
|
+
],
|
|
346
|
+
},
|
|
336
347
|
{
|
|
337
348
|
name: "report-finalize",
|
|
338
349
|
module: "./commands/report/finalize.mjs",
|
|
@@ -3,15 +3,17 @@ import { runPythonModule } from "../../lib/python-helper.mjs";
|
|
|
3
3
|
const USAGE = `okstra worker-liveness — report whether pending workers are still alive
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
okstra worker-liveness [--
|
|
7
|
-
[--team-state <path> --worker <worker-id>]...
|
|
6
|
+
okstra worker-liveness [--team-state <path> --worker <worker-id>]...
|
|
8
7
|
[--max-idle <seconds>] [--launch-grace <seconds>] [--json]
|
|
9
8
|
|
|
10
|
-
--
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
--team-state and --worker select one pending worker. The worker row's
|
|
10
|
+
livenessMode picks the probe: audit-heartbeat reads the in-process
|
|
11
|
+
worker audit sidecar path and reports stalled past the heartbeat
|
|
12
|
+
cadence; wrapper-status reports did-not-launch when no prompt sibling
|
|
13
|
+
.log or .status.json exists past the launch grace. Both graces begin
|
|
14
|
+
at workers[].startedAt, never at an artifact mtime — the audit sidecar
|
|
15
|
+
is reused on re-dispatch, so the previous attempt's last heartbeat is
|
|
16
|
+
not this dispatch's signal.
|
|
15
17
|
|
|
16
18
|
Output: JSON { ok, checkedAt, probes[], unhealthy[] }. Exit 1 when any worker is
|
|
17
19
|
stalled or did not launch, so a poll loop can branch on the exit code. Read-only:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runInstalledScript } from "../../lib/python-helper.mjs";
|
|
2
|
+
|
|
3
|
+
const USAGE = `okstra report-translate — build and verify a final-report translation sidecar
|
|
4
|
+
|
|
5
|
+
A thin spawn shim over \`scripts/okstra-report-translate.py\` (installed at
|
|
6
|
+
\`$HOME/.okstra/bin/okstra-report-translate.py\`).
|
|
7
|
+
|
|
8
|
+
The final-report data.json is authored in English and stays the SSOT. When the
|
|
9
|
+
resolved report language is not English, Phase 7 dispatches a translator that
|
|
10
|
+
writes a sidecar beside the report; \`render-views\` overlays it so only the
|
|
11
|
+
human HTML is localized.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
okstra report-translate extract <path-to-final-report.data.json>
|
|
15
|
+
Write <stem>.translation-source.json — every pointer the report holds a
|
|
16
|
+
translatable string at, paired with its English text. The translator
|
|
17
|
+
fills in the values, so a sidecar cannot cite a path the report lacks.
|
|
18
|
+
|
|
19
|
+
okstra report-translate check <path-to-final-report.i18n.<lang>.json>
|
|
20
|
+
Resolve every pointer in a filled sidecar against the report it names
|
|
21
|
+
and report what is still English. Exits non-zero on a pointer that
|
|
22
|
+
resolves nowhere — the translator's own gate before it returns.
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export async function run(args) {
|
|
26
|
+
return runInstalledScript({
|
|
27
|
+
scriptName: "okstra-report-translate.py",
|
|
28
|
+
args,
|
|
29
|
+
usage: USAGE,
|
|
30
|
+
});
|
|
31
|
+
}
|