okstra 0.148.1 → 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.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/runtime/BUILD.json +2 -2
  3. package/runtime/bin/okstra-render-report-views.py +20 -0
  4. package/runtime/prompts/profiles/project-analysis.md +18 -0
  5. package/runtime/prompts/profiles/release-handoff.md +3 -0
  6. package/runtime/prompts/profiles/requirements-discovery.md +7 -0
  7. package/runtime/python/okstra_ctl/report_html/common.py +77 -47
  8. package/runtime/python/okstra_ctl/report_html/filters.py +125 -30
  9. package/runtime/python/okstra_ctl/report_html/models.py +15 -0
  10. package/runtime/python/okstra_ctl/report_html/render.py +44 -5
  11. package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +14 -4
  12. package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +3 -3
  13. package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +5 -3
  14. package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -7
  15. package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +5 -9
  16. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -5
  17. package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +1 -2
  18. package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +90 -4
  19. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +1 -8
  20. package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +6 -4
  21. package/runtime/python/okstra_ctl/report_html/visualizations.py +146 -11
  22. package/runtime/python/okstra_ctl/report_view_artifacts.py +5 -0
  23. package/runtime/python/okstra_ctl/time_report.py +2 -2
  24. package/runtime/python/okstra_ctl/usage_report.py +2 -2
  25. package/runtime/schemas/final-report-v2.0.schema.json +229 -1
  26. package/runtime/templates/reports/final-report.template.md +55 -0
  27. package/runtime/templates/reports/html/assets/base.css +59 -9
  28. package/runtime/templates/reports/html/base.template.html +21 -42
  29. package/runtime/templates/reports/html/macros/forms.html +20 -20
  30. package/runtime/templates/reports/html/macros/layout.html +18 -5
  31. package/runtime/templates/reports/html/macros/visualizations.html +7 -5
  32. package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +30 -15
  33. package/runtime/templates/reports/html/tasks/error-analysis.template.html +22 -15
  34. package/runtime/templates/reports/html/tasks/feature-analysis.template.html +35 -15
  35. package/runtime/templates/reports/html/tasks/final-verification.template.html +21 -14
  36. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +78 -19
  37. package/runtime/templates/reports/html/tasks/implementation.template.html +34 -16
  38. package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +11 -11
  39. package/runtime/templates/reports/html/tasks/project-analysis.template.html +65 -25
  40. package/runtime/templates/reports/html/tasks/release-handoff.template.html +28 -14
  41. package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +35 -15
  42. package/runtime/validators/validate_analysis_report.py +36 -0
@@ -515,6 +515,17 @@
515
515
  ],
516
516
  "additionalProperties": false,
517
517
  "properties": {
518
+ "handoffScope": {
519
+ "type": "object",
520
+ "required": ["mode"],
521
+ "additionalProperties": false,
522
+ "description": "Which shape of handoff this was. 'whole-task' turns the verified task branch into one PR; 'stage-group' merges a user-selected subset of verified stages into a collector branch and opens the PR from that. Without it a reader cannot tell one report from the other, and in stage-group mode cannot tell which stages shipped — the branch name alone does not say.",
523
+ "properties": {
524
+ "mode": { "enum": ["whole-task", "stage-group"] },
525
+ "stages": { "type": "array", "items": { "type": "integer", "minimum": 1 } },
526
+ "collectorBranch": { "type": "string" }
527
+ }
528
+ },
518
529
  "sourceVerificationReport": {
519
530
  "type": "object",
520
531
  "required": ["path", "verdictTokenQuote"],
@@ -1400,6 +1411,57 @@
1400
1411
  }
1401
1412
  },
1402
1413
 
