okstra 0.130.3 → 0.130.4
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/prompts/profiles/implementation-planning.md +5 -0
- package/runtime/python/okstra_ctl/report_views.py +32 -5
- package/runtime/schemas/final-report-v1.0.schema.json +2 -1
- package/runtime/templates/reports/final-report.template.md +3 -3
- package/runtime/templates/reports/report.css +18 -0
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -78,6 +78,11 @@
|
|
|
78
78
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
79
79
|
- at least two implementation options. **Each option must include**:
|
|
80
80
|
- **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
|
|
81
|
+
- **Two-tier change description.** Each `fileStructure` row carries `summary` **and** optional `details`, and they are not interchangeable:
|
|
82
|
+
- `summary` — one plain-language sentence a reviewer who has never opened this file can follow: what changes and why it is needed. Name behaviour and domain nouns, not identifiers. No function/type/variable names, no call chains, no line numbers, no conditional logic spelled out. **Enforced:** `schemas/final-report-v1.0.schema.json` `$defs.OptionCandidate.fileStructure.items.summary` caps it at 120 characters, so a dense identifier dump fails schema validation.
|
|
83
|
+
- `details` — the technical specifics that used to be crammed into `summary`: exact symbol names, signatures, comparison semantics, line ranges, enum members. Omit the field when there is nothing beyond the summary.
|
|
84
|
+
- Bad `summary` (identifier dump, no readable claim): `Widen the return of retrieveFontFamilyGroupWithFontVersionId to also yield the matched FontFamily (own key and enabled flag) and the matched Font.status.`
|
|
85
|
+
- Good — `summary`: `Carry enough information out of the font lookup to tell whether the matched family is switched off.` / `details`: `Add the matched FontFamily (own key + enabled) and Font.status to the return of retrieveFontFamilyGroupWithFontVersionId. Both are already in scope in the :102-104 loop and dropped at return.`
|
|
81
86
|
- affected interfaces / public contracts and downstream consumers
|
|
82
87
|
- estimated blast radius (units, configs, deployment manifests, data migrations)
|
|
83
88
|
- trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
|
|
@@ -513,6 +513,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
513
513
|
)
|
|
514
514
|
|
|
515
515
|
narrow_cols = _narrow_columns(header_cells, rows)
|
|
516
|
+
path_cols = _path_columns(header_cells)
|
|
516
517
|
# `User input` carries the embedded form widget (textarea / select /
|
|
517
518
|
# input) which needs all the horizontal space it can get; its
|
|
518
519
|
# markdown plain text is empty or a short placeholder so the auto
|
|
@@ -523,7 +524,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
523
524
|
head = (
|
|
524
525
|
"<thead><tr>"
|
|
525
526
|
+ "".join(
|
|
526
|
-
f"<th{_col_class(idx, narrow_cols)}>{_inline(c)}</th>"
|
|
527
|
+
f"<th{_col_class(idx, narrow_cols, path_cols)}>{_inline(c)}</th>"
|
|
527
528
|
for idx, c in enumerate(header_cells)
|
|
528
529
|
)
|
|
529
530
|
+ "</tr></thead>"
|
|
@@ -562,7 +563,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
562
563
|
)
|
|
563
564
|
else:
|
|
564
565
|
cells_html.append(
|
|
565
|
-
f"<td{_col_class(idx, narrow_cols)}>{_inline(cell)}</td>"
|
|
566
|
+
f"<td{_col_class(idx, narrow_cols, path_cols)}>{_inline(cell)}</td>"
|
|
566
567
|
)
|
|
567
568
|
body_rows.append(
|
|
568
569
|
f'<tr id="{html.escape(meta.row_id.lower())}" '
|
|
@@ -579,7 +580,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
579
580
|
body_rows.append(
|
|
580
581
|
tr_open
|
|
581
582
|
+ "".join(
|
|
582
|
-
f"<td{_col_class(idx, narrow_cols)}>{_inline(c)}</td>"
|
|
583
|
+
f"<td{_col_class(idx, narrow_cols, path_cols)}>{_inline(c)}</td>"
|
|
583
584
|
for idx, c in enumerate(row)
|
|
584
585
|
)
|
|
585
586
|
+ "</tr>"
|
|
@@ -921,8 +922,30 @@ def _narrow_columns(
|
|
|
921
922
|
return narrow
|
|
922
923
|
|
|
923
924
|
|
|
924
|
-
def _col_class(col_idx: int, narrow_cols: set[int]) -> str:
|
|
925
|
-
|
|
925
|
+
def _col_class(col_idx: int, narrow_cols: set[int], path_cols: set[int]) -> str:
|
|
926
|
+
if col_idx in narrow_cols:
|
|
927
|
+
return ' class="td-narrow"'
|
|
928
|
+
if col_idx in path_cols:
|
|
929
|
+
return ' class="td-path"'
|
|
930
|
+
return ""
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
# A repo-relative path has no spaces, so `overflow-wrap: anywhere` puts its
|
|
934
|
+
# min-content width at one character and the auto table layout hands the
|
|
935
|
+
# column almost nothing while a prose neighbour takes the rest — the path
|
|
936
|
+
# then renders one or two characters per line. `td-path` floors the width.
|
|
937
|
+
_PATH_HEADER_PREFIXES: tuple[str, ...] = ("path", "파일 경로", "경로")
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def _path_columns(header_cells: list[str]) -> set[int]:
|
|
941
|
+
return {
|
|
942
|
+
col
|
|
943
|
+
for col, header in enumerate(header_cells)
|
|
944
|
+
if _INLINE_MD_STRIP_RE.sub("", header or "")
|
|
945
|
+
.strip()
|
|
946
|
+
.lower()
|
|
947
|
+
.startswith(_PATH_HEADER_PREFIXES)
|
|
948
|
+
}
|
|
926
949
|
|
|
927
950
|
|
|
928
951
|
_DANGEROUS_URL_SCHEME_RE = re.compile(
|
|
@@ -960,6 +983,10 @@ def _inline(text: str) -> str:
|
|
|
960
983
|
# markdown source intentionally stacks short fields with <br>). html.escape
|
|
961
984
|
# above turned them into <br>; restore the tag.
|
|
962
985
|
out = out.replace("<br>", "<br>").replace("<br/>", "<br>").replace("<br />", "<br>")
|
|
986
|
+
# `<small>` demotes a cell's technical detail line below its plain-language
|
|
987
|
+
# first line (File Structure `details`). Allowlisted alongside <br>; every
|
|
988
|
+
# other tag stays escaped.
|
|
989
|
+
out = out.replace("<small>", "<small>").replace("</small>", "</small>")
|
|
963
990
|
return out
|
|
964
991
|
|
|
965
992
|
|
|
@@ -1256,7 +1256,8 @@
|
|
|
1256
1256
|
"ticketId": { "$ref": "#/$defs/TicketId" },
|
|
1257
1257
|
"action": { "enum": ["Create", "Modify", "Delete"] },
|
|
1258
1258
|
"path": { "type": "string", "minLength": 1 },
|
|
1259
|
-
"summary": { "type": "string", "minLength": 1 }
|
|
1259
|
+
"summary": { "type": "string", "minLength": 1, "maxLength": 120 },
|
|
1260
|
+
"details": { "type": "string", "minLength": 1 }
|
|
1260
1261
|
}
|
|
1261
1262
|
}
|
|
1262
1263
|
},
|
|
@@ -204,10 +204,10 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
|
|
|
204
204
|
|
|
205
205
|
- File Structure:
|
|
206
206
|
|
|
207
|
-
|
|
|
208
|
-
|
|
207
|
+
| {{ t("columns.recordMeta") }} | Path (and line-range) | Change summary |
|
|
208
|
+
|------|------------------------|----------------|
|
|
209
209
|
{% for fs in opt.fileStructure -%}
|
|
210
|
-
| {{ fs.id | mdcell }}
|
|
210
|
+
| **{{ fs.id | mdcell }}**<br>Ticket: `{{ fs.ticketId | mdcell }}`<br>Action: {{ fs.action | mdcell }} | `{{ fs.path | mdcell }}` | {{ fs.summary | mdcell }}{% if fs.details %}<br><small>{{ fs.details | mdcell }}</small>{% endif %} |
|
|
211
211
|
{% endfor %}
|
|
212
212
|
|
|
213
213
|
- {{ t("implementationPlanning.optionInterfacesLabel") }}: {{ opt.interfaces }}
|
|
@@ -188,6 +188,24 @@ td.td-narrow, th.td-narrow {
|
|
|
188
188
|
width: 5%;
|
|
189
189
|
white-space: nowrap;
|
|
190
190
|
}
|
|
191
|
+
/* Path columns — assigned by `_path_columns()` in report_views.py. A
|
|
192
|
+
* repo-relative path holds no spaces, so the `overflow-wrap: anywhere`
|
|
193
|
+
* above lets the auto table layout starve the column down to a couple of
|
|
194
|
+
* characters per line. Floor the width and let it wrap at separators. */
|
|
195
|
+
td.td-path, th.td-path {
|
|
196
|
+
min-width: 26ch;
|
|
197
|
+
word-break: break-word;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Second line of a two-tier cell: the plain-language summary leads, the
|
|
201
|
+
* technical detail follows demoted. */
|
|
202
|
+
td small {
|
|
203
|
+
display: inline-block;
|
|
204
|
+
margin-top: 0.35em;
|
|
205
|
+
font-size: 0.88em;
|
|
206
|
+
color: GrayText;
|
|
207
|
+
}
|
|
208
|
+
|
|
191
209
|
thead th {
|
|
192
210
|
position: sticky;
|
|
193
211
|
top: 3rem;
|