okstra 0.149.0 → 0.150.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 +1 -1
- package/docs/project-structure-overview.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- 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 +158 -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/report-writer.md +14 -13
- 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/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_html/filters.py +15 -77
- package/runtime/python/okstra_ctl/report_html/render.py +44 -2
- package/runtime/python/okstra_ctl/report_translation.py +440 -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 +28 -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 []
|
|
@@ -7058,42 +7058,39 @@ def _rerender_report_views_after_autofix(report_path: Path) -> str:
|
|
|
7058
7058
|
"""Re-render the ``*.html`` sibling against the just-substituted MD.
|
|
7059
7059
|
Returns a short status note for the autofix message (empty on no-op,
|
|
7060
7060
|
descriptive on failure).
|
|
7061
|
+
|
|
7062
|
+
Delegates to ``scripts/okstra-render-report-views.py`` rather than calling
|
|
7063
|
+
a renderer directly: that script is the single reference point that routes
|
|
7064
|
+
schema-v2 reports to the data.json-driven task view and schema-v1 reports
|
|
7065
|
+
to the legacy markdown renderer. Rendering here would have to re-derive
|
|
7066
|
+
both the routing and the run-meta, and getting either wrong overwrites a
|
|
7067
|
+
schema-v2 report's HTML — the user's only way to answer its clarifications
|
|
7068
|
+
— with a legacy render that carries no answer controls.
|
|
7061
7069
|
"""
|
|
7070
|
+
import subprocess
|
|
7071
|
+
|
|
7062
7072
|
if not report_path.is_file():
|
|
7063
7073
|
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,
|
|
7074
|
+
renderer = (
|
|
7075
|
+
Path(__file__).resolve().parent.parent
|
|
7076
|
+
/ "scripts" / "okstra-render-report-views.py"
|
|
7091
7077
|
)
|
|
7078
|
+
if not renderer.is_file():
|
|
7079
|
+
return f"report-views re-render skipped (renderer missing under {renderer.parent})"
|
|
7092
7080
|
try:
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7081
|
+
proc = subprocess.run(
|
|
7082
|
+
[sys.executable, str(renderer), str(report_path)],
|
|
7083
|
+
capture_output=True,
|
|
7084
|
+
text=True,
|
|
7085
|
+
timeout=60,
|
|
7086
|
+
)
|
|
7087
|
+
except subprocess.TimeoutExpired:
|
|
7088
|
+
return "report-views re-render failed: renderer timed out (60s)"
|
|
7089
|
+
if proc.returncode != 0:
|
|
7090
|
+
detail = (proc.stderr or proc.stdout).strip().splitlines()
|
|
7091
|
+
return f"report-views re-render failed: {detail[-1] if detail else 'unknown error'}"
|
|
7092
|
+
emitted = (proc.stdout or "").strip().splitlines()
|
|
7093
|
+
if emitted and emitted[-1].startswith("html: skipped"):
|
|
7097
7094
|
return "report-views skipped (no §1 clarification rows)"
|
|
7098
7095
|
return "report-views re-rendered"
|
|
7099
7096
|
|
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
|
+
}
|