1414
+ "RequirementsRejectionCriteria": {
1415
+ "type": "object",
1416
+ "required": ["source", "statement"],
1417
+ "additionalProperties": false,
1418
+ "description": "The delivered outcome that would make this work wrong — the reporter's own line, never inferred. It decides classification and routing: work that must not change existing behaviour routes differently from work that may. `source` says where the line came from, because a line the reporter wrote and a line raised as a question carry different authority. 'absent-not-material' is the one case with no statement to quote: the criteria were missing and would not have changed the routing.",
1419
+ "properties": {
1420
+ "source": {
1421
+ "enum": [
1422
+ "brief-desired-outcome",
1423
+ "brief-out-of-scope",
1424
+ "brief-source-material",
1425
+ "clarification-raised",
1426
+ "absent-not-material"
1427
+ ]
1428
+ },
1429
+ "statement": { "type": "string" },
1430
+ "clarificationId": { "type": "string", "pattern": "^C-\\d+$" },
1431
+ "evidenceRefs": { "type": "array", "items": { "type": "string", "minLength": 1 } }
1432
+ }
1433
+ },
1434
+
1435
+ "RequirementsDomainAlignment": {
1436
+ "type": "object",
1437
+ "required": ["glossaryConsulted", "decisionsConsulted", "terms"],
1438
+ "additionalProperties": false,
1439
+ "description": "The terminology this phase settled before routing. A term that means two things in the brief means two things in every later phase, so each one resolves to a single canonical form here, with the source that decided it.",
1440
+ "properties": {
1441
+ "glossaryConsulted": { "type": "boolean" },
1442
+ "decisionsConsulted": { "type": "boolean" },
1443
+ "terms": {
1444
+ "type": "array",
1445
+ "items": {
1446
+ "type": "object",
1447
+ "required": ["raw", "canonical", "resolvedBy"],
1448
+ "additionalProperties": false,
1449
+ "properties": {
1450
+ "raw": { "type": "string", "minLength": 1 },
1451
+ "canonical": { "type": "string", "minLength": 1 },
1452
+ "resolvedBy": {
1453
+ "enum": ["glossary", "decision-record", "brief", "clarification"]
1454
+ },
1455
+ "evidenceRefs": {
1456
+ "type": "array",
1457
+ "items": { "type": "string", "minLength": 1 }
1458
+ }
1459
+ }
1460
+ }
1461
+ }
1462
+ }
1463
+ },
1464
+
1403
1465
  "RequirementsDiscovery": {
1404
1466
  "type": "object",
1405
1467
  "required": ["requestVerbatim", "systemInterpretation", "classification", "confirmedRequirements", "unresolvedRequirements", "routing", "taskDependencies", "userNarrative"],
@@ -1407,6 +1469,8 @@
1407
1469
  "properties": {
1408
1470
  "requestVerbatim": { "type": "string", "minLength": 1 },
1409
1471
  "systemInterpretation": { "type": "string", "minLength": 1 },
1472
+ "rejectionCriteria": { "$ref": "#/$defs/RequirementsRejectionCriteria" },
1473
+ "domainAlignment": { "$ref": "#/$defs/RequirementsDomainAlignment" },
1410
1474
  "classification": {
1411
1475
  "type": "object",
1412
1476
  "required": ["requestKind", "scope", "complexity"],
@@ -1984,6 +2048,158 @@
1984
2048
  }
1985
2049
  },
1986
2050
 
2051
+ "ProjectLanguage": {
2052
+ "type": "object",
2053
+ "required": ["name", "version", "versionSource"],
2054
+ "additionalProperties": false,
2055
+ "properties": {
2056
+ "name": { "type": "string", "minLength": 1 },
2057
+ "version": { "type": "string", "minLength": 1 },
2058
+ "versionSource": { "$ref": "#/$defs/ProjectVersionSource" },
2059
+ "fileCount": { "type": "integer", "minimum": 0 },
2060
+ "lineCount": { "type": "integer", "minimum": 0 },
2061
+ "currentCodeEvidence": {
2062
+ "type": "array",
2063
+ "items": { "$ref": "#/$defs/CurrentCodeEvidence" }
2064
+ }
2065
+ }
2066
+ },
2067
+
2068
+ "ProjectVersionSource": {
2069
+ "enum": [
2070
+ "manifest-range",
2071
+ "lockfile-pinned",
2072
+ "container-image",
2073
+ "runtime-config",
2074
+ "declared-doc"
2075
+ ],
2076
+ "description": "Where the version was read, which decides how much it can be trusted. 'manifest-range' — a range in a manifest (`^1.17.0`), so the installed version is not this one. 'lockfile-pinned' — the exact version a lockfile resolves to. 'container-image' — the tag an image is built from. 'runtime-config' — a value the running service reports or a config fixes. 'declared-doc' — prose in a document, with nothing enforcing it. A range and a pin must never be recorded the same way: a mismatch between them is a real compatibility risk."
2077
+ },
2078
+
2079
+ "ProjectFramework": {
2080
+ "type": "object",
2081
+ "required": ["name", "version", "versionSource", "role"],
2082
+ "additionalProperties": false,
2083
+ "properties": {
2084
+ "name": { "type": "string", "minLength": 1 },
2085
+ "version": { "type": "string", "minLength": 1 },
2086
+ "versionSource": { "$ref": "#/$defs/ProjectVersionSource" },
2087
+ "role": { "type": "string", "minLength": 1 },
2088
+ "currentCodeEvidence": {
2089
+ "type": "array",
2090
+ "items": { "$ref": "#/$defs/CurrentCodeEvidence" }
2091
+ }
2092
+ }
2093
+ },
2094
+
2095
+ "ProjectTool": {
2096
+ "type": "object",
2097
+ "required": ["kind", "name"],
2098
+ "additionalProperties": false,
2099
+ "properties": {
2100
+ "kind": {
2101
+ "enum": [
2102
+ "packageManager",
2103
+ "build",
2104
+ "test",
2105
+ "lint",
2106
+ "typecheck",
2107
+ "format",
2108
+ "ci",
2109
+ "container"
2110
+ ]
2111
+ },
2112
+ "name": { "type": "string", "minLength": 1 },
2113
+ "version": { "type": "string" },
2114
+ "versionSource": { "$ref": "#/$defs/ProjectVersionSource" },
2115
+ "command": { "type": "string" },
2116
+ "currentCodeEvidence": {
2117
+ "type": "array",
2118
+ "items": { "$ref": "#/$defs/CurrentCodeEvidence" }
2119
+ }
2120
+ }
2121
+ },
2122
+
2123
+ "ProjectTechStack": {
2124
+ "type": "object",
2125
+ "required": ["languages"],
2126
+ "additionalProperties": false,
2127
+ "description": "What the project is written in and built with. Record only what a file states — a language with no version in any manifest, lockfile or image is still recorded, with the version left to the source that does declare it.",
2128
+ "properties": {
2129
+ "languages": {
2130
+ "type": "array",
2131
+ "minItems": 1,
2132
+ "items": { "$ref": "#/$defs/ProjectLanguage" }
2133
+ },
2134
+ "frameworks": {
2135
+ "type": "array",
2136
+ "items": { "$ref": "#/$defs/ProjectFramework" }
2137
+ },
2138
+ "toolchain": {
2139
+ "type": "array",
2140
+ "items": { "$ref": "#/$defs/ProjectTool" }
2141
+ }
2142
+ }
2143
+ },
2144
+
2145
+ "ProjectInternalInterface": {
2146
+ "type": "object",
2147
+ "required": ["id", "name", "kind", "ownerComponentId", "signature"],
2148
+ "additionalProperties": false,
2149
+ "description": "A seam inside the project that other components call across. `kind` is what the seam is, not where its file sits: a `port` is the abstraction a domain declares, an `adapter` the implementation that satisfies one.",
2150
+ "properties": {
2151
+ "id": { "type": "string", "pattern": "^II-[0-9]{3}$" },
2152
+ "name": { "type": "string", "minLength": 1 },
2153
+ "kind": {
2154
+ "enum": ["port", "adapter", "service-api", "module-export", "event"]
2155
+ },
2156
+ "ownerComponentId": { "type": "string", "minLength": 1 },
2157
+ "signature": { "type": "string", "minLength": 1 },
2158
+ "consumers": {
2159
+ "type": "array",
2160
+ "items": { "type": "string", "minLength": 1 }
2161
+ },
2162
+ "currentCodeEvidence": {
2163
+ "type": "array",
2164
+ "items": { "$ref": "#/$defs/CurrentCodeEvidence" }
2165
+ }
2166
+ }
2167
+ },
2168
+
2169
+ "ProjectWorkflowStep": {
2170
+ "type": "object",
2171
+ "required": ["order", "action", "componentId"],
2172
+ "additionalProperties": false,
2173
+ "properties": {
2174
+ "order": { "type": "integer", "minimum": 1 },
2175
+ "action": { "type": "string", "minLength": 1 },
2176
+ "componentId": { "type": "string", "minLength": 1 },
2177
+ "externalSystem": { "type": "string" },
2178
+ "currentCodeEvidence": {
2179
+ "type": "array",
2180
+ "items": { "$ref": "#/$defs/CurrentCodeEvidence" }
2181
+ }
2182
+ }
2183
+ },
2184
+
2185
+ "ProjectWorkflow": {
2186
+ "type": "object",
2187
+ "required": ["id", "name", "trigger", "steps"],
2188
+ "additionalProperties": false,
2189
+ "description": "One run of work through the system, start to finish — what sets it off and which component does what, in order. This is the only block that says how the parts are used rather than how they are arranged.",
2190
+ "properties": {
2191
+ "id": { "type": "string", "pattern": "^PW-[0-9]{3}$" },
2192
+ "name": { "type": "string", "minLength": 1 },
2193
+ "trigger": { "type": "string", "minLength": 1 },
2194
+ "outcome": { "type": "string" },
2195
+ "steps": {
2196
+ "type": "array",
2197
+ "minItems": 2,
2198
+ "items": { "$ref": "#/$defs/ProjectWorkflowStep" }
2199
+ }
2200
+ }
2201
+ },
2202
+
1987
2203
  "ProjectHotspot": {
1988
2204
  "type": "object",
1989
2205
  "required": ["id", "title", "impact", "risk", "reason", "evidenceRefs"],
@@ -2005,7 +2221,10 @@
2005
2221
  "properties": {
2006
2222
  "id": { "type": "string", "pattern": "^PQ-[0-9]{3}$" },
2007
2223
  "area": { "type": "string", "minLength": 1 },
2008
- "status": { "enum": ["covered", "partial", "gap", "risk"] },
2224
+ "status": {
2225
+ "enum": ["covered", "partial", "gap", "risk"],
2226
+ "description": "How far the area's tests actually protect it. 'covered' — tests exercise the area and a regression fails them. 'partial' — tests exist but leave part of the area unexercised. 'gap' — no test exercises the area. 'risk' — tests exist and run, but passing them does not prove the area works (they skip, assert nothing, or gate on a condition that is never met). 'risk' is not a weaker 'gap': it is the more dangerous state, because the suite reports success."
2227
+ },
2009
2228
  "summary": { "type": "string", "minLength": 1 },
2010
2229
  "evidenceRefs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
2011
2230
  }
@@ -2035,6 +2254,15 @@
2035
2254
  "type": "array",
2036
2255
  "items": { "$ref": "#/$defs/ProjectDependency" }
2037
2256
  },
2257
+ "techStack": { "$ref": "#/$defs/ProjectTechStack" },
2258
+ "internalInterfaces": {
2259
+ "type": "array",
2260
+ "items": { "$ref": "#/$defs/ProjectInternalInterface" }
2261
+ },
2262
+ "workflows": {
2263
+ "type": "array",
2264
+ "items": { "$ref": "#/$defs/ProjectWorkflow" }
2265
+ },
2038
2266
  "entryPoints": {
2039
2267
  "type": "array",
2040
2268
  "items": { "$ref": "#/$defs/ProjectEntryPoint" }
@@ -534,6 +534,14 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
534
534
 
535
535
  {{ t("releaseHandoff.auditNote") }}
536
536
 
537
+ {% if releaseHandoff.handoffScope %}
538
+ ### 5.6.0 Handoff Scope
539
+
540
+ - Mode: `{{ releaseHandoff.handoffScope.mode | mdcell }}`
541
+ {% if releaseHandoff.handoffScope.stages %}- Stages: {{ releaseHandoff.handoffScope.stages | join(', ') | mdcell }}
542
+ {% endif %}{% if releaseHandoff.handoffScope.collectorBranch %}- Collector branch: `{{ releaseHandoff.handoffScope.collectorBranch | mdcell }}`
543
+ {% endif %}
544
+ {% endif %}
537
545
  ### 5.6.1 Source Verification Report
538
546
 
539
547
  - Path (project-relative): `{{ releaseHandoff.sourceVerificationReport.path }}`
@@ -921,6 +929,30 @@ Single scan of every agreed check that this run did NOT confirm — requirement
921
929
 
922
930
  {{ t("analysis.projectIntro") }}
923
931
 
932
+ {% if projectAnalysis.techStack %}
933
+ ### 5.10.0 Tech Stack
934
+
935
+ | Language | Version | Version source | Files | Lines |
936
+ |----------|---------|----------------|-------|-------|
937
+ {% for row in projectAnalysis.techStack.languages -%}
938
+ | {{ row.name | mdcell }} | {{ row.version | mdcell }} | {{ row.versionSource | mdcell }} | {{ row.fileCount | default('--') | mdcell }} | {{ row.lineCount | default('--') | mdcell }} |
939
+ {% endfor %}
940
+ {% if projectAnalysis.techStack.frameworks %}
941
+ | Framework | Version | Version source | Role |
942
+ |-----------|---------|----------------|------|
943
+ {% for row in projectAnalysis.techStack.frameworks -%}
944
+ | {{ row.name | mdcell }} | {{ row.version | mdcell }} | {{ row.versionSource | mdcell }} | {{ row.role | mdcell }} |
945
+ {% endfor %}
946
+ {% endif %}
947
+ {% if projectAnalysis.techStack.toolchain %}
948
+ | Tool kind | Name | Version | Command |
949
+ |-----------|------|---------|---------|
950
+ {% for row in projectAnalysis.techStack.toolchain -%}
951
+ | {{ row.kind | mdcell }} | {{ row.name | mdcell }} | {{ row.version | default('--') | mdcell }} | {{ row.command | default('--') | mdcell }} |
952
+ {% endfor %}
953
+ {% endif %}
954
+ {% endif %}
955
+
924
956
  ### 5.10.1 Components
925
957
 
926
958
  | ID | Name | Responsibility | Paths | Current code evidence |
@@ -957,6 +989,29 @@ Single scan of every agreed check that this run did NOT confirm — requirement
957
989
  | {{ row.name | mdcell }} | {{ row.adapter | mdcell }} | {{ row.direction | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
958
990
  {% endfor %}
959
991
 
992
+ {% if projectAnalysis.internalInterfaces %}
993
+ ### 5.10.3b Internal Interfaces
994
+
995
+ | ID | Name | Kind | Owner | Signature | Consumers | Current code evidence |
996
+ |----|------|------|-------|-----------|-----------|-----------------------|
997
+ {% for row in projectAnalysis.internalInterfaces -%}
998
+ | {{ row.id | mdcell }} | {{ row.name | mdcell }} | {{ row.kind | mdcell }} | {{ row.ownerComponentId | mdcell }} | `{{ row.signature | mdcell }}` | {{ row.consumers | default([]) | join(', ') | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}`{% if not loop.last %}<br>{% endif %}{% endfor %} |
999
+ {% endfor %}
1000
+ {% endif %}
1001
+ {% if projectAnalysis.workflows %}
1002
+ ### 5.10.3c Workflows
1003
+
1004
+ {% for flow in projectAnalysis.workflows -%}
1005
+ **{{ flow.id | mdcell }} — {{ flow.name | mdcell }}** (trigger: {{ flow.trigger | mdcell }}{% if flow.outcome %}; outcome: {{ flow.outcome | mdcell }}{% endif %})
1006
+
1007
+ | Step | Action | Component | External system |
1008
+ |------|--------|-----------|-----------------|
1009
+ {% for step in flow.steps -%}
1010
+ | {{ step.order | mdcell }} | {{ step.action | mdcell }} | {{ step.componentId | mdcell }} | {{ step.externalSystem | default('--') | mdcell }} |
1011
+ {% endfor %}
1012
+ {% endfor %}
1013
+ {% endif %}
1014
+
960
1015
  ### 5.10.4 Feature Index
961
1016
 
962
1017
  | ID | Name | Description | Representative entry point | Related components | Current code evidence |
@@ -3,16 +3,23 @@
3
3
  body { margin: 0; background: Canvas; color: CanvasText; }
4
4
  .skip-link { position: absolute; left: -999px; }
5
5
  .skip-link:focus { left: 1rem; top: 1rem; z-index: 20; background: Canvas; padding: .6rem; }
6
- .human-report-header, main, .audit { width: min(1120px, calc(100% - 2rem)); margin-inline: auto; }
6
+ .human-report-header, main { width: min(1120px, calc(100% - 2rem)); margin-inline: auto; }
7
7
  .human-report-header { padding: 3rem 0 1.5rem; }
8
8
  .human-report-header h1 { max-width: 30ch; font-size: clamp(1.5rem, 3vw, 2.25rem); line-height: 1.15; margin: .25rem 0 .75rem; }
9
+ .report-meta { display: flex; flex-wrap: wrap; gap: .3rem 1.6rem; margin: 0 0 1.1rem; font-size: .88rem; }
10
+ .report-meta div { display: flex; gap: .45rem; }
11
+ .report-meta dt { color: GrayText; }
12
+ .report-meta dd { margin: 0; }
9
13
  .lede { max-width: 70ch; font-size: clamp(1.05rem, 2vw, 1.35rem); line-height: 1.4; margin: 0 0 1rem; }
10
14
  .eyebrow { text-transform: uppercase; letter-spacing: .08em; font-size: .78rem; color: GrayText; }
11
15
  main { display: grid; gap: 1rem; padding-bottom: 3rem; }
12
16
  section { padding: 1.4rem; border: 1px solid color-mix(in srgb, CanvasText 14%, transparent); border-radius: 16px; background: color-mix(in srgb, Canvas 94%, CanvasText 6%); }
13
17
  h2 { margin-top: 0; font-size: 1.45rem; }
14
18
  h3 { margin-bottom: .35rem; }
15
- .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: .8rem; }
19
+ /* One card per row. Fitting three across left each body about sixteen
20
+ characters wide — a third of what reads comfortably — and turned every card
21
+ into a narrow vertical strip. These carry paragraphs, not labels. */
22
+ .summary-grid { display: grid; grid-template-columns: 1fr; gap: .8rem; }
16
23
  .summary-card { padding: 1rem; border-radius: 12px; background: color-mix(in srgb, Highlight 8%, Canvas); border-left: 4px solid color-mix(in srgb, Highlight 55%, GrayText); }
17
24
  .tone-high, .tone-important { border-left-color: #d94b4b; }
18
25
  .tone-medium { border-left-color: #d79b25; }
@@ -26,12 +33,46 @@ svg .edge-arrow-head { fill: GrayText; }
26
33
  svg .node rect { fill: color-mix(in srgb, Highlight 14%, Canvas); stroke: Highlight; }
27
34
  svg .node text { fill: CanvasText; font-size: 13px; }
28
35
  table { width: 100%; border-collapse: collapse; margin-top: 1rem; font-size: .9rem; }
29
- th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-mix(in srgb, CanvasText 18%, transparent); padding: .55rem; }
36
+ /* Korean takes a line break between any two syllables, so an auto-layout
37
+ column squeezed by a prose neighbour stacked "일부만 검증" one character per
38
+ line. keep-all wraps at word boundaries instead, and the floor keeps the
39
+ id / status / kind columns from collapsing to a single glyph. anywhere is
40
+ the release valve: a path too long for its column breaks rather than
41
+ widening the table until the right-hand columns run off the page. */
42
+ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-mix(in srgb, CanvasText 18%, transparent); padding: .55rem; min-width: 6em; word-break: keep-all; overflow-wrap: anywhere; }
30
43
  .visualization-fallback { margin-top: .8rem; }
31
- .status { display: inline-block; border-radius: 999px; padding: .1rem .5rem; background: color-mix(in srgb, GrayText 15%, Canvas); }
44
+ /* A hint, not a cap auto layout still widens it when a value needs more.
45
+ Without it a two-column table split the width evenly and gave a stack of
46
+ short labels as much room as the prose beside it. */
47
+ .row-key { width: 11em; }
48
+ .row-key dl { margin: 0; }
49
+ .row-key div + div { margin-top: .2rem; }
50
+ /* Name and value share a line so a labelled stack costs no extra height;
51
+ the name is dimmed because the value is what the reader came for. */
52
+ .row-key dt { display: inline; color: GrayText; }
53
+ .row-key dt::after { content: " "; }
54
+ .row-key dd { display: inline; margin: 0; }
55
+ /* The legend for a vocabulary whose members are judgements. A cell fits the
56
+ label; the claim behind it has to live somewhere the reader can reach. */
57
+ .enum-legend { margin: .8rem 0 0; font-size: .9rem; }
58
+ .enum-legend dt { display: inline; font-weight: 600; }
59
+ .enum-legend dt::after { content: " — "; }
60
+ .enum-legend dd { display: inline; margin: 0; color: GrayText; }
61
+ .enum-legend dd::after { content: ""; display: block; margin-bottom: .3rem; }
62
+ /* Each entry is two blocks of prose and a citation. As table rows they shared
63
+ the width three ways and every one of them wrapped into a narrow ribbon. */
64
+ .ledger { list-style: none; margin: 1rem 0 0; padding: 0; }
65
+ .ledger-item { padding: 1rem 0; border-top: 1px solid color-mix(in srgb, CanvasText 14%, transparent); }
66
+ .ledger-item:first-child { border-top: 0; padding-top: 0; }
67
+ .ledger-item p { margin: 0 0 .4rem; }
68
+ .ledger-item p:last-child { margin-bottom: 0; }
69
+ .ledger-key { display: flex; flex-wrap: wrap; gap: .6rem; font-weight: 600; }
70
+ .ledger-kind { font-weight: 400; color: GrayText; }
71
+ .ledger-text { max-width: 78ch; }
72
+ .ledger-source { font-size: .9rem; color: GrayText; }
73
+ .ledger-source > span:first-child::after { content: ":"; }
74
+ .status { display: inline-block; white-space: nowrap; border-radius: 999px; padding: .1rem .5rem; background: color-mix(in srgb, GrayText 15%, Canvas); }
32
75
  .status-gap, .status-risk { background: color-mix(in srgb, #d94b4b 18%, Canvas); }
33
- .audit { margin-bottom: 3rem; padding: 1rem; border: 1px solid GrayText; border-radius: 12px; }
34
- .audit pre { overflow: auto; white-space: pre-wrap; }
35
76
  .human-report-footer { width: min(1120px, calc(100% - 2rem)); margin: 0 auto 2rem; display: flex; flex-wrap: wrap; gap: .6rem; }
36
77
  .human-report-footer pre { flex-basis: 100%; white-space: pre-wrap; max-height: 14em; overflow: auto; }
37
78
  fieldset, label { display: block; margin: .7rem 0; }
@@ -39,9 +80,18 @@ textarea, select { width: 100%; max-width: 60rem; padding: .55rem; font: inherit
39
80
  .clarification-lede, .clarification-item p { max-width: 78ch; }
40
81
  .clarification-item { padding: 1.2rem 0; border-top: 1px solid color-mix(in srgb, CanvasText 14%, transparent); }
41
82
  .clarification-item:first-of-type { border-top: 0; }
42
- .clarification-expected { color: GrayText; font-size: .9rem; }
83
+ .clarification-expected { padding: .7rem .9rem; border-radius: 10px; background: color-mix(in srgb, CanvasText 6%, Canvas); }
84
+ .clarification-expected dt { font-weight: 600; margin-bottom: .3rem; }
85
+ .clarification-expected dd { margin: 0; }
43
86
  .clarification-item label { font-weight: 600; margin-bottom: .3rem; }
44
87
  .user-response-hint { flex-basis: 100%; margin: 0; color: GrayText; font-size: .9rem; }
45
- @media (max-width: 640px) { section { padding: 1rem; } .visualization { display: none; } }
46
- @media print { .skip-link, .audit, script { display: none !important; } body { color: #000; background: #fff; } section { break-inside: avoid; border-color: #bbb; } .visualization-fallback { display: table; } }
88
+ button { padding: .5rem 1rem; border-radius: 8px; border: 1px solid color-mix(in srgb, CanvasText 28%, transparent); background: color-mix(in srgb, CanvasText 5%, Canvas); color: CanvasText; font: inherit; cursor: pointer; }
89
+ button:hover { background: color-mix(in srgb, CanvasText 12%, Canvas); }
90
+ button:active { transform: translateY(1px); }
91
+ button:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
92
+ button[data-action="export-user-response"] { background: Highlight; border-color: Highlight; color: HighlightText; font-weight: 600; }
93
+ button[data-action="export-user-response"]:hover { background: color-mix(in srgb, Highlight 82%, CanvasText); }
94
+ /* A seven-column table needs 42em of floor, more than a phone can give. */
95
+ @media (max-width: 640px) { section { padding: 1rem; } .visualization { display: none; } th, td { min-width: 4.5em; } }
96
+ @media print { .skip-link, script { display: none !important; } body { color: #000; background: #fff; } section { break-inside: avoid; border-color: #bbb; } .visualization-fallback { display: table; } }
47
97
  code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; padding: .1em .3em; border-radius: 4px; background: color-mix(in srgb, CanvasText 8%, Canvas); }
@@ -1,6 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  {% from "html/macros/forms.html" import clarification_responses %}
3
- <html lang="{{ 'ko' if humanSummary else 'en' }}" data-task-template="{{ taskType }}">
3
+ <html lang="en" data-task-template="{{ taskType }}">
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width,initial-scale=1">
@@ -12,59 +12,38 @@
12
12
  <header class="human-report-header">
13
13
  <p class="eyebrow">{{ taskType }}</p>
14
14
  <h1>{{ taskType }} #{{ runMeta.seq }}</h1>
15
+ <dl class="report-meta">
16
+ <div><dt>Task</dt><dd data-report-meta="taskTitle">{{ reportMeta.taskTitle | inline_code }}</dd></div>
17
+ <div><dt>Task key</dt><dd data-report-meta="taskKey"><code>{{ reportMeta.taskKey }}</code></dd></div>
18
+ <div><dt>Written</dt><dd data-report-meta="createdAt">{{ reportMeta.createdAt }}</dd></div>
19
+ {% if reportMeta.elapsed %}<div><dt>Elapsed</dt><dd data-report-meta="elapsed">{{ reportMeta.elapsed }}</dd></div>{% endif %}
20
+ </dl>
15
21
  <p class="lede">{{ humanSummary.headline | inline_code }}</p>
16
22
  <p>{{ humanSummary.outcome | inline_code }}</p>
17
- <button type="button" data-action="export-user-response">사용자 응답 내보내기</button>
18
23
  </header>
19
24
  <main id="main-content" data-report-role="human-main">
20
25
  {% block human_content %}{% endblock %}
21
26
  {{ clarification_responses(clarificationItems) }}
22
27
  {% if evidenceIndex %}
23
28
  <section data-report-section="evidence-ledger">
24
- <h2>근거 대장</h2>
25
- <table>
26
- <thead><tr><th>ID</th><th>내용</th><th>출처 · 신뢰도</th></tr></thead>
27
- <tbody>
28
- {% for row_id, row in evidenceIndex | dictsort %}
29
- <tr id="ev-{{ row_id }}"><td>{{ row_id }}</td><td>{{ row.text | inline_code }}</td><td>{% if row.codeEvidence %}{{ row.codeEvidence | code_evidence }}{% else %}{{ row.source | inline_code }}{% endif %}</td></tr>
30
- {% endfor %}
31
- </tbody>
32
- </table>
29
+ <h2>Evidence ledger</h2>
30
+ <ol class="ledger">
31
+ {% for row_id, row in evidenceIndex.items() %}
32
+ <li class="ledger-item" id="id-{{ row_id }}">
33
+ <p class="ledger-key"><span class="ledger-id">{{ row_id }}</span><span class="ledger-kind">{{ row.kind }}</span></p>
34
+ <p class="ledger-text">{{ row.text | inline_code }}</p>
35
+ <p class="ledger-source"><span>Source · confidence</span> {% if row.codeEvidence %}{{ row.codeEvidence | code_evidence }}{% else %}{{ row.source | inline_code }}{% endif %}</p>
36
+ </li>
37
+ {% endfor %}
38
+ </ol>
33
39
  </section>
34
40
  {% endif %}
35
41
  </main>
36
- <details id="audit-details" class="audit" data-report-role="audit">
37
- <summary>감사 정보</summary>
38
- <section data-audit-field="crossVerification">
39
- <h2>Cross Verification</h2>
40
- <pre>{{ audit.crossVerification | audit_json }}</pre>
41
- </section>
42
- <section data-audit-field="executionStatus">
43
- <h2>Execution Status by Agent</h2>
44
- <pre>{{ audit.executionStatus | audit_json }}</pre>
45
- </section>
46
- <section data-audit-field="tokenUsage">
47
- <h2>Token Usage</h2>
48
- <pre>{{ audit.tokenUsage | audit_json }}</pre>
49
- </section>
50
- {% if audit.planBodyVerification is defined %}
51
- <section data-audit-field="planBodyVerification">
52
- <h2>Plan Body Verification</h2>
53
- <pre>{{ audit.planBodyVerification | audit_json }}</pre>
54
- </section>
55
- {% endif %}
56
- {% if audit.deliveryEvidence is defined %}
57
- <section data-audit-field="deliveryEvidence">
58
- <h2>Delivery Evidence</h2>
59
- <pre>{{ audit.deliveryEvidence | audit_json }}</pre>
60
- </section>
61
- {% endif %}
62
- </details>
63
42
  <footer class="human-report-footer">
64
- <button type="button" data-action="export-user-response">사용자 응답 내보내기</button>
65
- <button type="button" data-action="copy-user-response">복사</button>
66
- <button type="button" data-action="dismiss-user-response" hidden>닫기</button>
67
- <p class="user-response-hint">내보내기를 누르면 <code>user-response-{{ runMeta.task_type }}-{{ runMeta.seq }}.md</code> 다운로드됩니다. 파일을 <code>runs/{{ runMeta.task_type }}/user-responses/</code> 넣고 다음 run 실행하면 답변이 자동으로 반영됩니다.</p>
43
+ <button type="button" data-action="export-user-response">Export my answers</button>
44
+ <button type="button" data-action="copy-user-response">Copy</button>
45
+ <button type="button" data-action="dismiss-user-response" hidden>Dismiss</button>
46
+ <p class="user-response-hint">Export downloads <code>user-response-{{ runMeta.task_type }}-{{ runMeta.seq }}.md</code>. Drop that file into <code>runs/{{ runMeta.task_type }}/user-responses/</code> and the next run picks your answers up on its own.</p>
68
47
  <pre id="user-response-output" aria-live="polite"></pre>
69
48
  </footer>
70
49
  <script id="run-meta" type="application/json">{{ {
@@ -1,46 +1,46 @@
1
1
  {% macro analysis_review(selector_ids) -%}
2
2
  <section id="analysis-review" data-report-section="analysis-review">
3
- <h2>분석 검토</h2>
4
- <fieldset><legend>판정</legend>
5
- <input id="analysis-review-accepted" type="radio" name="analysis-review-status" value="accepted"><label for="analysis-review-accepted">수락</label>
6
- <input id="analysis-review-revision-requested" type="radio" name="analysis-review-status" value="revision-requested"><label for="analysis-review-revision-requested">수정 요청</label>
7
- <input id="analysis-review-rejected" type="radio" name="analysis-review-status" value="rejected"><label for="analysis-review-rejected">거절</label>
3
+ <h2>Review this analysis</h2>
4
+ <fieldset><legend>Verdict</legend>
5
+ <input id="analysis-review-accepted" type="radio" name="analysis-review-status" value="accepted"><label for="analysis-review-accepted">Accept</label>
6
+ <input id="analysis-review-revision-requested" type="radio" name="analysis-review-status" value="revision-requested"><label for="analysis-review-revision-requested">Request changes</label>
7
+ <input id="analysis-review-rejected" type="radio" name="analysis-review-status" value="rejected"><label for="analysis-review-rejected">Reject</label>
8
8
  </fieldset>
9
- <label for="analysis-review-affected-ids">영향받는 ID</label>
9
+ <label for="analysis-review-affected-ids">Affected ids</label>
10
10
  <select id="analysis-review-affected-ids" multiple>
11
11
  {% for analysis_id in selector_ids %}<option value="{{ analysis_id }}">{{ analysis_id }}</option>{% endfor %}
12
12
  </select>
13
- <label for="analysis-review-reason">이유</label><textarea id="analysis-review-reason" rows="3"></textarea>
14
- <label for="analysis-review-evidence">추가 근거</label><textarea id="analysis-review-evidence" rows="3"></textarea>
15
- <label for="analysis-review-scope-change">요청 범위 변경</label><textarea id="analysis-review-scope-change" rows="3"></textarea>
13
+ <label for="analysis-review-reason">Why</label><textarea id="analysis-review-reason" rows="3"></textarea>
14
+ <label for="analysis-review-evidence">Further evidence</label><textarea id="analysis-review-evidence" rows="3"></textarea>
15
+ <label for="analysis-review-scope-change">Scope change requested</label><textarea id="analysis-review-scope-change" rows="3"></textarea>
16
16
  </section>
17
17
  {%- endmacro %}
18
18
 
19
19
  {% macro plan_approval(state) -%}
20
20
  <section id="plan-approval" data-report-section="plan-approval">
21
- <h2>계획 승인</h2>
22
- <label for="approval-option">구현 옵션</label>
21
+ <h2>Approve this plan</h2>
22
+ <label for="approval-option">Implementation option</label>
23
23
  <select id="approval-option"{% if state.disabled_reason %} disabled{% endif %}>
24
24
  {% for name in state.option_names %}
25
- {% if name == state.recommended_option %}<option data-recommended="true" selected value="{{ name }}">{{ name | inline_code }} (권장)</option>{% else %}<option value="{{ name }}">{{ name | inline_code }}</option>{% endif %}
25
+ {% if name == state.recommended_option %}<option data-recommended="true" selected value="{{ name }}">{{ name | inline_code }} (recommended)</option>{% else %}<option value="{{ name }}">{{ name | inline_code }}</option>{% endif %}
26
26
  {% endfor %}
27
27
  </select>
28
- <input type="checkbox" id="approval-checkbox"{% if state.disabled_reason %} disabled{% endif %}><label for="approval-checkbox">이 계획을 승인합니다.</label>
29
- {% if state.disabled_reason %}<div class="approval-disabled-reason"><p><strong>{{ state.disabled_reason }}</strong>라 아직 승인할 수 없습니다.</p><p>{{ state.blocker_ids | join(', ') }} 답하고 사용자 응답을 내보낸 clarification 재개 명령으로 보고서를 다시 생성하세요.</p></div>{% endif %}
28
+ <input type="checkbox" id="approval-checkbox"{% if state.disabled_reason %} disabled{% endif %}><label for="approval-checkbox">I approve this plan.</label>
29
+ {% if state.disabled_reason %}<div class="approval-disabled-reason"><p>Approval is held: <strong>{{ state.disabled_reason }}</strong>.</p><p>Answer {{ state.blocker_ids | join(', ') }}, export your answers, then regenerate the report with the clarification resume command.</p></div>{% endif %}
30
30
  </section>
31
31
  {%- endmacro %}
32
32
 
33
33
  {% macro clarification_responses(items) -%}
34
34
  {% if items %}
35
- <section class="clarification-responses" aria-label="답변이 필요한 질문">
36
- <h2>답변이 필요한 질문 {{ items | length }}개</h2>
37
- <p class="clarification-lede">코드만 봐서는 정할 없어 남겨 항목입니다. 답을 채운 아래 <strong>사용자 응답 내보내기</strong>를 누르세요.</p>
35
+ <section class="clarification-responses" aria-label="Questions waiting on you">
36
+ <h2>{{ items | length }} questions waiting on you</h2>
37
+ <p class="clarification-lede">The code alone could not settle these. Fill in your answers, then press <strong>Export my answers</strong> at the foot of the page.</p>
38
38
  {% for row in items %}
39
- <article class="clarification-item" data-response-id="{{ row.id }}" data-kind="{{ row.kind }}">
39
+ <article class="clarification-item" id="id-{{ row.id }}" data-response-id="{{ row.id }}" data-kind="{{ row.kind }}">
40
40
  <p class="eyebrow">{{ row.id }} · {{ row.kind }}</p>
41
41
  {{ row.statement | paragraphs }}
42
- <p class="clarification-expected"><strong>답변 형식</strong>{{ row.expectedForm | inline_code }}</p>
43
- <label for="response-{{ row.id }}">{{ row.id }} 답변</label>
42
+ <dl class="clarification-expected"><dt>Answer as</dt><dd>{{ row.expectedForm | inline_code }}</dd></dl>
43
+ <label for="response-{{ row.id }}">Your answer to {{ row.id }}</label>
44
44
  <textarea id="response-{{ row.id }}" data-response-id="{{ row.id }}" rows="4">{{ row.userInput | default('') }}</textarea>
45
45
  </article>
46
46
  {% endfor %}