okstra 0.185.1 → 0.186.1

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 (34) hide show
  1. package/dist/commands/chat/chat.mjs +60 -14
  2. package/dist/commands/chat/chat.mjs.map +1 -1
  3. package/docs/cli.md +1 -1
  4. package/docs/for-ai/skills/okstra-chat.md +4 -4
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  8. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  9. package/runtime/python/okstra_ctl/cmux.py +63 -35
  10. package/runtime/python/okstra_ctl/report_html/common.py +86 -11
  11. package/runtime/python/okstra_ctl/report_html/filters.py +27 -10
  12. package/runtime/python/okstra_ctl/report_html/render.py +1 -0
  13. package/runtime/python/okstra_ctl/report_html/report_index.py +5 -1
  14. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +1 -1
  15. package/runtime/skills/okstra-chat/SKILL.md +10 -2
  16. package/runtime/templates/reports/html/assets/base.css +48 -3
  17. package/runtime/templates/reports/html/base.template.html +7 -4
  18. package/runtime/templates/reports/html/i18n/en.json +86 -8
  19. package/runtime/templates/reports/html/i18n/ko.json +86 -8
  20. package/runtime/templates/reports/html/macros/forms.html +74 -55
  21. package/runtime/templates/reports/html/macros/layout.html +2 -2
  22. package/runtime/templates/reports/html/macros/visualizations.html +1 -1
  23. package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +2 -2
  24. package/runtime/templates/reports/html/tasks/error-analysis.template.html +3 -3
  25. package/runtime/templates/reports/html/tasks/feature-analysis.template.html +4 -4
  26. package/runtime/templates/reports/html/tasks/final-verification.template.html +3 -3
  27. package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +9 -9
  28. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +20 -18
  29. package/runtime/templates/reports/html/tasks/implementation.template.html +3 -3
  30. package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +3 -3
  31. package/runtime/templates/reports/html/tasks/project-analysis.template.html +10 -10
  32. package/runtime/templates/reports/html/tasks/release-handoff.template.html +2 -2
  33. package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +4 -4
  34. package/runtime/validators/validate-run.py +2 -8
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import re
5
+ from functools import lru_cache
5
6
 
6
7
  import okstra_vendor # noqa: F401 # registers vendored dependency aliases
7
8
  from markupsafe import Markup, escape
@@ -9,12 +10,24 @@ from markupsafe import Markup, escape
9
10
  _INLINE_CODE = re.compile(r"`([^`]+)`")
10
11
  _SENTENCE_END = re.compile(r"(?<=[.!?])\s+")
11
12
  _SENTENCES_PER_PARAGRAPH = 2
12
- # `\b` after the digits would end the token only where the next character is
13
- # non-word, and a Korean particle is a word character — `EA-001에` matched
14
- # nothing, so every id a translated report cites mid-sentence lost its link
15
- # while the English source kept it. The boundary a row id actually needs is
16
- # "not part of a longer alphanumeric run", which is what these assertions say.
17
- _ID_TOKEN = re.compile(r"(?<![A-Za-z0-9])[A-Z]{1,3}-\d+(?!\d)")
13
+
14
+
15
+ @lru_cache(maxsize=16)
16
+ def _anchors_pattern(keys: tuple[str, ...]) -> re.Pattern[str] | None:
17
+ """이 문서가 실제로 가진 아이디만, 것부터 맞춘다.
18
+
19
+ 고정 `[A-Z]{1,3}-\\d+` 토큰은 `PREP-001` 과 `P-Step-5.1` 을 놓쳐
20
+ 행이 페이지에 있어도 본문이 그냥 글자가 됐다. 인덱스에 있는 키로
21
+ 패턴을 만들면 `DEV-10339` 같은 티켓은 그 id 의 행이 없을 때 링크되지 않는다.
22
+ """
23
+ if not keys:
24
+ return None
25
+ ordered = tuple(sorted(keys, key=len, reverse=True))
26
+ return re.compile(
27
+ r"(?<![A-Za-z0-9])(?:"
28
+ + "|".join(re.escape(key) for key in ordered)
29
+ + r")(?![A-Za-z0-9])"
30
+ )
18
31
 
19
32
 
20
33
  def _link_ids(escaped: str, anchors: dict) -> str:
