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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.130.3",
3
+ "version": "0.130.4",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.130.3",
3
- "builtAt": "2026-07-21T14:59:13.137Z",
2
+ "package": "0.130.4",
3
+ "builtAt": "2026-07-21T15:17:32.560Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -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
- return ' class="td-narrow"' if col_idx in narrow_cols else ""
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 &lt;br&gt;; restore the tag.
962
985
  out = out.replace("&lt;br&gt;", "<br>").replace("&lt;br/&gt;", "<br>").replace("&lt;br /&gt;", "<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("&lt;small&gt;", "<small>").replace("&lt;/small&gt;", "</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
- | ID | Ticket ID | Action | Path (and line-range) | Change summary |
208
- |----|-----------|--------|------------------------|----------------|
207
+ | {{ t("columns.recordMeta") }} | Path (and line-range) | Change summary |
208
+ |------|------------------------|----------------|
209
209
  {% for fs in opt.fileStructure -%}
210
- | {{ fs.id | mdcell }} | `{{ fs.ticketId | mdcell }}` | {{ fs.action | mdcell }} | `{{ fs.path | mdcell }}` | {{ fs.summary | 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;