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.
- 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 +44 -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 +59 -9
- package/runtime/templates/reports/html/base.template.html +21 -42
- package/runtime/templates/reports/html/macros/forms.html +20 -20
- 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/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": {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
46
|
-
|
|
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="
|
|
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
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
{
|
|
29
|
-
<
|
|
30
|
-
{%
|
|
31
|
-
</
|
|
32
|
-
|
|
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"
|
|
65
|
-
<button type="button" data-action="copy-user-response"
|
|
66
|
-
<button type="button" data-action="dismiss-user-response" hidden
|
|
67
|
-
<p class="user-response-hint"
|
|
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
|
|
4
|
-
<fieldset><legend
|
|
5
|
-
<input id="analysis-review-accepted" type="radio" name="analysis-review-status" value="accepted"><label for="analysis-review-accepted"
|
|
6
|
-
<input id="analysis-review-revision-requested" type="radio" name="analysis-review-status" value="revision-requested"><label for="analysis-review-revision-requested"
|
|
7
|
-
<input id="analysis-review-rejected" type="radio" name="analysis-review-status" value="rejected"><label for="analysis-review-rejected"
|
|
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"
|
|
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"
|
|
14
|
-
<label for="analysis-review-evidence"
|
|
15
|
-
<label for="analysis-review-scope-change"
|
|
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
|
|
22
|
-
<label for="approval-option"
|
|
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 }} (
|
|
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"
|
|
29
|
-
{% if state.disabled_reason %}<div class="approval-disabled-reason"><p
|
|
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
|
|
37
|
-
<p class="clarification-lede"
|
|
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
|
-
<
|
|
43
|
-
<label for="response-{{ row.id }}">{{ row.id }}
|
|
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 %}
|