@@ -27,12 +40,16 @@ def _link_ids(escaped: str, anchors: dict) -> str:
27
40
  """
28
41
  if not anchors:
29
42
  return escaped
43
+ pattern = _anchors_pattern(tuple(sorted(anchors)))
44
+ if pattern is None:
45
+ return escaped
30
46
 
31
- def swap(match: re.Match) -> str:
32
- name = anchors.get(match.group(0))
33
- return f'<a href="#{name}">{match.group(0)}</a>' if name else match.group(0)
47
+ def swap(match: re.Match[str]) -> str:
48
+ token = match.group(0)
49
+ name = anchors.get(token)
50
+ return f'<a href="#{name}">{token}</a>' if name else token
34
51
 
35
- return _ID_TOKEN.sub(swap, escaped)
52
+ return pattern.sub(swap, escaped)
36
53
 
37
54
 
38
55
  def inline_code(value: object, anchors: dict | None = None) -> Markup:
@@ -153,6 +153,7 @@ def render_v2_html_view(
153
153
  "runMeta": run_meta,
154
154
  "reportMeta": _report_meta(data, run_meta),
155
155
  "taskType": view.task_type,
156
+ "lang": lang,
156
157
  "sourceData": source_data,
157
158
  "dataSha256": _sha256(data_path),
158
159
  "clarificationItems": data.get("clarificationItems", []),
@@ -24,6 +24,8 @@ _SLUG_RE = re.compile(r'\bdata-report-section="([^"]+)"')
24
24
  _TAG_RE = re.compile(r"<[^>]+>")
25
25
 
26
26
  INDEX_TITLE_ID = "report-index-title"
27
+ # 맨 위로 버튼 패널에 같은 목차 항목을 채우는 자리. 본문 목차와 한 함수에서 만든다.
28
+ _INDEX_ITEMS_SLOT = "<!--report-index-items-->"
27
29
 
28
30
 
29
31
  def _heading_text(title_markup: str) -> str:
@@ -34,6 +36,7 @@ def _heading_text(title_markup: str) -> str:
34
36
  def inject_report_index(document: str, *, label: str) -> str:
35
37
  """Return ``document`` with a section index at the top of ``<main>``.
36
38
 
39
+ The same list fills the back-to-top hover panel, so the two cannot drift.
37
40
  Sections that lack both an id and a slug are skipped rather than given a
38
41
  generated anchor: a link whose target moves between renders is worse than
39
42
  an entry the reader never had.
@@ -72,4 +75,5 @@ def inject_report_index(document: str, *, label: str) -> str:
72
75
  f"<ol>{items}</ol>"
73
76
  "</nav>"
74
77
  )
75
- return f"{head}\n{index}{body}"
78
+ filled = f"{head}\n{index}{body}"
79
+ return filled.replace(_INDEX_ITEMS_SLOT, f"<ol>{items}</ol>", 1)
@@ -134,7 +134,7 @@ def build_implementation_planning_view(data: dict) -> HumanReportView:
134
134
  row["activityId"]: row for row in activities if row.get("activityId")
135
135
  },
136
136
  "decisionCards": decision_cards,
137
- "evidenceIndex": evidence_index(data),
137
+ "evidenceIndex": evidence_index(data, _OMITTED_FIELDS),
138
138
  }
