okstra 0.148.0 → 0.149.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/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-render-report-views.py +20 -0
- package/runtime/prompts/profiles/project-analysis.md +18 -0
- package/runtime/prompts/profiles/release-handoff.md +3 -0
- package/runtime/prompts/profiles/requirements-discovery.md +7 -0
- package/runtime/python/okstra_ctl/report_html/common.py +77 -47
- package/runtime/python/okstra_ctl/report_html/filters.py +125 -30
- package/runtime/python/okstra_ctl/report_html/models.py +15 -0
- package/runtime/python/okstra_ctl/report_html/render.py +49 -5
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +14 -4
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +3 -3
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +5 -3
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -7
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +5 -9
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -5
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +1 -2
- package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +90 -4
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +1 -8
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +6 -4
- package/runtime/python/okstra_ctl/report_html/visualizations.py +146 -11
- package/runtime/python/okstra_ctl/report_view_artifacts.py +5 -0
- package/runtime/python/okstra_ctl/time_report.py +2 -2
- package/runtime/python/okstra_ctl/usage_report.py +2 -2
- package/runtime/schemas/final-report-v2.0.schema.json +229 -1
- package/runtime/templates/reports/final-report.template.md +55 -0
- package/runtime/templates/reports/html/assets/base.css +64 -8
- package/runtime/templates/reports/html/base.template.html +21 -41
- package/runtime/templates/reports/html/macros/forms.html +24 -22
- package/runtime/templates/reports/html/macros/layout.html +18 -5
- package/runtime/templates/reports/html/macros/visualizations.html +7 -5
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +30 -15
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +22 -15
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +35 -15
- package/runtime/templates/reports/html/tasks/final-verification.template.html +21 -14
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +78 -19
- package/runtime/templates/reports/html/tasks/implementation.template.html +34 -16
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +11 -11
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +65 -25
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +28 -14
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +35 -15
- package/runtime/templates/reports/report.js +7 -1
- package/runtime/validators/validate_analysis_report.py +36 -0
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
{% extends "html/base.template.html" %}
|
|
2
|
-
{% from "html/macros/layout.html" import narrative as render_narrative, summary_card %}
|
|
2
|
+
{% from "html/macros/layout.html" import narrative as render_narrative, summary_card, row_key %}
|
|
3
3
|
{% from "html/macros/visualizations.html" import figure %}
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="opportunities">
|
|
7
|
-
<h2
|
|
8
|
-
{{ render_narrative(narrative.opportunityOverview, "improvementDiscovery.userNarrative.opportunityOverview"
|
|
9
|
-
<table data-report-field="improvementDiscovery.lensCoverage"><thead><tr><th
|
|
7
|
+
<h2>Improvements found</h2>
|
|
8
|
+
{{ render_narrative(narrative.opportunityOverview, "improvementDiscovery.userNarrative.opportunityOverview") }}
|
|
9
|
+
<table data-report-field="improvementDiscovery.lensCoverage"><thead><tr><th>Lens</th><th>Detail</th><th>Evidence</th></tr></thead><tbody>{% for row in improvement.lensCoverage %}<tr>{{ row_key(pairs=[("Lens", row.lens), ("Result", row.status | enum_label("lens"))]) }}<td>{{ row.rationale | inline_code }}</td><td>{{ row.evidence | evidence_refs }}</td></tr>{% endfor %}</tbody></table>
|
|
10
10
|
</section>
|
|
11
11
|
|
|
12
12
|
<section data-report-section="ranked-candidates">
|
|
13
|
-
<h2
|
|
14
|
-
{{ render_narrative(narrative.prioritizationExplanation, "improvementDiscovery.userNarrative.prioritizationExplanation"
|
|
15
|
-
<div class="summary-grid" data-report-field="improvementDiscovery.candidates">{% for row in improvement.candidates %}<article class="summary-card tone-{{ row.severity }}"><p class="eyebrow"
|
|
13
|
+
<h2>Ranked candidates</h2>
|
|
14
|
+
{{ render_narrative(narrative.prioritizationExplanation, "improvementDiscovery.userNarrative.prioritizationExplanation") }}
|
|
15
|
+
<div class="summary-grid" data-report-field="improvementDiscovery.candidates">{% for row in improvement.candidates %}<article class="summary-card tone-{{ row.severity }}" id="id-{{ row.id }}"><p class="eyebrow">#{{ loop.index }} · {{ row.id }} · Impact {{ row.severity }} · Effort {{ row.effort | enum_label("effort") }}</p><h3>{{ row.title | inline_code }}</h3><p>{{ row.expectedBehaviorAfter | inline_code }}</p><p><strong>Next step:</strong> {{ row.recommendedNextPhase }}</p><p class="evidence-refs">Evidence: {{ row.evidence | evidence_refs }}</p></article>{% else %}<p>There is no improvement candidate to pick.</p>{% endfor %}</div>
|
|
16
16
|
</section>
|
|
17
17
|
|
|
18
18
|
<section data-report-section="impact-effort">
|
|
19
|
-
<h2
|
|
19
|
+
<h2>Impact and effort</h2>
|
|
20
20
|
{{ figure(matrixFigure) }}
|
|
21
21
|
</section>
|
|
22
22
|
|
|
23
23
|
<section data-report-section="selection">
|
|
24
|
-
<h2
|
|
25
|
-
{{ render_narrative(narrative.selectionGuidance, "improvementDiscovery.userNarrative.selectionGuidance"
|
|
26
|
-
<p><strong
|
|
24
|
+
<h2>How to choose</h2>
|
|
25
|
+
{{ render_narrative(narrative.selectionGuidance, "improvementDiscovery.userNarrative.selectionGuidance") }}
|
|
26
|
+
<p><strong>Selection cap:</strong> at most {{ improvement.selectionLimit }}</p>
|
|
27
27
|
<ul>{% for row in improvement.candidates %}<li><label><input type="checkbox" name="improvement-candidate" value="{{ row.id }}"> {{ row.id }} · {{ row.title | inline_code }}</label></li>{% endfor %}</ul>
|
|
28
28
|
</section>
|
|
29
29
|
{% endblock %}
|
|
@@ -1,57 +1,97 @@
|
|
|
1
1
|
{% extends "html/base.template.html" %}
|
|
2
|
-
{% from "html/macros/layout.html" import narrative as render_narrative, action_list, summary_card %}
|
|
2
|
+
{% from "html/macros/layout.html" import narrative as render_narrative, action_list, summary_card, row_key %}
|
|
3
3
|
{% from "html/macros/visualizations.html" import figure %}
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="project-overview">
|
|
7
|
-
<h2
|
|
8
|
-
{{ render_narrative(narrative.projectOverview, "projectAnalysis.userNarrative.projectOverview"
|
|
7
|
+
<h2>Project overview</h2>
|
|
8
|
+
{{ render_narrative(narrative.projectOverview, "projectAnalysis.userNarrative.projectOverview") }}
|
|
9
9
|
<div class="summary-grid">
|
|
10
|
-
{% for reason in humanSummary.whyItMatters %}{{ summary_card("
|
|
10
|
+
{% for reason in humanSummary.whyItMatters %}{{ summary_card("", reason, "important") }}{% endfor %}
|
|
11
11
|
</div>
|
|
12
12
|
</section>
|
|
13
13
|
|
|
14
14
|
<section data-report-section="architecture" data-report-field="projectAnalysis.components">
|
|
15
|
-
<h2
|
|
16
|
-
{{ render_narrative(narrative.architectureExplanation, "projectAnalysis.userNarrative.architectureExplanation"
|
|
17
|
-
{{ figure(architectureFigure) }}
|
|
15
|
+
<h2>Architecture and dependencies</h2>
|
|
16
|
+
{{ render_narrative(narrative.architectureExplanation, "projectAnalysis.userNarrative.architectureExplanation") }}
|
|
17
|
+
{{ figure(architectureFigure, anchor_nodes=true) }}
|
|
18
18
|
</section>
|
|
19
19
|
|
|
20
|
+
<section data-report-section="infrastructure">
|
|
21
|
+
<h2>Infrastructure</h2>
|
|
22
|
+
{{ figure(infrastructureFigure) }}
|
|
23
|
+
</section>
|
|
24
|
+
|
|
25
|
+
{% if project.get("techStack") %}
|
|
26
|
+
<section data-report-section="tech-stack" data-report-field="projectAnalysis.techStack">
|
|
27
|
+
<h2>Tech stack</h2>
|
|
28
|
+
<h3>Language</h3>
|
|
29
|
+
<table><thead><tr><th>Language</th><th>Version</th><th>Version source</th><th>Files</th><th>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>Framework</h3>
|
|
31
|
+
<table><thead><tr><th>Framework</th><th>Role</th><th>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>Tool</h3>
|
|
33
|
+
<table><thead><tr><th>Tool</th><th>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
|
+
</section>
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
37
|
+
{% if project.get("internalInterfaces") %}
|
|
38
|
+
<section data-report-section="internal-interfaces" data-report-field="projectAnalysis.internalInterfaces">
|
|
39
|
+
<h2>Internal interfaces</h2>
|
|
40
|
+
<table><thead><tr><th>Interface</th><th>Signature</th><th>Consumers</th><th>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
|
+
</section>
|
|
42
|
+
{% endif %}
|
|
43
|
+
|
|
44
|
+
{% if project.get("workflows") %}
|
|
45
|
+
<section data-report-section="workflows" data-report-field="projectAnalysis.workflows">
|
|
46
|
+
<h2>Workflows</h2>
|
|
47
|
+
{{ figure(workflowFigure) }}
|
|
48
|
+
{% for flow in project.workflows %}
|
|
49
|
+
<article class="workflow" id="id-{{ flow.id }}">
|
|
50
|
+
<p class="eyebrow">{{ flow.id }}</p>
|
|
51
|
+
<h3>{{ flow.name | inline_code }}</h3>
|
|
52
|
+
<p><strong>Starts with</strong> — {{ flow.trigger | inline_code }}{% if flow.get("outcome") %} · <strong>Result</strong> — {{ flow.outcome | inline_code }}{% endif %}</p>
|
|
53
|
+
<table><thead><tr><th>Order</th><th>Action</th><th>Component</th><th>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
|
+
</article>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</section>
|
|
57
|
+
{% endif %}
|
|
58
|
+
|
|
20
59
|
<section data-report-section="boundaries">
|
|
21
|
-
<h2
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
</
|
|
25
|
-
<h3
|
|
26
|
-
<
|
|
27
|
-
<h3
|
|
28
|
-
<
|
|
60
|
+
<h2>System boundaries</h2>
|
|
61
|
+
<p class="scan-coverage" data-report-field="projectAnalysis.scanCoverage"><strong>{{ project.scanCoverage.scannedPathCount }}</strong> paths read · <strong>{{ project.scanCoverage.unscannedPathCount }}</strong> left unread — {{ project.scanCoverage.reason | inline_code }}</p>
|
|
62
|
+
<h3>Entry point</h3>
|
|
63
|
+
<table data-report-field="projectAnalysis.entryPoints"><thead><tr><th>Entry point</th><th>Path</th><th>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>Data stores</h3>
|
|
65
|
+
<table data-report-field="projectAnalysis.dataStores"><thead><tr><th>Store</th><th>Reads</th><th>Writes</th><th>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>External system</h3>
|
|
67
|
+
<table data-report-field="projectAnalysis.externalSystems"><thead><tr><th>External system</th><th>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>
|
|
29
68
|
</section>
|
|
30
69
|
|
|
31
70
|
<section data-report-section="hotspots" data-report-field="projectAnalysis.rankedHotspots">
|
|
32
|
-
<h2
|
|
33
|
-
{{ render_narrative(narrative.hotspotExplanation, "projectAnalysis.userNarrative.hotspotExplanation"
|
|
71
|
+
<h2>Hotspots to look at first</h2>
|
|
72
|
+
{{ render_narrative(narrative.hotspotExplanation, "projectAnalysis.userNarrative.hotspotExplanation") }}
|
|
34
73
|
<div class="summary-grid">
|
|
35
74
|
{% for row in project.rankedHotspots %}
|
|
36
|
-
<article class="summary-card tone-{{ row.risk }}"><p class="eyebrow">{{ row.id }} ·
|
|
75
|
+
<article class="summary-card tone-{{ row.risk }}" id="id-{{ row.id }}"><p class="eyebrow">{{ row.id }} · Impact {{ row.impact }} · Risk {{ row.risk }}</p><h3>{{ row.title | inline_code }}</h3><p>{{ row.reason | inline_code }}</p><p class="evidence-refs">Evidence: {{ row.evidenceRefs | evidence_refs }}</p></article>
|
|
37
76
|
{% endfor %}
|
|
38
77
|
</div>
|
|
39
78
|
</section>
|
|
40
79
|
|
|
41
80
|
<section data-report-section="quality-and-tests" data-report-field="projectAnalysis.qualityCoverage">
|
|
42
|
-
<h2
|
|
43
|
-
{{ render_narrative(narrative.qualityExplanation, "projectAnalysis.userNarrative.qualityExplanation"
|
|
44
|
-
<table><thead><tr><th
|
|
81
|
+
<h2>Quality and test coverage</h2>
|
|
82
|
+
{{ render_narrative(narrative.qualityExplanation, "projectAnalysis.userNarrative.qualityExplanation") }}
|
|
83
|
+
<table><thead><tr><th>Area</th><th>Detail</th><th>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
|
+
<dl class="enum-legend">{% for label, hint in "coverage" | enum_legend %}<dt>{{ label }}</dt><dd>{{ hint }}</dd>{% endfor %}</dl>
|
|
45
85
|
</section>
|
|
46
86
|
|
|
47
87
|
<section data-report-section="feature-map" data-report-field="projectAnalysis.featureIndex">
|
|
48
|
-
<h2
|
|
49
|
-
<div class="summary-grid">{% for row in project.featureIndex %}{{ summary_card(row.id ~ " · " ~ (row.name | inline_code), row.description) }}{% endfor %}</div>
|
|
88
|
+
<h2>Feature map</h2>
|
|
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>
|
|
50
90
|
</section>
|
|
51
91
|
|
|
52
92
|
<section data-report-section="user-actions">
|
|
53
|
-
<h2
|
|
93
|
+
<h2>What to do next</h2>
|
|
54
94
|
{{ action_list(humanSummary.actions) }}
|
|
55
|
-
{% if humanSummary.blockers %}<h3
|
|
95
|
+
{% if humanSummary.blockers %}<h3>Blockers</h3><ul>{% for item in humanSummary.blockers %}<li>{{ item | inline_code }}</li>{% endfor %}</ul>{% endif %}
|
|
56
96
|
</section>
|
|
57
97
|
{% endblock %}
|
|
@@ -1,36 +1,50 @@
|
|
|
1
1
|
{% extends "html/base.template.html" %}
|
|
2
|
-
{% from "html/macros/layout.html" import narrative as render_narrative, summary_card %}
|
|
2
|
+
{% from "html/macros/layout.html" import narrative as render_narrative, summary_card, row_key %}
|
|
3
3
|
{% from "html/macros/visualizations.html" import figure %}
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="handoff-outcome">
|
|
7
|
-
<h2
|
|
8
|
-
{{ render_narrative(narrative.handoffOutcome, "releaseHandoff.userNarrative.handoffOutcome"
|
|
7
|
+
<h2>Handoff outcome</h2>
|
|
8
|
+
{{ render_narrative(narrative.handoffOutcome, "releaseHandoff.userNarrative.handoffOutcome") }}
|
|
9
9
|
{{ figure(handoffFigure) }}
|
|
10
10
|
</section>
|
|
11
11
|
|
|
12
|
+
{% if handoff.get("handoffScope") %}
|
|
13
|
+
<section data-report-section="handoff-scope" data-report-field="releaseHandoff.handoffScope">
|
|
14
|
+
<h2>Handoff scope</h2>
|
|
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
|
+
</section>
|
|
17
|
+
{% endif %}
|
|
18
|
+
|
|
12
19
|
<section data-report-section="user-selection" data-report-field="releaseHandoff.userSelections">
|
|
13
|
-
<h2
|
|
14
|
-
<dl><dt
|
|
20
|
+
<h2>The behaviour you chose</h2>
|
|
21
|
+
<dl><dt>Handoff mode</dt><dd>{{ handoff.userSelections.h1 }}</dd><dt>Conflict check</dt><dd>{{ handoff.userSelections.h2b }}</dd><dt>PR Body</dt><dd>{{ handoff.userSelections.h3 }}</dd></dl>
|
|
15
22
|
</section>
|
|
16
23
|
|
|
17
24
|
<section data-report-section="branch-and-pr" data-report-field="releaseHandoff.featureBranchState">
|
|
18
|
-
<h2
|
|
19
|
-
{{ render_narrative(narrative.branchAndPrExplanation, "releaseHandoff.userNarrative.branchAndPrExplanation"
|
|
20
|
-
<p><strong
|
|
21
|
-
<p data-report-field="releaseHandoff.pullRequestOutcome"><strong>PR
|
|
25
|
+
<h2>Branch and PR</h2>
|
|
26
|
+
{{ render_narrative(narrative.branchAndPrExplanation, "releaseHandoff.userNarrative.branchAndPrExplanation") }}
|
|
27
|
+
<p><strong>Branch:</strong> {{ handoff.featureBranchState.branchName }}</p>
|
|
28
|
+
<p data-report-field="releaseHandoff.pullRequestOutcome"><strong>PR Result:</strong> {{ handoff.pullRequestOutcome.kind }}{% if prUrl %} · <a href="{{ prUrl }}" rel="noopener noreferrer">Pull request Open</a>{% endif %}</p>
|
|
22
29
|
</section>
|
|
23
30
|
|
|
24
31
|
<section data-report-section="conflicts" data-report-field="releaseHandoff.mergeConflictProbe">
|
|
25
|
-
<h2
|
|
26
|
-
{{ render_narrative(narrative.conflictExplanation, "releaseHandoff.userNarrative.conflictExplanation"
|
|
27
|
-
{{ summary_card(handoff.mergeConflictProbe.kind, "
|
|
32
|
+
<h2>Conflict verdict</h2>
|
|
33
|
+
{{ render_narrative(narrative.conflictExplanation, "releaseHandoff.userNarrative.conflictExplanation") }}
|
|
34
|
+
{{ summary_card(handoff.mergeConflictProbe.kind, "Base branch: " ~ (handoff.mergeConflictProbe.baseBranch | default('not-run'))) }}
|
|
28
35
|
{% if handoff.mergeConflictProbe.conflictingPaths %}<ul>{% for path in handoff.mergeConflictProbe.conflictingPaths %}<li>{{ path }}</li>{% endfor %}</ul>{% endif %}
|
|
29
36
|
</section>
|
|
30
37
|
|
|
38
|
+
<section data-report-section="delivered-commits" data-report-field="releaseHandoff.commitList">
|
|
39
|
+
<h2>Commits delivered</h2>
|
|
40
|
+
{% if handoff.commitList is mapping %}<p>There are no commits.</p>{% else %}
|
|
41
|
+
<table><thead><tr><th>Commit</th><th>Subject</th><th>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>The verification report this rests on</strong> — <code>{{ handoff.sourceVerificationReport.path }}</code> · {{ handoff.sourceVerificationReport.verdictTokenQuote | inline_code }}</p>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
31
45
|
<section data-report-section="next-action">
|
|
32
|
-
<h2
|
|
33
|
-
{{ render_narrative(narrative.nextAction, "releaseHandoff.userNarrative.nextAction"
|
|
46
|
+
<h2>What to do next</h2>
|
|
47
|
+
{{ render_narrative(narrative.nextAction, "releaseHandoff.userNarrative.nextAction") }}
|
|
34
48
|
<p>{{ handoff.routingRecommendation | inline_code }}</p>
|
|
35
49
|
</section>
|
|
36
50
|
{% endblock %}
|
|
@@ -4,34 +4,54 @@
|
|
|
4
4
|
|
|
5
5
|
{% block human_content %}
|
|
6
6
|
<section data-report-section="request">
|
|
7
|
-
<h2
|
|
8
|
-
{{ render_narrative(narrative.requestUnderstanding, "requirementsDiscovery.userNarrative.requestUnderstanding"
|
|
9
|
-
<h3
|
|
7
|
+
<h2>The request as understood</h2>
|
|
8
|
+
{{ render_narrative(narrative.requestUnderstanding, "requirementsDiscovery.userNarrative.requestUnderstanding") }}
|
|
9
|
+
<h3>Your request, verbatim</h3>
|
|
10
10
|
<p data-report-field="requirementsDiscovery.requestVerbatim">{{ requirements.requestVerbatim | inline_code }}</p>
|
|
11
|
-
<h3
|
|
11
|
+
<h3>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
|
+
{% if requirements.get("rejectionCriteria") %}
|
|
17
|
+
<section data-report-section="rejection-criteria" data-report-field="requirementsDiscovery.rejectionCriteria">
|
|
18
|
+
<h2>What would prove this wrong</h2>
|
|
19
|
+
{% if requirements.rejectionCriteria.source == "absent-not-material" %}
|
|
20
|
+
<p>The requester left no rejection criteria, even if there had been, neither the classification nor the next step would change.</p>
|
|
21
|
+
{% else %}
|
|
22
|
+
<p>{{ requirements.rejectionCriteria.get("statement") | inline_code }}</p>
|
|
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>
|
|
24
|
+
{% endif %}
|
|
25
|
+
</section>
|
|
26
|
+
{% endif %}
|
|
27
|
+
|
|
28
|
+
{% if requirements.get("domainAlignment") %}
|
|
29
|
+
<section data-report-section="domain-alignment" data-report-field="requirementsDiscovery.domainAlignment">
|
|
30
|
+
<h2>Terms settled</h2>
|
|
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>As the brief put it</th><th>Settled term</th><th>Settled by</th><th>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">No term was ambiguous.</td></tr>{% endfor %}</tbody></table>
|
|
33
|
+
</section>
|
|
34
|
+
{% endif %}
|
|
35
|
+
|
|
16
36
|
<section data-report-section="requirements">
|
|
17
|
-
<h2
|
|
18
|
-
{{ render_narrative(narrative.confirmedRequirements, "requirementsDiscovery.userNarrative.confirmedRequirements"
|
|
19
|
-
<div class="summary-grid" data-report-field="requirementsDiscovery.confirmedRequirements">{% for row in requirements.confirmedRequirements %}{{ summary_card(row.id ~ " ·
|
|
20
|
-
{{ render_narrative(narrative.unresolvedRequirements, "requirementsDiscovery.userNarrative.unresolvedRequirements"
|
|
21
|
-
<div class="summary-grid" data-report-field="requirementsDiscovery.unresolvedRequirements">{% for row in requirements.unresolvedRequirements %}{{ summary_card(row.id ~ " ·
|
|
37
|
+
<h2>Settled and unsettled requirements</h2>
|
|
38
|
+
{{ render_narrative(narrative.confirmedRequirements, "requirementsDiscovery.userNarrative.confirmedRequirements") }}
|
|
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
|
+
{{ 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>Nothing is left unsettled.</p>{% endfor %}</div>
|
|
22
42
|
</section>
|
|
23
43
|
|
|
24
44
|
<section data-report-section="routing">
|
|
25
|
-
<h2
|
|
26
|
-
{{ render_narrative(narrative.routingExplanation, "requirementsDiscovery.userNarrative.routingExplanation"
|
|
27
|
-
<p><strong
|
|
45
|
+
<h2>Next tasks and dependencies</h2>
|
|
46
|
+
{{ render_narrative(narrative.routingExplanation, "requirementsDiscovery.userNarrative.routingExplanation") }}
|
|
47
|
+
<p><strong>Next task:</strong> {{ requirements.routing.nextTaskType }} · {{ requirements.routing.rationale | inline_code }}</p>
|
|
28
48
|
{{ figure(routingFigure) }}
|
|
29
|
-
{% if not requirements.taskDependencies %}<p class="empty-state"
|
|
49
|
+
{% if not requirements.taskDependencies %}<p class="empty-state">This requirements task stands alone; nothing else feeds it.</p>{% endif %}
|
|
30
50
|
</section>
|
|
31
51
|
|
|
32
52
|
<section data-report-section="decisions">
|
|
33
|
-
<h2
|
|
34
|
-
<div class="summary-grid">{% for row in clarificationItems %}{{ summary_card(row.id ~ " · " ~ row.kind, row.statement, "important") }}{% else %}<p
|
|
53
|
+
<h2>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>No further decision is needed.</p>{% endfor %}</div>
|
|
35
55
|
{{ action_list(humanSummary.actions) }}
|
|
36
56
|
</section>
|
|
37
57
|
{% endblock %}
|
|
@@ -74,7 +74,13 @@
|
|
|
74
74
|
|
|
75
75
|
function collectEntries() {
|
|
76
76
|
var entries = [];
|
|
77
|
-
|
|
77
|
+
// `tr` is the legacy Markdown-derived view; `article` is the v2 task
|
|
78
|
+
// template's card. Both container shapes carry the id and kind, and an
|
|
79
|
+
// allowlist of the two keeps the inner widgets (which repeat
|
|
80
|
+
// `data-response-id`) from matching as containers themselves.
|
|
81
|
+
var rows = document.querySelectorAll(
|
|
82
|
+
"tr[data-response-id], article[data-response-id]"
|
|
83
|
+
);
|
|
78
84
|
for (var i = 0; i < rows.length; i++) {
|
|
79
85
|
var row = rows[i];
|
|
80
86
|
var value = readRowValue(row);
|
|
@@ -213,6 +213,42 @@ def _validate_project_semantics(data: dict, errors: list[str]) -> None:
|
|
|
213
213
|
f"projectAnalysis.featureIndex `{row.get('id') or '?'}` representative "
|
|
214
214
|
"entry point must be inside analysisCommon.scope.includedPaths"
|
|
215
215
|
)
|
|
216
|
+
_validate_component_references(analysis, errors)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _validate_component_references(analysis: dict, errors: list[str]) -> None:
|
|
220
|
+
"""Every componentId a row names has to be a component this report declares.
|
|
221
|
+
|
|
222
|
+
The reader clicks these — they render as links to the component's row — so
|
|
223
|
+
a reference to a component that was never listed is a dead anchor, and one
|
|
224
|
+
to a component that exists under a different id is worse: it reads as a
|
|
225
|
+
relationship the analysis never found.
|
|
226
|
+
"""
|
|
227
|
+
declared = {
|
|
228
|
+
row.get("id")
|
|
229
|
+
for row in analysis.get("components") or []
|
|
230
|
+
if isinstance(row, dict) and row.get("id")
|
|
231
|
+
}
|
|
232
|
+
if not declared:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
def check(component_id: object, where: str) -> None:
|
|
236
|
+
if isinstance(component_id, str) and component_id and component_id not in declared:
|
|
237
|
+
errors.append(f"{where} names component `{component_id}`, which is not declared")
|
|
238
|
+
|
|
239
|
+
for row in analysis.get("internalInterfaces") or []:
|
|
240
|
+
if not isinstance(row, dict):
|
|
241
|
+
continue
|
|
242
|
+
where = f"projectAnalysis.internalInterfaces `{row.get('id') or '?'}`"
|
|
243
|
+
check(row.get("ownerComponentId"), where)
|
|
244
|
+
for consumer in row.get("consumers") or []:
|
|
245
|
+
check(consumer, where)
|
|
246
|
+
for row in analysis.get("workflows") or []:
|
|
247
|
+
if not isinstance(row, dict):
|
|
248
|
+
continue
|
|
249
|
+
for step in row.get("steps") or []:
|
|
250
|
+
if isinstance(step, dict):
|
|
251
|
+
check(step.get("componentId"), f"projectAnalysis.workflows `{row.get('id') or '?'}`")
|
|
216
252
|
|
|
217
253
|
|
|
218
254
|
def _validate_feature_semantics(data: dict, errors: list[str]) -> None:
|