okstra 0.158.1 → 0.160.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/README.md +1 -1
- package/docs/architecture/storage-model.md +2 -0
- package/docs/architecture.md +1 -1
- package/docs/cli.md +8 -3
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +3 -0
- package/docs/for-ai/skills/okstra-run.md +2 -1
- package/docs/for-ai/skills/okstra-user-response.md +5 -5
- package/docs/project-structure-overview.md +5 -1
- package/docs/task-process/implementation.md +28 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/okstra-claude-exec.sh +4 -1
- package/runtime/prompts/host-orchestration/README.md +18 -0
- package/runtime/prompts/host-orchestration/implementation.md +57 -0
- package/runtime/prompts/launch.template.md +10 -1
- package/runtime/prompts/lead/adapters/claude-code.md +1 -1
- package/runtime/prompts/lead/context-loader.md +5 -2
- package/runtime/prompts/lead/convergence.md +3 -1
- package/runtime/prompts/lead/plan-body-verification.md +21 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/lead/team-contract.md +2 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +11 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -1
- package/runtime/prompts/profiles/implementation-planning.md +2 -0
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +3 -0
- package/runtime/python/okstra_ctl/clarification_items.py +9 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +6 -6
- package/runtime/python/okstra_ctl/convergence.py +168 -11
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -2
- package/runtime/python/okstra_ctl/error_issue.py +640 -0
- package/runtime/python/okstra_ctl/error_report.py +56 -0
- package/runtime/python/okstra_ctl/error_zip.py +23 -10
- package/runtime/python/okstra_ctl/incremental_scope.py +159 -19
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +18 -5
- package/runtime/python/okstra_ctl/issue_signals.py +186 -0
- package/runtime/python/okstra_ctl/paths.py +38 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +167 -3
- package/runtime/python/okstra_ctl/profile_show.py +134 -0
- package/runtime/python/okstra_ctl/recap.py +63 -0
- package/runtime/python/okstra_ctl/render_final_report.py +11 -62
- package/runtime/python/okstra_ctl/report_html/filters.py +6 -1
- package/runtime/python/okstra_ctl/report_html/render.py +9 -8
- package/runtime/python/okstra_ctl/report_html/run_usage.py +110 -0
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +69 -16
- package/runtime/python/okstra_ctl/report_html/visualizations.py +107 -14
- package/runtime/python/okstra_ctl/report_translation.py +4 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -3
- package/runtime/python/okstra_ctl/run.py +41 -2
- package/runtime/python/okstra_ctl/run_audit.py +477 -0
- package/runtime/python/okstra_ctl/usage_cells.py +47 -0
- package/runtime/python/okstra_ctl/user_response.py +25 -10
- package/runtime/python/okstra_ctl/verdict_blocks.py +183 -0
- package/runtime/python/okstra_ctl/wizard.py +64 -10
- package/runtime/python/okstra_ctl/worker_audit_check.py +44 -0
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +207 -0
- package/runtime/python/okstra_ctl/worker_heartbeat.py +9 -3
- package/runtime/python/okstra_ctl/worker_liveness.py +81 -9
- package/runtime/schemas/final-report-v1.0.schema.json +14 -0
- package/runtime/schemas/final-report-v2.0.schema.json +56 -2
- package/runtime/skills/okstra-inspect/SKILL.md +3 -1
- package/runtime/skills/okstra-inspect/facets/error-issue.md +77 -0
- package/runtime/skills/okstra-inspect/facets/run-audit.md +34 -0
- package/runtime/skills/okstra-run/SKILL.md +28 -10
- package/runtime/skills/okstra-user-response/SKILL.md +18 -18
- package/runtime/templates/reports/final-report.template.md +4 -0
- package/runtime/templates/reports/html/assets/base.css +14 -1
- package/runtime/templates/reports/html/base.template.html +42 -0
- package/runtime/templates/reports/html/i18n/en.json +30 -1
- package/runtime/templates/reports/html/i18n/ko.json +30 -1
- package/runtime/templates/reports/html/macros/forms.html +15 -0
- package/runtime/templates/reports/html/macros/visualizations.html +3 -2
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +1 -0
- package/runtime/templates/reports/i18n/en.json +2 -0
- package/runtime/validators/validate-run.py +331 -208
- package/runtime/validators/validate_session_conformance.py +102 -32
- package/src/cli-registry.mjs +34 -0
- package/src/commands/execute/incremental-scope.mjs +10 -0
- package/src/commands/execute/worker-audit-check.mjs +35 -0
- package/src/commands/inspect/error-issue.mjs +27 -0
- package/src/commands/inspect/profile-show.mjs +29 -0
- package/src/commands/inspect/run-audit.mjs +26 -0
|
@@ -54,6 +54,14 @@ DEFAULT_LAUNCH_GRACE_SECONDS = 60
|
|
|
54
54
|
DEFAULT_POLL_INTERVAL_SECONDS = 20.0
|
|
55
55
|
DEFAULT_WAIT_TIMEOUT_SECONDS = 2400.0
|
|
56
56
|
|
|
57
|
+
# A budget breach is one observation, and "slow" and "dead" are only
|
|
58
|
+
# distinguishable across two. On a breach the probe re-reads the sidecar this
|
|
59
|
+
# far into the future — a fraction of the stage's own budget, so a stage with a
|
|
60
|
+
# longer budget also gets a longer confirmation. Measured false positives this
|
|
61
|
+
# absorbs (dev-10400): `analysis` 386s against a 360s budget,
|
|
62
|
+
# `data-json-write-start` 1602s against 1260s.
|
|
63
|
+
DEFAULT_STALL_CONFIRM_RATIO = 0.5
|
|
64
|
+
|
|
57
65
|
|
|
58
66
|
def _utc_now() -> datetime:
|
|
59
67
|
return datetime.now(timezone.utc)
|
|
@@ -183,13 +191,58 @@ class ProbeTarget:
|
|
|
183
191
|
result_path: Path | None = None
|
|
184
192
|
|
|
185
193
|
|
|
186
|
-
def
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
def _confirm_window(probe: dict, stall_confirm: float | None) -> float:
|
|
195
|
+
"""Seconds to wait before a budget breach becomes a verdict.
|
|
196
|
+
|
|
197
|
+
Only a breach carries ``budgetSeconds``. The other stalled shapes — a
|
|
198
|
+
sidecar with no heartbeat at all, a newest beat that predates this dispatch
|
|
199
|
+
— are not "the worker is mid-tool-call", so waiting tells us nothing new
|
|
200
|
+
about them.
|
|
201
|
+
"""
|
|
202
|
+
if "budgetSeconds" not in probe:
|
|
203
|
+
return 0.0
|
|
204
|
+
if stall_confirm is not None:
|
|
205
|
+
return max(0.0, float(stall_confirm))
|
|
206
|
+
return probe["budgetSeconds"] * DEFAULT_STALL_CONFIRM_RATIO
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def probe_one(
|
|
210
|
+
target: ProbeTarget,
|
|
211
|
+
*,
|
|
212
|
+
now: datetime,
|
|
213
|
+
max_idle: float,
|
|
214
|
+
launch_grace: float,
|
|
215
|
+
stall_confirm: float | None = None,
|
|
216
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
217
|
+
clock: Callable[[], datetime] = _utc_now,
|
|
218
|
+
) -> dict:
|
|
219
|
+
"""One worker's verdict, with a budget breach confirmed before it stands.
|
|
220
|
+
|
|
221
|
+
The wait this costs is bounded by the confirmation window; what the probe
|
|
222
|
+
exists to avoid is paying ``DEFAULT_WAIT_TIMEOUT_SECONDS`` for a worker that
|
|
223
|
+
died early, and that is still never paid.
|
|
224
|
+
"""
|
|
225
|
+
if target.liveness_mode != LIVENESS_AUDIT_HEARTBEAT:
|
|
226
|
+
return probe_launch(target.artifact, target.dispatched_at, now, launch_grace)
|
|
227
|
+
probe = probe_heartbeat(
|
|
228
|
+
target.artifact, target.dispatched_at, now, max_idle, launch_grace
|
|
229
|
+
)
|
|
230
|
+
if probe["state"] != "stalled":
|
|
231
|
+
return probe
|
|
232
|
+
window = _confirm_window(probe, stall_confirm)
|
|
233
|
+
if window <= 0:
|
|
234
|
+
return probe
|
|
235
|
+
sleep(window)
|
|
236
|
+
confirmed = probe_heartbeat(
|
|
237
|
+
target.artifact, target.dispatched_at, clock(), max_idle, launch_grace
|
|
238
|
+
)
|
|
239
|
+
if confirmed.get("lastHeartbeat") == probe.get("lastHeartbeat"):
|
|
240
|
+
return confirmed
|
|
241
|
+
# The question the window asks is whether the heartbeat moved, not whether
|
|
242
|
+
# the re-read is healthy on its own terms. A beat opening a stage with a
|
|
243
|
+
# smaller budget than the window we just slept reads as stale the instant it
|
|
244
|
+
# lands, which would call a worker dead for proving it is alive.
|
|
245
|
+
return {**confirmed, "state": "live", "reason": ""}
|
|
193
246
|
|
|
194
247
|
|
|
195
248
|
def probe_all(
|
|
@@ -198,9 +251,15 @@ def probe_all(
|
|
|
198
251
|
now: datetime,
|
|
199
252
|
max_idle: float,
|
|
200
253
|
launch_grace: float,
|
|
254
|
+
stall_confirm: float | None = None,
|
|
255
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
256
|
+
clock: Callable[[], datetime] = _utc_now,
|
|
201
257
|
) -> dict:
|
|
202
258
|
probes = [
|
|
203
|
-
probe_one(
|
|
259
|
+
probe_one(
|
|
260
|
+
t, now=now, max_idle=max_idle, launch_grace=launch_grace,
|
|
261
|
+
stall_confirm=stall_confirm, sleep=sleep, clock=clock,
|
|
262
|
+
)
|
|
204
263
|
for t in targets
|
|
205
264
|
]
|
|
206
265
|
unhealthy = [p for p in probes if p["state"] in ("stalled", "did-not-launch")]
|
|
@@ -221,6 +280,7 @@ def wait_for_results(
|
|
|
221
280
|
launch_grace: float,
|
|
222
281
|
interval: float,
|
|
223
282
|
timeout: float,
|
|
283
|
+
stall_confirm: float | None = None,
|
|
224
284
|
clock: Callable[[], datetime] = _utc_now,
|
|
225
285
|
sleep: Callable[[float], None] = time.sleep,
|
|
226
286
|
) -> dict:
|
|
@@ -239,7 +299,8 @@ def wait_for_results(
|
|
|
239
299
|
while True:
|
|
240
300
|
now = clock()
|
|
241
301
|
result = probe_all(
|
|
242
|
-
targets, now=now, max_idle=max_idle, launch_grace=launch_grace
|
|
302
|
+
targets, now=now, max_idle=max_idle, launch_grace=launch_grace,
|
|
303
|
+
stall_confirm=stall_confirm, sleep=sleep, clock=clock,
|
|
243
304
|
)
|
|
244
305
|
pending = [
|
|
245
306
|
str(t.result_path) for t in targets if not result_ready(t)
|
|
@@ -367,6 +428,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
367
428
|
help="--wait poll interval in seconds")
|
|
368
429
|
parser.add_argument("--timeout", type=float, default=DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
369
430
|
help="--wait deadline in seconds")
|
|
431
|
+
parser.add_argument(
|
|
432
|
+
"--stall-confirm", type=float, default=None,
|
|
433
|
+
help=(
|
|
434
|
+
"seconds to re-check a heartbeat budget breach before calling it "
|
|
435
|
+
"stalled (default: half that stage's budget; 0 disables). A slow "
|
|
436
|
+
"worker appends its next heartbeat inside this window; a dead one "
|
|
437
|
+
"does not."
|
|
438
|
+
),
|
|
439
|
+
)
|
|
370
440
|
args = parser.parse_args(argv)
|
|
371
441
|
|
|
372
442
|
if len(args.team_state) != len(args.worker):
|
|
@@ -388,6 +458,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
388
458
|
now=_utc_now(),
|
|
389
459
|
max_idle=args.max_idle,
|
|
390
460
|
launch_grace=args.launch_grace,
|
|
461
|
+
stall_confirm=args.stall_confirm,
|
|
391
462
|
)
|
|
392
463
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
393
464
|
# Non-zero on an unhealthy worker so a poll loop can branch on the exit
|
|
@@ -410,6 +481,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
410
481
|
launch_grace=args.launch_grace,
|
|
411
482
|
interval=args.interval,
|
|
412
483
|
timeout=args.timeout,
|
|
484
|
+
stall_confirm=args.stall_confirm,
|
|
413
485
|
)
|
|
414
486
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
415
487
|
return {"completed": 0, "unhealthy": 1}.get(result["outcome"], 2)
|
|
@@ -2959,6 +2959,20 @@
|
|
|
2959
2959
|
"required": ["roundCount", "gateResult", "planItems", "dissentLog"],
|
|
2960
2960
|
"additionalProperties": false,
|
|
2961
2961
|
"properties": {
|
|
2962
|
+
"uniformVerifiers": {
|
|
2963
|
+
"type": "array",
|
|
2964
|
+
"description": "Verifiers whose every vote this round was one verdict. Advisory: a unanimous round is legitimate, but the gate reads as a three-way cross-check unless this sits beside it.",
|
|
2965
|
+
"items": {
|
|
2966
|
+
"type": "object",
|
|
2967
|
+
"additionalProperties": false,
|
|
2968
|
+
"required": ["worker", "verdict", "itemCount"],
|
|
2969
|
+
"properties": {
|
|
2970
|
+
"worker": { "type": "string", "minLength": 1 },
|
|
2971
|
+
"verdict": { "type": "string", "minLength": 1 },
|
|
2972
|
+
"itemCount": { "type": "integer", "minimum": 1 }
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
},
|
|
2962
2976
|
"roundCount": { "type": "integer", "minimum": 0 },
|
|
2963
2977
|
"gateResult": {
|
|
2964
2978
|
"enum": [
|
|
@@ -1637,7 +1637,11 @@
|
|
|
1637
1637
|
"items": { "type": "string" }
|
|
1638
1638
|
},
|
|
1639
1639
|
"confidence": { "enum": ["low", "medium", "high"] },
|
|
1640
|
-
"disproveWith": { "type": "string", "minLength": 1 }
|
|
1640
|
+
"disproveWith": { "type": "string", "minLength": 1 },
|
|
1641
|
+
"downstreamOf": {
|
|
1642
|
+
"type": "array",
|
|
1643
|
+
"items": { "type": "string", "pattern": "^EA-\\d{3,}$" }
|
|
1644
|
+
}
|
|
1641
1645
|
}
|
|
1642
1646
|
}
|
|
1643
1647
|
},
|
|
@@ -1699,6 +1703,28 @@
|
|
|
1699
1703
|
"enum": ["open", "answered", "resolved", "obsolete"]
|
|
1700
1704
|
},
|
|
1701
1705
|
|
|
1706
|
+
"ClarificationScopeToken": {
|
|
1707
|
+
"enum": ["in-repo", "cross-repo", "new-schema", "deferrable"]
|
|
1708
|
+
},
|
|
1709
|
+
|
|
1710
|
+
"ClarificationOption": {
|
|
1711
|
+
"type": "object",
|
|
1712
|
+
"required": ["role", "answer", "rationale", "scopeImpact", "addedWork", "directionChange"],
|
|
1713
|
+
"additionalProperties": false,
|
|
1714
|
+
"properties": {
|
|
1715
|
+
"role": { "enum": ["recommended", "alternative"] },
|
|
1716
|
+
"answer": { "type": "string", "minLength": 1 },
|
|
1717
|
+
"rationale": { "type": "string", "minLength": 1 },
|
|
1718
|
+
"scopeImpact": {
|
|
1719
|
+
"type": "array",
|
|
1720
|
+
"minItems": 1,
|
|
1721
|
+
"items": { "$ref": "#/$defs/ClarificationScopeToken" }
|
|
1722
|
+
},
|
|
1723
|
+
"addedWork": { "type": "string", "minLength": 1 },
|
|
1724
|
+
"directionChange": { "type": "string", "minLength": 1 }
|
|
1725
|
+
}
|
|
1726
|
+
},
|
|
1727
|
+
|
|
1702
1728
|
"WorkerStatus": {
|
|
1703
1729
|
"enum": ["completed", "error", "timeout", "not-run", "synthesis-only"]
|
|
1704
1730
|
},
|
|
@@ -3736,6 +3762,20 @@
|
|
|
3736
3762
|
"required": ["roundCount", "gateResult", "planItems", "dissentLog"],
|
|
3737
3763
|
"additionalProperties": false,
|
|
3738
3764
|
"properties": {
|
|
3765
|
+
"uniformVerifiers": {
|
|
3766
|
+
"type": "array",
|
|
3767
|
+
"description": "Verifiers whose every vote this round was one verdict. Advisory: a unanimous round is legitimate, but the gate reads as a three-way cross-check unless this sits beside it.",
|
|
3768
|
+
"items": {
|
|
3769
|
+
"type": "object",
|
|
3770
|
+
"additionalProperties": false,
|
|
3771
|
+
"required": ["worker", "verdict", "itemCount"],
|
|
3772
|
+
"properties": {
|
|
3773
|
+
"worker": { "type": "string", "minLength": 1 },
|
|
3774
|
+
"verdict": { "type": "string", "minLength": 1 },
|
|
3775
|
+
"itemCount": { "type": "integer", "minimum": 1 }
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
},
|
|
3739
3779
|
"roundCount": { "type": "integer", "minimum": 0 },
|
|
3740
3780
|
"gateResult": {
|
|
3741
3781
|
"enum": [
|
|
@@ -4074,6 +4114,15 @@
|
|
|
4074
4114
|
"type": "object",
|
|
4075
4115
|
"required": ["id", "ticketId", "kind", "statement", "expectedForm", "blocks", "status"],
|
|
4076
4116
|
"additionalProperties": false,
|
|
4117
|
+
"allOf": [
|
|
4118
|
+
{
|
|
4119
|
+
"if": {
|
|
4120
|
+
"properties": { "kind": { "const": "decision" } },
|
|
4121
|
+
"required": ["kind"]
|
|
4122
|
+
},
|
|
4123
|
+
"then": { "required": ["options"] }
|
|
4124
|
+
}
|
|
4125
|
+
],
|
|
4077
4126
|
"properties": {
|
|
4078
4127
|
"id": { "type": "string", "pattern": "^C-\\d{3,}$" },
|
|
4079
4128
|
"ticketId": { "$ref": "#/$defs/TicketId" },
|
|
@@ -4082,7 +4131,12 @@
|
|
|
4082
4131
|
"expectedForm": { "type": "string", "minLength": 1 },
|
|
4083
4132
|
"blocks": { "$ref": "#/$defs/ClarificationBlocks" },
|
|
4084
4133
|
"status": { "$ref": "#/$defs/ClarificationStatus" },
|
|
4085
|
-
"userInput": { "type": "string" }
|
|
4134
|
+
"userInput": { "type": "string" },
|
|
4135
|
+
"options": {
|
|
4136
|
+
"type": "array",
|
|
4137
|
+
"minItems": 2,
|
|
4138
|
+
"items": { "$ref": "#/$defs/ClarificationOption" }
|
|
4139
|
+
}
|
|
4086
4140
|
}
|
|
4087
4141
|
},
|
|
4088
4142
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-inspect
|
|
3
3
|
description: >-
|
|
4
|
-
Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (verdict / pass); its elapsed time or context/read cost; its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (error report). Also
|
|
4
|
+
Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (verdict / pass); its elapsed time or context/read cost; its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (error report). Also turns cross-project okstra errors into an anonymized zip (error feedback) or GitHub issues (file okstra issues), and audits run health across tasks (run audit). NOT for starting a run (okstra-run), rollups/schedules (okstra-rollup / okstra-schedule-gen), a brief (okstra-brief-gen), setup (okstra-setup), or cross-project management (okstra-manager).
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA Inspect
|
|
@@ -18,6 +18,8 @@ Single read-side entry point for okstra runtime inspection plus the one status m
|
|
|
18
18
|
| `cost` | `facets/cost.md` | Estimate file/read context cost for a task bundle. |
|
|
19
19
|
| `errors` | `facets/errors.md` | Aggregate okstra-run error logs for a task into a timestamped markdown report; print a summary. |
|
|
20
20
|
| `error-zip` | `facets/error-zip.md` | Collect cross-project okstra error logs into an anonymized zip (report + raw) and summarize clusters. |
|
|
21
|
+
| `error-issue` | `facets/error-issue.md` | Turn cross-project okstra anomalies into GitHub issue candidates; file the approved ones after explicit user approval. |
|
|
22
|
+
| `run-audit` | `facets/run-audit.md` | Check every run's artifacts against progress invariants; report what went wrong without an error ever being logged. |
|
|
21
23
|
| `recap` | `facets/recap.md` | Summarize a task's run-to-run phase transitions, then answer free-form questions over its `.okstra` artifacts. Appends each summary/Q&A to `recap/recap-log.jsonl`, and writes agent-authored notes to `notes/` for feeding into later runs. |
|
|
22
24
|
|
|
23
25
|
## Step 0: Preflight (shared)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# okstra-inspect facet — error-issue
|
|
2
|
+
|
|
3
|
+
Loaded lazily by the dispatch table in `SKILL.md` (core). Shared rules — Step 0 preflight, the standard task-key resolution rule (0/1/N), the no-task fallback, and Output Rules — live in the core file and still apply here.
|
|
4
|
+
|
|
5
|
+
## error-issue
|
|
6
|
+
|
|
7
|
+
Trigger phrases: "okstra error-issue", "issue candidates", "file okstra issues", "report okstra defects".
|
|
8
|
+
|
|
9
|
+
Turn okstra run anomalies into GitHub issue candidates, then file the approved ones. Read-only over every target's `.okstra/`. Apart from the plan file you edit between the two commands, the only write is the GitHub issue — and that only after the user approves.
|
|
10
|
+
|
|
11
|
+
**Never run `submit` without an explicit user approval in this session.** `plan` is free to run unattended; `submit` is not.
|
|
12
|
+
|
|
13
|
+
### error-issue.1 — Build the plan
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
okstra error-issue plan --out ~/.okstra/error-issue-plan.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Parse the stdout JSON: `candidateCount`, `belowGate`, `notOkstraDefect`, `conflicted`, `unreachableRuns`.
|
|
20
|
+
|
|
21
|
+
`candidateCount` includes candidates whose action is already `skip`, so it is not the number of issues that will be filed. Report the filed count from the create/comment rows of the board below.
|
|
22
|
+
|
|
23
|
+
### error-issue.2 — Review each candidate's classification
|
|
24
|
+
|
|
25
|
+
Read the plan file. For each candidate, verify the recorded `classification` against its `signals` list. The classification was computed by `issue_signals.classify`; your job is to confirm it reads correctly against the raw evidence, not to invent a new one.
|
|
26
|
+
|
|
27
|
+
If a candidate's signals do not support its classification, drop it from the plan file rather than arguing with it in prose. If deciding requires reading the raw `stderrExcerpt`, add an evidence entry and quote the line — an evidence entry always carries all three of `signal`, `value`, `source`, so write `{"signal": "<the signal it backs>", "value": "<the quoted line>", "source": "message excerpt"}`. `submit` rejects an entry missing any of the three; a two-key entry is not a lighter form of evidence, it is an unreadable one.
|
|
28
|
+
|
|
29
|
+
### error-issue.3 — Present the approval board (Korean)
|
|
30
|
+
|
|
31
|
+
First state the destination on its own line — `plan.json` 의 `repo` 값을 그대로 읽어 `등록 대상: <repo>` 로 적는다. 이 값은 `~/.okstra/error-issue.json` 이 덮어쓸 수 있으므로 okstra 레포라고 단정하지 않는다. CLI 가 `repo` 없는 plan 을 거부하는 이유가 바로 이것 — 목적지는 승인 화면에 반드시 있어야 할 값이다.
|
|
32
|
+
|
|
33
|
+
Then show two tables, never merged into one.
|
|
34
|
+
|
|
35
|
+
**등록 예정** — candidates whose action is `create` or `comment`. `title` 열은 실제로 올라갈 제목 그대로 싣는다. 그것이 없으면 "승인 화면에서 본 것과 올라가는 것이 같다"가 성립하지 않는다:
|
|
36
|
+
|
|
37
|
+
| # | action | title | fingerprint | errorType / phase | 발생 | 지지 신호(`evidence`) | 기존 이슈 |
|
|
38
|
+
|---|---|---|---|---|---:|---|---|
|
|
39
|
+
|
|
40
|
+
**보류** — candidates whose action is already `skip`. They are NOT filed; they are shown so the human sees what was set aside. `skip` has two distinct origins and the row must say which:
|
|
41
|
+
|
|
42
|
+
- **반대 신호** — `conflictingSignals` is non-empty. `classify` called it an okstra defect while another signal pointed elsewhere.
|
|
43
|
+
- **이미 보고됨** — `conflictingSignals` is empty and `existingIssue` is set. An open issue already covers the last occurrence, so there is nothing new to say.
|
|
44
|
+
|
|
45
|
+
| # | fingerprint | errorType / phase | 발생 | 보류 사유 | 지지 신호 | 반대 신호 / 기존 이슈 |
|
|
46
|
+
|---|---|---|---:|---|---|---|
|
|
47
|
+
|
|
48
|
+
**본문** — then, for every `등록 예정` row, print that candidate's `body` **whole and verbatim** in a fenced block, one block per row, labelled with the same `#`. Never elide a section. The table summarizes; the body is what actually gets posted, and "승인 화면에서 본 것과 올라가는 것이 같다" is this facet's whole claim — a title plus a signal list does not carry it.
|
|
49
|
+
|
|
50
|
+
Bodies are bounded by construction: every line is derived (counts, signal values, invariant names) except the one representative message, which shares its source with the `title` in the same row. The raw error records are deliberately NOT in the body — their free text carries the reporting project's ticket ids and source filenames, and no shape rule can separate those from okstra's own vocabulary. They stay in the candidate's `records` in `plan.json` and in the `okstra error-zip` archive. If you need to vet them, read `plan.json` locally; do not paste them into the body.
|
|
51
|
+
|
|
52
|
+
Then ask via `AskUserQuestion` (options: 전부 등록 / 일부만 선택 / 등록 안 함). Deselected candidates are removed from the plan file before submit.
|
|
53
|
+
|
|
54
|
+
A held candidate is promoted only when the user explicitly says so, and the action you set depends on `existingIssue`:
|
|
55
|
+
|
|
56
|
+
- `existingIssue` is null → set `action` to `create`.
|
|
57
|
+
- `existingIssue` is set → set `action` to `comment`. **Never `create` over an existing issue** — that files a duplicate carrying the same `okstra-fingerprint` marker, and from then on `find_existing_issue` matches whichever of the two the list returns first, so every later run reports against an arbitrary one.
|
|
58
|
+
|
|
59
|
+
Say which you set and why.
|
|
60
|
+
|
|
61
|
+
Surface `unreachableRuns > 0` — no silent omission.
|
|
62
|
+
|
|
63
|
+
### error-issue.4 — Submit
|
|
64
|
+
|
|
65
|
+
Only after an explicit approval:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
okstra error-issue submit --plan ~/.okstra/error-issue-plan.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Report `created` / `commented` / `skipped` / `rejected` / `failed`. If `rejected > 0`, print each rejection's `reasons` verbatim — a rejection means the outbound text or the evidence failed the final gate, and it is never something to work around.
|
|
72
|
+
|
|
73
|
+
If `failed > 0`, print each failure's `error` and **do not re-run `submit` on the same plan file**. A failure is a candidate that crashed or whose `gh` call errored; the candidates before it were already filed, and their `action` in the plan file still says `create`. Re-run `error-issue plan` instead — the fingerprint lookup turns the already-filed ones into `comment`/`skip`, which is the only thing that keeps a retry from filing duplicates.
|
|
74
|
+
|
|
75
|
+
### error-issue.5 — Next step
|
|
76
|
+
|
|
77
|
+
End with: "이 이슈를 실제로 고치려면 `/okstra-brief-gen` 에 이슈 URL 을 주고, okstra 레포에서 `okstra-run --task-type error-analysis` 로 진행하세요."
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# okstra-inspect facet — run-audit
|
|
2
|
+
|
|
3
|
+
Loaded lazily by the dispatch table in `SKILL.md` (core). Shared rules — Step 0 preflight, the standard task-key resolution rule (0/1/N), the no-task fallback, and Output Rules — live in the core file and still apply here.
|
|
4
|
+
|
|
5
|
+
## run-audit
|
|
6
|
+
|
|
7
|
+
Trigger phrases: "okstra run-audit", "run audit", "진행 점검", "태스크들 제대로 가고 있나", "silent failures".
|
|
8
|
+
|
|
9
|
+
Check every run's artifacts against progress invariants. This catches what the error log cannot: a run that never logged a failure but still ended wrong. Read-only.
|
|
10
|
+
|
|
11
|
+
### run-audit.1 — Run the audit
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
okstra run-audit
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### run-audit.2 — Report (Korean)
|
|
18
|
+
|
|
19
|
+
Group the `violations` array by `invariant` and report one section each:
|
|
20
|
+
|
|
21
|
+
| invariant | 위반 태스크 수 | 프로젝트 수 |
|
|
22
|
+
|---|---:|---|
|
|
23
|
+
|
|
24
|
+
Under each, list the affected `taskKey` values with their `detail` and `source`. Always cite `source` — the reader must be able to open the file the verdict came from.
|
|
25
|
+
|
|
26
|
+
### run-audit.3 — Split the two outcomes
|
|
27
|
+
|
|
28
|
+
State plainly which violations are this task's own state versus a repeated okstra defect:
|
|
29
|
+
|
|
30
|
+
- an invariant broken in **one** task → that task's own state; tell the user what to do about that task
|
|
31
|
+
- an invariant broken across **two or more** tasks → an okstra defect; point at `/okstra-inspect error-issue` to file it
|
|
32
|
+
- `approval-not-forgotten` is the exception and never routes to `error-issue` no matter how far it spreads. It means a human has not approved yet, not that okstra did something wrong — `error_issue.NEVER_AN_ISSUE` drops it, so pointing the user at `error-issue` for it promises a filing that will never happen. Report it as a to-do list of tasks awaiting the user's approval instead.
|
|
33
|
+
|
|
34
|
+
Never file an issue from this facet. Filing is `error-issue`'s job and it has its own approval gate.
|
|
@@ -174,7 +174,9 @@ When `next.kind == "done"`, fetch the public wizard outcome:
|
|
|
174
174
|
okstra wizard outcome --state-file /var/folders/.../okstra-wizard.AbCd.json
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Output: `{ok: true, outcome: {renderArgs: {...}, persistActions: [...], confirmationText: "..."}}`.
|
|
177
|
+
Output: `{ok: true, outcome: {renderArgs: {...}, orchestration: {chainStages: "..."}, persistActions: [...], confirmationText: "..."}}`.
|
|
178
|
+
|
|
179
|
+
`renderArgs` holds only flags `okstra render-bundle` accepts, which is what makes the pass-everything rule below safe. `orchestration` holds signals this skill acts on itself — never pass its entries to `render-bundle`.
|
|
178
180
|
|
|
179
181
|
Run every `outcome.persistActions[]` entry BEFORE `render-bundle`. The only supported action is:
|
|
180
182
|
|
|
@@ -216,6 +218,19 @@ The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.
|
|
|
216
218
|
|
|
217
219
|
You can delete the literal state-file path after this point — its job is done. Invoke `rm` with the literal path (e.g. `rm /var/folders/.../okstra-wizard.AbCd.json`), not a shell variable.
|
|
218
220
|
|
|
221
|
+
<!-- BEGIN FRAGMENT: host-orchestration-implementation -->
|
|
222
|
+
## Host orchestration rules — implementation
|
|
223
|
+
|
|
224
|
+
These are the rules the **host orchestrator** follows around an `implementation`
|
|
225
|
+
run: when to offer a conformance waiver, what a concurrent-run marker means, how
|
|
226
|
+
to recover a stale stage SHA, and what the chaining queue does when the next
|
|
227
|
+
stage is not ready. They are not lead phase rules — the lead's rules live in
|
|
228
|
+
`prompts/profiles/`.
|
|
229
|
+
|
|
230
|
+
This file is the single source. Two surfaces are generated from it: the
|
|
231
|
+
`okstra-run` skill body (marker block, synced by `tools/sync-skill-fragments.mjs`)
|
|
232
|
+
and each run's `instruction-set/host-orchestration-rules.md`. Edit here.
|
|
233
|
+
|
|
219
234
|
### Step 5.1 (implementation only): blocking local conformance waiver offer
|
|
220
235
|
|
|
221
236
|
`render-bundle` accepts an optional `--qa-waiver "<stageKey>:<reason>"` flag (implementation only). It records a **user-acknowledged** waiver into the task-level conformance manifest entry (`entry.waiver`), letting the run proceed when an `io`-only Tier 3 conformance script genuinely cannot run. The waiver records the user's reason **verbatim**.
|
|
@@ -256,6 +271,13 @@ If `render-bundle` fails with a `PrepareError` containing `Recorded stage SHAs n
|
|
|
256
271
|
|
|
257
272
|
If the anchor (`implementation_base_commit`) is reported unresolvable, run the same command's `--reset-anchor <ref>` after user confirmation. Correcting a confirm item without the picker is forbidden — the runtime also rejects a confirm correction without `--use-ref`.
|
|
258
273
|
|
|
274
|
+
### Next stage not yet ready — normal termination (not an exception gate)
|
|
275
|
+
Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
|
|
276
|
+
|
|
277
|
+
### Exception gate during chaining
|
|
278
|
+
If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
|
|
279
|
+
<!-- END FRAGMENT: host-orchestration-implementation -->
|
|
280
|
+
|
|
259
281
|
## Step 6: Take over as Okstra lead
|
|
260
282
|
|
|
261
283
|
Read `<INSTRUCTION_SET_PATH>/lead-execution-prompt.md` verbatim and take over as `Okstra lead` in the current host-native session. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it.
|
|
@@ -265,11 +287,11 @@ Then proceed through the phases exactly as the lead prompt directs (Phase 1 cont
|
|
|
265
287
|
Inform the user with one short line:
|
|
266
288
|
> Took over as Okstra lead (`<host-runtime>`) for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
|
|
267
289
|
|
|
268
|
-
## Step 7: implementation unattended chaining (
|
|
290
|
+
## Step 7: implementation unattended chaining (orchestration.chainStages)
|
|
269
291
|
|
|
270
|
-
When `task-type == implementation` and Step 5
|
|
292
|
+
When `task-type == implementation` and Step 5 outcome's `orchestration.chainStages` CSV has 2+ elements, the current session acts as the orchestrator and runs the stages in dependency order as an unattended chain (a single element behaves like the existing single run, so skip this section — the end of Step 6 is the end of the run).
|
|
271
293
|
|
|
272
|
-
Queue = the topologically-sorted stage list from splitting `
|
|
294
|
+
Queue = the topologically-sorted stage list from splitting `orchestration.chainStages` on `,` (the order Task 5 emitted by topologically sorting the dependency closure). For each stage `N` in the queue, in order:
|
|
273
295
|
|
|
274
296
|
1. Call Step 5's `render-bundle` with the same arguments but `--stage N` (the base commit is auto-computed by prepare from the predecessor's done `head_commit`, so do not pass it by hand). Step 5's blocking local conformance waiver offer·concurrent-run detection·git-reconcile gates apply identically to each stage's `render-bundle`.
|
|
275
297
|
2. As in Step 6, become the host-native Okstra lead and run that stage's Phase 1–7 inline. Phase 6's lead post-stage persistence appends that stage's `status:"done"` row to `runs/<plan-task-key>/consumers.jsonl` (per the implementation profile directive).
|
|
@@ -278,11 +300,7 @@ Queue = the topologically-sorted stage list from splitting `chain-stages` on `,`
|
|
|
278
300
|
|
|
279
301
|
Once the whole queue is consumed, end the chain and report completion to the user.
|
|
280
302
|
|
|
281
|
-
|
|
282
|
-
Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
|
|
283
|
-
|
|
284
|
-
### Exception gate during chaining
|
|
285
|
-
If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
|
|
303
|
+
The two branches that end or pause the queue — "Next stage not yet ready" and "Exception gate during chaining" — are in the host orchestration rules block above.
|
|
286
304
|
|
|
287
305
|
## Persisting the PR template scope (release-handoff)
|
|
288
306
|
|
|
@@ -308,4 +326,4 @@ Do not read the wizard state file directly. `okstra wizard outcome` exposes any
|
|
|
308
326
|
|
|
309
327
|
- Echo each captured answer (`result.echo`) on one short line so the user sees what was registered.
|
|
310
328
|
- Never invent identity; if a `text` prompt returns an empty answer where the wizard rejects it, the user must retry.
|
|
311
|
-
- After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, the end of Step 6 is the end of the run — but in an unattended chain where `
|
|
329
|
+
- After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, the end of Step 6 is the end of the run — but in an unattended chain where `orchestration.chainStages` has 2+ elements, repeat Step 6 per stage until Step 7's queue is empty (or it stops at a "not ready" / exception gate), then finish.
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-user-response
|
|
3
3
|
description: >-
|
|
4
|
-
Use this to answer an okstra task's open clarification questions in-session, without hand-editing any file. The tell is a request to respond to an okstra run's clarification items or its approval gate — "answer okstra", "I'll answer the questions", "clarification response", "user response", "approve and move on". This skill lists tasks whose latest report still has open clarification blockers, then walks the open C-ids one at a time — each rewritten as a self-contained question with background,
|
|
4
|
+
Use this to answer an okstra task's open clarification questions in-session, without hand-editing any file. The tell is a request to respond to an okstra run's clarification items or its approval gate — "answer okstra", "I'll answer the questions", "clarification response", "user response", "approve and move on". This skill lists tasks whose latest report still has open clarification blockers, then walks the open C-ids one at a time — each rewritten as a self-contained question with background, its options with their impact plus "Enter directly" — echoes the collected answers back for an explicit "confirmed" acknowledgement, optionally records approval, and writes the `user-responses/` sidecar via `okstra user-response write`. NOT for starting a run (okstra-run), inspecting a finished task (okstra-inspect), or generating a brief (okstra-brief-gen). The skill never picks an answer — it builds the option board and transcribes what the user decides.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA User Response
|
|
8
8
|
|
|
9
9
|
Single entry point for answering the clarification questions an okstra run left behind (the open `C-*` rows under the final report's `## 1. Clarification Items`) **in-session**, and recording those answers as a `runs/<type>/user-responses/` sidecar. The next `/okstra-run` auto-attaches this sidecar via `--clarification-response`.
|
|
10
10
|
|
|
11
|
-
**Core principle — the skill never picks an answer for the user.** It builds the option board — background, a self-contained question,
|
|
11
|
+
**Core principle — the skill never picks an answer for the user.** It builds the option board — background, a self-contained question, the report's options with their impact, `Enter directly` — and the user alone picks from it; every `value` is what the user chose or typed. It never calls `write` until the user has explicitly confirmed.
|
|
12
12
|
|
|
13
13
|
**Second principle — one question at a time.** Never batch two clarification items into one question, and never dump the whole open list at the user. Ask item 1, transcribe the answer, then ask item 2.
|
|
14
14
|
|
|
15
15
|
| Sub-command | What it does |
|
|
16
16
|
|---|---|
|
|
17
17
|
| `list` | List tasks that still have approval-open clarification, newest report first. |
|
|
18
|
-
| `show` | Expand one report's open `C-*` rows (statement +
|
|
18
|
+
| `show` | Expand one report's open `C-*` rows (statement + options + contextRefs). |
|
|
19
19
|
| `write` | Record the collected answers (+ optional approval) as a `user-responses/` sidecar. |
|
|
20
20
|
|
|
21
21
|
## Step 0: Preflight (shared)
|
|
@@ -71,7 +71,11 @@ Carry the chosen entry's `reportPath` and `taskKey` forward.
|
|
|
71
71
|
okstra user-response show --report <reportPath>
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Returns `{reportPath, rows: [{id, kind, blocks, status, statement,
|
|
74
|
+
Returns `{reportPath, rows: [{id, kind, blocks, status, statement, expectedForm, options, contextRefs, resolvedRefs}]}`.
|
|
75
|
+
|
|
76
|
+
Each entry of `options[]` is `{role, answer, rationale, scopeImpact, addedWork, directionChange}`. `role` is `recommended` for exactly one entry and `alternative` for the rest; `scopeImpact` is a token list drawn from `in-repo` / `cross-repo` / `new-schema` / `deferrable`; `addedWork` and `directionChange` are one sentence each. A schema-v1 report has nowhere to record impact, so those three arrive empty — the CLI reconstructs only the answers from the report's `Expected form` cell.
|
|
77
|
+
|
|
78
|
+
`resolvedRefs` is `[{ref, definition}]` — the CLI has already looked up what each internal token (`RB-002`, `FU-001`, `§4.7`, …) means in the report body; `definition` is `null` only when the report text alone could not resolve it (e.g. a `path:line` pointer).
|
|
75
79
|
|
|
76
80
|
This call is a **data fetch, not a presentation step**. Do not print `rows` at the user, and do not paste a raw `statement` as a question — a `statement` like `Rewrite RB-002 rollback (see §4.7)` is meaningless on its own, which is the whole reason this skill exists. Announce only the count and the plan:
|
|
77
81
|
|
|
@@ -103,23 +107,19 @@ Close the background with the raw source on one line, so the mapping back to the
|
|
|
103
107
|
|
|
104
108
|
> Source: `C-014` — "<raw statement>"
|
|
105
109
|
|
|
106
|
-
### 3b. The picker —
|
|
110
|
+
### 3b. The picker — the report's options plus `Enter directly`
|
|
107
111
|
|
|
108
|
-
One `AskUserQuestion` (single-select)
|
|
112
|
+
One `AskUserQuestion` (single-select). Fill the slots from `options[]` in array order — the `role: recommended` entry first with its label suffixed `(Recommended)`, then the `alternative` entries — and always close with `Enter directly` as the last option. Never mark anything but the `recommended` entry as recommended.
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
2. **Alternative** — `alternatives[0]`.
|
|
112
|
-
3. **Alternative** — `alternatives[1]`.
|
|
113
|
-
4. **`Enter directly`** — always last, always present.
|
|
114
|
+
Each `label` is that option's `answer`, kept to the answer itself (1–5 words). Each `description` carries the rationale followed by the three impact axes, in this fixed order:
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
> `<rationale>` — Scope: `<scopeImpact, comma-joined>` · Added work: `<addedWork>` · Direction: `<directionChange>`
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
- **`Reframe this question`** — the item is held rather than answered, and the next run re-asks it.
|
|
118
|
+
The three axes answer three different questions: how far the choice reaches, what new work it creates, and what it overturns. Never fold them into one phrase — whichever is easiest to write ends up standing in for the other two, and the user weighs a scope change as though it were free. That is the failure this board exists to prevent.
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
When an axis is empty — a schema-v1 report has nowhere to record impact — write `not stated in the report` for that axis. Do not infer it, and do not read the code to reconstruct it. A guessed side effect is worse than a stated gap, because the user cannot tell the two apart.
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
When `options[]` carries more than three entries, keep the recommended one plus the two alternatives whose `scopeImpact` differs most from it, and say in the background text how many you left out. When it carries exactly two, the picker has three options in total — do not pad it with an invented third.
|
|
123
123
|
|
|
124
124
|
### 3c. Transcribe the decision, then move on
|
|
125
125
|
|
|
@@ -127,13 +127,13 @@ Record one entry — `{id, kind, value, rationale?, disposition}`, `kind` copied
|
|
|
127
127
|
|
|
128
128
|
| The user picks | `value` | `disposition` |
|
|
129
129
|
|---|---|---|
|
|
130
|
-
|
|
|
130
|
+
| One of the `options[]` entries | that option's `answer` text, not its short label | `answer` |
|
|
131
131
|
| `Enter directly` → their own answer | the user's utterance verbatim (rationale into `rationale`) | `answer` |
|
|
132
|
-
|
|
|
132
|
+
| Free text asking for the item to be re-asked | what the user wants re-asked, verbatim (empty → the raw statement) | `reframe` |
|
|
133
133
|
|
|
134
134
|
A reframe is not an answer, so it does not satisfy the approval gate.
|
|
135
135
|
|
|
136
|
-
If the user replies with a question instead of an answer ("what does this mean?"), record nothing: **Read** the `§`/`path:line` from `contextRefs[]`, explain it in plain language, and re-ask the same item with the same
|
|
136
|
+
If the user replies with a question instead of an answer ("what does this mean?"), record nothing: **Read** the `§`/`path:line` from `contextRefs[]`, explain it in plain language, and re-ask the same item with the same options. Explain only — **do not resolve it for them**; the decision goes back to the user.
|
|
137
137
|
|
|
138
138
|
Echo one line per finished item (`[2/5] C-014 → answer: 60s`), then ask the next one. Do not summarize the whole set until Step 4.
|
|
139
139
|
|
|
@@ -406,6 +406,10 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
|
|
|
406
406
|
{% endif %}
|
|
407
407
|
{% if implementationPlanning.planBodyVerification.gateBlockedBy %}- **Blocked by**: {% for cause in implementationPlanning.planBodyVerification.gateBlockedBy %}`{{ cause }}`{% if not loop.last %}, {% endif %}{% endfor %}
|
|
408
408
|
{% endif %}
|
|
409
|
+
{% if implementationPlanning.planBodyVerification.uniformVerifiers %}- **{{ t("implementationPlanning.planBodyUniformVerifierLabel") }}**: {% for u in implementationPlanning.planBodyVerification.uniformVerifiers %}`{{ u.worker }}` → `{{ u.verdict }}` ({{ u.itemCount }}){% if not loop.last %}, {% endif %}{% endfor %}
|
|
410
|
+
|
|
411
|
+
> {{ t("implementationPlanning.planBodyUniformVerifierLegend") }}
|
|
412
|
+
{% endif %}
|
|
409
413
|
> {{ t("implementationPlanning.planBodyGateLegend") }}
|
|
410
414
|
>
|
|
411
415
|
> {{ t("implementationPlanning.planBodyBlockedByLegend") }}
|
|
@@ -38,6 +38,7 @@ svg { max-width: 100%; min-width: 520px; height: auto; }
|
|
|
38
38
|
svg .edge { stroke: GrayText; stroke-width: 2; }
|
|
39
39
|
svg .edge-arrow-head { fill: GrayText; }
|
|
40
40
|
svg .node rect { fill: color-mix(in srgb, Highlight 14%, Canvas); stroke: Highlight; }
|
|
41
|
+
svg .node-leading rect { fill: color-mix(in srgb, Highlight 32%, Canvas); stroke-width: 2; }
|
|
41
42
|
svg .node text { fill: CanvasText; font-size: 13px; }
|
|
42
43
|
table { width: 100%; border-collapse: collapse; margin-top: 1rem; font-size: .9rem; }
|
|
43
44
|
/* Korean takes a line break between any two syllables, so an auto-layout
|
|
@@ -79,7 +80,19 @@ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-m
|
|
|
79
80
|
.ledger-source { font-size: .9rem; color: GrayText; }
|
|
80
81
|
.ledger-source > span:first-child::after { content: ":"; }
|
|
81
82
|
.status { display: inline-block; white-space: nowrap; border-radius: 999px; padding: .1rem .5rem; background: color-mix(in srgb, GrayText 15%, Canvas); }
|
|
82
|
-
.status-gap, .status-risk { background: color-mix(in srgb, #d94b4b 18%, Canvas); }
|
|
83
|
+
.status-gap, .status-risk, .status-error, .status-timeout { background: color-mix(in srgb, #d94b4b 18%, Canvas); }
|
|
84
|
+
/* Figures are read by comparing them down the column, which only works when
|
|
85
|
+
the digits line up: tabular-nums stops a 1 from being narrower than a 7, and
|
|
86
|
+
the right edge is the one they share. */
|
|
87
|
+
.run-usage-lede { max-width: 78ch; }
|
|
88
|
+
.figure { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
89
|
+
/* Four nowrap number columns need almost none of the width, and the default
|
|
90
|
+
even split spent it on them while the agent names wrapped mid-word. */
|
|
91
|
+
[data-report-section="run-usage"] .row-key { width: 45%; }
|
|
92
|
+
.cli-extra { display: block; color: GrayText; font-size: .85em; }
|
|
93
|
+
tfoot th { font-weight: 600; }
|
|
94
|
+
tfoot tr:first-child > * { border-top: 2px solid color-mix(in srgb, CanvasText 30%, transparent); }
|
|
95
|
+
tfoot .grand-total > * { font-weight: 700; }
|
|
83
96
|
.human-report-footer { width: min(1120px, calc(100% - 2rem)); margin: 0 auto 2rem; display: flex; flex-wrap: wrap; gap: .6rem; }
|
|
84
97
|
.human-report-footer pre { flex-basis: 100%; white-space: pre-wrap; max-height: 14em; overflow: auto; }
|
|
85
98
|
fieldset, label { display: block; margin: .7rem 0; }
|