139
139
  return HumanReportView(
140
140
  "implementation-planning",
@@ -59,7 +59,7 @@ If the host has a native picker, use it. Otherwise print a numbered list.
59
59
  okstra chat unread --room <room> --as <display>
60
60
  ```
61
61
 
62
- Show the rows. Each row is `id:time:@from:to:body`. Recipient `all` stays `all`. Sender is always `@name`.
62
+ Show the rows. Each row is `id @from YYYY-MM-DD HH:MM body`. Sender is always `@name`. The id is the first token (before the first space). A reply inserts `↑<parentId>` after the time. Recipient is not on the line.
63
63
 
64
64
  If the output is `no unread`, do not ack.
65
65
 
@@ -73,7 +73,7 @@ Use the last unread row's id. CLI unread does not move the cursor; this ack does
73
73
 
74
74
  ## Step 3: Next action
75
75
 
76
- Ask: send, unread, inbox, log, or done.
76
+ Ask: send, unread, inbox, log, reply, or done.
77
77
 
78
78
  There is no ack menu item. Choosing unread again shows the rows then acks, same as Step 2.
79
79
 
@@ -84,6 +84,14 @@ There is no ack menu item. Choosing unread again shows the rows then acks, same
84
84
  3. Ask for the body as free input.
85
85
  4. Run `okstra chat send --room <room> --as <display> --to <all|name> --body <text>`.
86
86
 
87
+ ### Reply
88
+
89
+ 1. Ask for the parent message id as free input.
90
+ 2. Ask for the body as free input.
91
+ 3. Run `okstra chat send --room <room> --as <display> --reply-to <id> --body <text>`.
92
+
93
+ Do not pick a recipient. There is no reply subcommand.
94
+
87
95
  ### Inbox
88
96
 
89
97
  ```bash
@@ -108,8 +108,11 @@ textarea, select { width: 100%; max-width: 60rem; padding: .55rem; font: inherit
108
108
  .clarification-expected dt { font-weight: 600; margin-bottom: .3rem; }
109
109
  .clarification-expected dd { margin: 0; }
110
110
  .clarification-item label { font-weight: 600; margin-bottom: .3rem; }
111
- .activity-list { display: grid; gap: .8rem; }
112
- .activity-card { padding: 1rem; border: 1px solid color-mix(in srgb, CanvasText 14%, transparent); border-radius: 12px; }
111
+ .activity-list { display: grid; gap: .4rem; }
112
+ .activity-card { padding: .55rem .8rem; border: 1px solid color-mix(in srgb, CanvasText 14%, transparent); border-radius: 12px; }
113
+ .activity-card h3 { font-size: 1rem; margin: 0 0 .15rem; }
114
+ .clarification-item.is-closed { padding: .35rem 0; }
115
+ .clarification-item.is-closed summary { cursor: pointer; font-weight: 600; }
113
116
  .activity-card > :first-child, .approval-context > :first-child { margin-top: 0; }
114
117
  .activity-card > :last-child, .approval-context > :last-child { margin-bottom: 0; }
115
118
  .activity-card details { margin-top: .8rem; }
@@ -122,7 +125,49 @@ button:active { transform: translateY(1px); }
122
125
  button:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
123
126
  button[data-action="export-user-response"] { background: Highlight; border-color: Highlight; color: HighlightText; font-weight: 600; }
124
127
  button[data-action="export-user-response"]:hover { background: color-mix(in srgb, Highlight 82%, CanvasText); }
128
+ .back-to-top-wrap {
129
+ position: fixed;
130
+ right: 1.2rem;
131
+ bottom: 1.2rem;
132
+ z-index: 15;
133
+ display: flex;
134
+ flex-direction: column;
135
+ align-items: flex-end;
136
+ }
137
+ .back-to-top {
138
+ padding: .55rem .9rem;
139
+ border-radius: 8px;
140
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
141
+ background: color-mix(in srgb, Canvas 92%, CanvasText 8%);
142
+ color: CanvasText;
143
+ text-decoration: none;
144
+ font: inherit;
145
+ font-size: .9rem;
146
+ box-shadow: 0 2px 8px color-mix(in srgb, CanvasText 18%, transparent);
147
+ }
148
+ .back-to-top:hover { background: color-mix(in srgb, CanvasText 12%, Canvas); }
149
+ .back-to-top:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
150
+ .back-to-top-index {
151
+ display: none;
152
+ box-sizing: border-box;
153
+ width: min(22rem, calc(100vw - 2.4rem));
154
+ max-height: min(70vh, 32rem);
155
+ overflow: auto;
156
+ margin: 0 0 .4rem;
157
+ padding: .8rem 1rem;
158
+ border-radius: 12px;
159
+ border: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
160
+ background: Canvas;
161
+ box-shadow: 0 8px 24px color-mix(in srgb, CanvasText 22%, transparent);
162
+ }
163
+ .back-to-top-index ol { margin: 0; padding-left: 1.2rem; }
164
+ .back-to-top-index li { margin: .25em 0; }
165
+ .back-to-top-index a { color: inherit; }
166
+ @media (hover: hover) {
167
+ .back-to-top-wrap:hover .back-to-top-index,
168
+ .back-to-top-wrap:focus-within .back-to-top-index { display: block; }
169
+ }
125
170
  /* A seven-column table needs 42em of floor, more than a phone can give. */
126
171
  @media (max-width: 640px) { section { padding: 1rem; } .visualization { display: none; } th, td { min-width: 4.5em; } nav.report-index ol { columns: 1; } }
127
- @media print { .skip-link, script { display: none !important; } body { color: #000; background: #fff; } section { break-inside: avoid; border-color: #bbb; } .visualization-fallback { display: table; } }
172
+ @media print { .skip-link, .back-to-top-wrap, script { display: none !important; } body { color: #000; background: #fff; } section { break-inside: avoid; border-color: #bbb; } .visualization-fallback { display: table; } }
128
173
  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,14 +1,14 @@
1
1
  <!DOCTYPE html>
2
2
  {% from "html/macros/forms.html" import clarification_responses %}
3
3
  {% from "html/macros/layout.html" import row_key %}
4
- <html lang="en" data-task-template="{{ taskType }}">
4
+ <html lang="{{ lang }}" data-task-template="{{ taskType }}">
5
5
  <head>
6
6
  <meta charset="utf-8">
7
7
  <meta name="viewport" content="width=device-width,initial-scale=1">
8
8
  <title>{{ (runMeta.task_key.split(':') | last)[:16] }} · {{ taskType }} #{{ runMeta.seq }}</title>
9
9
  <style>{{ css | safe }}</style>
10
10
  </head>
11
- <body>
11
+ <body id="top">
12
12
  <a class="skip-link" href="#main-content">{{ t('base.skip-to-report-content') }}</a>
13
13
  <header class="human-report-header">
14
14
  <p class="eyebrow">{{ taskType }}</p>
@@ -20,7 +20,6 @@
20
20
  {% if reportMeta.elapsed %}<div><dt>{{ t('base.elapsed') }}</dt><dd data-report-meta="elapsed">{{ reportMeta.elapsed }}</dd></div>{% endif %}
21
21
  </dl>
22
22
  <p class="lede">{{ humanSummary.headline | inline_code }}</p>
23
- <p>{{ humanSummary.outcome | inline_code }}</p>
24
23
  {% if executionRoles %}
25
24
  <section data-report-section="execution-roles">
26
25
  <h2>{{ t('base.execution-roles') }}</h2>
@@ -52,7 +51,7 @@
52
51
  <ol class="ledger">
53
52
  {% for row_id, row in evidenceIndex.items() %}
54
53
  <li class="ledger-item" id="id-{{ row_id }}">
55
- <p class="ledger-key"><span class="ledger-id">{{ row_id }}</span><span class="ledger-kind">{{ row.kind | enum_label('ledgerKind') }}</span></p>
54
+ <p class="ledger-key"><span class="ledger-id">{{ row_id | inline_code }}</span><span class="ledger-kind">{{ row.kind | enum_label('ledgerKind') }}</span></p>
56
55
  <p class="ledger-text">{{ row.text | inline_code }}</p>
57
56
  {% if row.codeEvidence %}<p class="ledger-source"><span>{{ t('base.source') }}</span> {{ row.codeEvidence | code_evidence }}</p>
58
57
  {% elif row.source %}<p class="ledger-source"><span>{{ t('base.source') }}</span> {{ row.source | inline_code }}</p>{% endif %}
@@ -115,6 +114,10 @@
115
114
  <p class="user-response-hint">{{ t('base.export-downloads') }} <code>user-response-{{ runMeta.task_type }}-{{ runMeta.seq }}.md</code>{{ t('base.drop-that-file-into') }} <code>runs/{{ runMeta.task_type }}/user-responses/</code> {{ t('base.and-the-next-run-picks-your-answers-up-on-it') }}</p>
116
115
  <pre id="user-response-output" aria-live="polite"></pre>
117
116
  </footer>{% endif %}
117
+ <div class="back-to-top-wrap">
118
+ <nav class="back-to-top-index" aria-label="{{ t('base.contents') }}"><!--report-index-items--></nav>
119
+ <a class="back-to-top" href="#top">{{ t('base.back-to-top') }}</a>
120
+ </div>
118
121
  <script id="run-meta" type="application/json">{{ {
119
122
  "task-key": runMeta.task_key,
120
123
  "task-type": runMeta.task_type,
@@ -81,7 +81,8 @@
81
81
  "cross-check-consensus": "Agreed across workers",
82
82
  "cross-check-dissent": "Workers disagreed",
83
83
  "missing-information": "Missing information",
84
- "follow-up": "Follow-up"
84
+ "follow-up": "Follow-up",
85
+ "plan-item": "Plan item"
85
86
  }
86
87
  },
87
88
  "enumHint": {
@@ -94,6 +95,7 @@
94
95
  },
95
96
  "base": {
96
97
  "skip-to-report-content": "Skip to report content",
98
+ "back-to-top": "Back to top",
97
99
  "contents": "Contents",
98
100
  "task": "Task",
99
101
  "execution-roles": "Execution roles",
@@ -153,6 +155,8 @@
153
155
  "answer-as": "Answer as",
154
156
  "questions-waiting-on-you": "Questions waiting on you",
155
157
  "count-questions-waiting-on-you": "{count} questions waiting on you",
158
+ "answered-questions": "Answered questions",
159
+ "count-answered-questions": "{count} answered questions",
156
160
  "your-answer-to-id": "Your answer to {id}",
157
161
  "choose-one": "Choose one",
158
162
  "recommended": "Recommended",
@@ -165,7 +169,45 @@
165
169
  "why-required-for-changes-or-rejection": "Why — required when you send it back or reject it",
166
170
  "answer-the-blockers-then-regenerate": "Answer {ids}, export your answers, then regenerate the report with the clarification resume command.",
167
171
  "selected-direction-invalidated": "Selected direction invalidated",
168
- "return-to-option-selection": "This plan cannot be approved. Re-enter implementation option selection with:"
172
+ "return-to-option-selection": "This plan cannot be approved. Re-enter implementation option selection with:",
173
+ "select-an-implementation-direction": "Select an implementation direction",
174
+ "direction": "Direction",
175
+ "confirmed": "Confirmed",
176
+ "selection-note": "Selection note",
177
+ "constraints": "Constraints"
178
+ },
179
+ "layout": {
180
+ "evidence": "Evidence:",
181
+ "supporting-evidence": "Supporting evidence:",
182
+ "id": "ID",
183
+ "name": "Name",
184
+ "kind": "Kind",
185
+ "status": "Status",
186
+ "source": "Source",
187
+ "subject": "Subject",
188
+ "answer": "Answer",
189
+ "disposition": "Disposition",
190
+ "sha": "SHA",
191
+ "order": "Order",
192
+ "plan-step": "Plan step",
193
+ "proposed-by": "Proposed by",
194
+ "lens": "Lens",
195
+ "result": "Result",
196
+ "version": "Version",
197
+ "version-source": "Version source",
198
+ "symbol": "Symbol",
199
+ "owner": "Owner",
200
+ "adapter": "Adapter",
201
+ "direction": "Direction",
202
+ "area": "Area",
203
+ "confidence": "Confidence",
204
+ "impact": "Impact",
205
+ "effort": "Effort",
206
+ "risk": "Risk",
207
+ "checked": "checked",
208
+ "none": "none",
209
+ "coverage-percent": "Coverage",
210
+ "scope-precision-percent": "Scope precision"
169
211
  },
170
212
  "visualizations": {
171
213
  "component": "Component",
@@ -201,7 +243,8 @@
201
243
  "what-this-does-to-operations": "What this does to operations",
202
244
  "area": "Area",
203
245
  "impact": "Impact",
204
- "there-is-no-operational-impact": "There is no operational impact."
246
+ "there-is-no-operational-impact": "There is no operational impact.",
247
+ "unsettled": "Unsettled"
205
248
  },
206
249
  "error-analysis": {
207
250
  "what-you-saw": "What you saw",
@@ -217,7 +260,8 @@
217
260
  "signal-that-rejects-it": "Signal that rejects it:",
218
261
  "next-step": "Next step",
219
262
  "recommended-task": "Recommended task",
220
- "the-cause-this-rests-on": "The cause this rests on"
263
+ "the-cause-this-rests-on": "The cause this rests on",
264
+ "run": "Run"
221
265
  },
222
266
  "feature-analysis": {
223
267
  "feature-overview": "Feature overview",
@@ -234,7 +278,10 @@
234
278
  "nothing-was-recorded": "Nothing was recorded.",
235
279
  "state-once-it-is-done": "State once it is done",
236
280
  "permissions-and-flags": "Permissions and flags",
237
- "non-functional-concerns": "Non-functional concerns"
281
+ "non-functional-concerns": "Non-functional concerns",
282
+ "on-failure": "On failure:",
283
+ "tests": "Tests:",
284
+ "gaps": "Gaps:"
238
285
  },
239
286
  "final-verification": {
240
287
  "verification-verdict": "Verification verdict",
@@ -276,7 +323,15 @@
276
323
  "candidate": "Candidate",
277
324
  "disposition": "Disposition",
278
325
  "reason": "Reason",
279
- "no-audit-entries": "No candidate was merged or rejected."
326
+ "no-audit-entries": "No candidate was merged or rejected.",
327
+ "selection-status": "Selection status",
328
+ "mode": "Mode",
329
+ "routing": "Routing",
330
+ "preselected-direction": "Preselected direction",
331
+ "option-id": "Option ID",
332
+ "direction": "Direction",
333
+ "confirmation-evidence": "Confirmation evidence",
334
+ "citation": "Citation"
280
335
  },
281
336
  "implementation-planning": {
282
337
  "what-the-plan-is-for": "What the plan is for",
@@ -355,7 +410,24 @@
355
410
  "what-each-agent-did": "What each agent did",
356
411
  "commands-and-evidence": "Commands and evidence",
357
412
  "unblock-condition": "Unblock condition:",
358
- "agent-evidence": "Agent evidence:"
413
+ "agent-evidence": "Agent evidence:",
414
+ "selected-direction": "Selected direction",
415
+ "source-report": "Source report",
416
+ "snapshot": "Snapshot",
417
+ "direction-realization": "Direction realization",
418
+ "core-mechanism": "Core mechanism",
419
+ "interfaces": "Interfaces",
420
+ "plan-coverage": "Plan coverage",
421
+ "coverage-percent": "Coverage",
422
+ "scope-precision-percent": "Scope precision",
423
+ "direction-invalidated": "Direction invalidated",
424
+ "code-evidence": "Code evidence",
425
+ "re-entry-route": "Re-entry route",
426
+ "original-requirement": "Original requirement",
427
+ "stages": "Stages",
428
+ "status": "Status",
429
+ "extract": "Extract",
430
+ "keep": "Keep as-is"
359
431
  },
360
432
  "implementation": {
361
433
  "what-was-delivered": "What was delivered",
@@ -366,6 +438,7 @@
366
438
  "implemented-by": "Implemented by",
367
439
  "verification-result": "Verification result",
368
440
  "independent-check": "Independent check:",
441
+ "exit-code": "exit code",
369
442
  "what-is-left": "What is left",
370
443
  "nothing-was-changed-outside-the-plan": "Nothing was changed outside the plan.",
371
444
  "commits-delivered": "Commits delivered",
@@ -453,6 +526,7 @@
453
526
  "pr-result": "PR Result:",
454
527
  "pull-request-open": "Pull request Open",
455
528
  "conflict-verdict": "Conflict verdict",
529
+ "base-branch": "Base branch:",
456
530
  "commits-delivered": "Commits delivered",
457
531
  "there-are-no-commits": "There are no commits.",
458
532
  "commit": "Commit",
@@ -479,7 +553,11 @@
479
553
  "next-task": "Next task:",
480
554
  "this-requirements-task-stands-alone-nothing": "This requirements task stands alone; nothing else feeds it.",
481
555
  "decisions-that-are-yours": "Decisions that are yours",
482
- "no-further-decision-is-needed": "No further decision is needed."
556
+ "no-further-decision-is-needed": "No further decision is needed.",
557
+ "settled": "Settled",
558
+ "decision-needed": "Decision needed",
559
+ "glossary": "Glossary",
560
+ "decision-record": "Decision record"
483
561
  }
484
562
  }
485
563
  }
@@ -81,7 +81,8 @@
81
81
  "cross-check-consensus": "작업자 간 합의",
82
82
  "cross-check-dissent": "작업자 간 이견",
83
83
  "missing-information": "빠진 정보",
84
- "follow-up": "후속 작업"
84
+ "follow-up": "후속 작업",
85
+ "plan-item": "계획 항목"
85
86
  }
86
87
  },
87
88
  "enumHint": {
@@ -94,6 +95,7 @@
94
95
  },
95
96
  "base": {
96
97
  "skip-to-report-content": "리포트 본문으로 건너뛰기",
98
+ "back-to-top": "맨 위로",
97
99
  "contents": "목차",
98
100
  "task": "태스크",
99
101
  "execution-roles": "실행 역할",
@@ -153,6 +155,8 @@
153
155
  "answer-as": "답변 형식",
154
156
  "questions-waiting-on-you": "답변을 기다리는 질문",
155
157
  "count-questions-waiting-on-you": "답변을 기다리는 질문 {count}건",
158
+ "answered-questions": "답한 질문",
159
+ "count-answered-questions": "답한 질문 {count}건",
156
160
  "your-answer-to-id": "{id}에 대한 답변",
157
161
  "choose-one": "하나를 선택하세요",
158
162
  "recommended": "권장",
@@ -165,7 +169,45 @@
165
169
  "why-required-for-changes-or-rejection": "사유 — 반려하거나 다시 고치게 할 때는 반드시 적어야 합니다",
166
170
  "answer-the-blockers-then-regenerate": "{ids}에 답한 뒤 답변을 내보내고, clarification resume 명령으로 리포트를 다시 만드세요.",
167
171
  "selected-direction-invalidated": "선택 방향 무효화",
168
- "return-to-option-selection": "이 계획은 승인할 수 없습니다. 다음 명령으로 구현 방향 선택 단계에 다시 진입하세요:"
172
+ "return-to-option-selection": "이 계획은 승인할 수 없습니다. 다음 명령으로 구현 방향 선택 단계에 다시 진입하세요:",
173
+ "select-an-implementation-direction": "구현 방향을 선택하세요",
174
+ "direction": "방향",
175
+ "confirmed": "확인함",
176
+ "selection-note": "선택 메모",
177
+ "constraints": "제약"
178
+ },
179
+ "layout": {
180
+ "evidence": "근거:",
181
+ "supporting-evidence": "뒷받침하는 근거:",
182
+ "id": "아이디",
183
+ "name": "이름",
184
+ "kind": "종류",
185
+ "status": "상태",
186
+ "source": "출처",
187
+ "subject": "제목",
188
+ "answer": "답변",
189
+ "disposition": "처리",
190
+ "sha": "SHA",
191
+ "order": "순서",
192
+ "plan-step": "계획 단계",
193
+ "proposed-by": "제안",
194
+ "lens": "렌즈",
195
+ "result": "결과",
196
+ "version": "버전",
197
+ "version-source": "버전 출처",
198
+ "symbol": "심볼",
199
+ "owner": "소유",
200
+ "adapter": "어댑터",
201
+ "direction": "방향",
202
+ "area": "영역",
203
+ "confidence": "확신도",
204
+ "impact": "영향",
205
+ "effort": "노력",
206
+ "risk": "위험",
207
+ "checked": "확인함",
208
+ "none": "없음",
209
+ "coverage-percent": "커버리지",
210
+ "scope-precision-percent": "범위 정밀도"
169
211
  },
170
212
  "visualizations": {
171
213
  "component": "구성 요소",
@@ -201,7 +243,8 @@
201
243
  "what-this-does-to-operations": "운영에 미치는 영향",
202
244
  "area": "영역",
203
245
  "impact": "영향",
204
- "there-is-no-operational-impact": "운영에 미치는 영향이 없습니다."
246
+ "there-is-no-operational-impact": "운영에 미치는 영향이 없습니다.",
247
+ "unsettled": "미결"
205
248
  },
206
249
  "error-analysis": {
207
250
  "what-you-saw": "관측된 증상",
@@ -217,7 +260,8 @@
217
260
  "signal-that-rejects-it": "기각하는 신호:",
218
261
  "next-step": "다음 단계",
219
262
  "recommended-task": "권장 태스크",
220
- "the-cause-this-rests-on": "이 판단이 근거하는 원인"
263
+ "the-cause-this-rests-on": "이 판단이 근거하는 원인",
264
+ "run": "실행"
221
265
  },
222
266
  "feature-analysis": {
223
267
  "feature-overview": "기능 개요",
@@ -234,7 +278,10 @@
234
278
  "nothing-was-recorded": "기록된 것이 없습니다.",
235
279
  "state-once-it-is-done": "끝난 뒤의 상태",
236
280
  "permissions-and-flags": "권한과 플래그",
237
- "non-functional-concerns": "비기능 요건"
281
+ "non-functional-concerns": "비기능 요건",
282
+ "on-failure": "실패 시:",
283
+ "tests": "테스트:",
284
+ "gaps": "공백:"
238
285
  },
239
286
  "final-verification": {
240
287
  "verification-verdict": "검증 판정",
@@ -276,7 +323,15 @@
276
323
  "candidate": "후보",
277
324
  "disposition": "처리",
278
325
  "reason": "이유",
279
- "no-audit-entries": "병합되거나 탈락한 후보가 없습니다."
326
+ "no-audit-entries": "병합되거나 탈락한 후보가 없습니다.",
327
+ "selection-status": "선택 상태",
328
+ "mode": "모드",
329
+ "routing": "라우팅",
330
+ "preselected-direction": "미리 고른 방향",
331
+ "option-id": "옵션 아이디",
332
+ "direction": "방향",
333
+ "confirmation-evidence": "확인 근거",
334
+ "citation": "인용"
280
335
  },
281
336
  "implementation-planning": {
282
337
  "what-the-plan-is-for": "이 계획이 하려는 것",
@@ -355,7 +410,24 @@
355
410
  "what-each-agent-did": "에이전트별 수행 내용",
356
411
  "commands-and-evidence": "명령과 근거",
357
412
  "unblock-condition": "승인 해제 조건:",
358
- "agent-evidence": "에이전트 근거:"
413
+ "agent-evidence": "에이전트 근거:",
414
+ "selected-direction": "선택한 방향",
415
+ "source-report": "원본 보고",
416
+ "snapshot": "스냅샷",
417
+ "direction-realization": "방향 구현",
418
+ "core-mechanism": "핵심 메커니즘",
419
+ "interfaces": "인터페이스",
420
+ "plan-coverage": "계획 커버리지",
421
+ "coverage-percent": "커버리지",
422
+ "scope-precision-percent": "범위 정밀도",
423
+ "direction-invalidated": "방향 무효화",
424
+ "code-evidence": "코드 근거",
425
+ "re-entry-route": "재진입 경로",
426
+ "original-requirement": "원래 요구사항",
427
+ "stages": "stage",
428
+ "status": "상태",
429
+ "extract": "추출",
430
+ "keep": "그대로 둠"
359
431
  },
360
432
  "implementation": {
361
433
  "what-was-delivered": "무엇을 만들었나",
@@ -366,6 +438,7 @@
366
438
  "implemented-by": "구현한 곳",
367
439
  "verification-result": "검증 결과",
368
440
  "independent-check": "독립 검증:",
441
+ "exit-code": "종료 코드",
369
442
  "what-is-left": "남은 것",
370
443
  "nothing-was-changed-outside-the-plan": "계획 밖에서 바뀐 것이 없습니다.",
371
444
  "commits-delivered": "만들어진 commit",
@@ -453,6 +526,7 @@
453
526
  "pr-result": "PR 결과:",
454
527
  "pull-request-open": "Pull request 열기",
455
528
  "conflict-verdict": "충돌 판정",
529
+ "base-branch": "베이스 브랜치:",
456
530
  "commits-delivered": "만들어진 commit",
457
531
  "there-are-no-commits": "commit 이 없습니다.",
458
532
  "commit": "commit",
@@ -479,7 +553,11 @@
479
553
  "next-task": "다음 태스크:",
480
554
  "this-requirements-task-stands-alone-nothing": "이 요구사항 태스크는 독립적입니다. 선행 태스크가 없습니다.",
481
555
  "decisions-that-are-yours": "사용자가 결정할 것",
482
- "no-further-decision-is-needed": "추가로 결정할 것이 없습니다."
556
+ "no-further-decision-is-needed": "추가로 결정할 것이 없습니다.",
557
+ "settled": "확정",
558
+ "decision-needed": "결정 필요",
559
+ "glossary": "용어집",
560
+ "decision-record": "결정 기록"
483
561
  }
484
562
  }
485
563
  }