okstra 0.170.3 → 0.172.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/docs/architecture.md +13 -0
- package/docs/cli.md +4 -2
- package/docs/for-ai/skills/okstra-user-response.md +2 -2
- package/docs/project-structure-overview.md +3 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/launch.template.md +4 -0
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +36 -12
- package/runtime/prompts/lead/plan-body-verification.md +22 -11
- package/runtime/prompts/lead/report-writer.md +11 -10
- package/runtime/prompts/lead/team-contract.md +2 -0
- package/runtime/prompts/profiles/_clarification-recommendation.md +3 -1
- package/runtime/prompts/profiles/_common-contract.md +2 -1
- package/runtime/prompts/profiles/implementation-planning.md +8 -1
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +1 -1
- package/runtime/python/okstra_ctl/agent_activity.py +306 -0
- package/runtime/python/okstra_ctl/clarification_items.py +37 -20
- package/runtime/python/okstra_ctl/cmux.py +144 -59
- package/runtime/python/okstra_ctl/lead_events.py +47 -4
- package/runtime/python/okstra_ctl/render.py +11 -3
- package/runtime/python/okstra_ctl/report_finalize.py +51 -14
- package/runtime/python/okstra_ctl/report_html/common.py +5 -3
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +17 -1
- package/runtime/python/okstra_ctl/report_translation.py +14 -0
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +150 -0
- package/runtime/schemas/final-report-v2.0.schema.json +189 -0
- package/runtime/skills/okstra-user-response/SKILL.md +2 -2
- package/runtime/templates/reports/final-report-v2.template.md +8 -0
- package/runtime/templates/reports/html/assets/base.css +7 -0
- package/runtime/templates/reports/html/i18n/en.json +6 -1
- package/runtime/templates/reports/html/i18n/ko.json +6 -1
- package/runtime/templates/reports/html/macros/forms.html +21 -2
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +25 -0
- package/runtime/templates/reports/i18n/en.json +4 -0
- package/runtime/templates/reports/report.js +26 -17
- package/runtime/templates/reports/user-response.template.md +3 -1
- package/runtime/templates/worker-prompt-preamble.md +8 -0
- package/runtime/validators/validate-run.py +989 -29
- package/runtime/validators/validate_session_conformance.py +523 -35
- package/src/cli-registry.mjs +7 -0
- package/src/commands/report/agent-activity.mjs +21 -0
|
@@ -125,8 +125,10 @@ PROSE_KEYS = frozenset({
|
|
|
125
125
|
"topic",
|
|
126
126
|
"trigger",
|
|
127
127
|
"triggerSignal",
|
|
128
|
+
"unblockCondition",
|
|
128
129
|
"userChoice",
|
|
129
130
|
"userInput",
|
|
131
|
+
"userText",
|
|
130
132
|
"verification",
|
|
131
133
|
"verificationMethod",
|
|
132
134
|
"verificationSignal",
|
|
@@ -138,6 +140,8 @@ PROSE_KEYS = frozenset({
|
|
|
138
140
|
# that must stay verbatim to be evidence at all.
|
|
139
141
|
STRUCTURAL_KEYS = frozenset({
|
|
140
142
|
"action", # implementation.py: _FILE_ACTIONS lookup key
|
|
143
|
+
"activityId",
|
|
144
|
+
"activityIds",
|
|
141
145
|
"adapter",
|
|
142
146
|
"affectedId",
|
|
143
147
|
"affectedTarget",
|
|
@@ -152,12 +156,15 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
152
156
|
"capturedHeadSha",
|
|
153
157
|
"carriedForwardFromSeq",
|
|
154
158
|
"carryJson",
|
|
159
|
+
"checkRefs",
|
|
155
160
|
"clarificationId",
|
|
161
|
+
"classification",
|
|
156
162
|
"claudeCode",
|
|
157
163
|
"collectorBranch",
|
|
158
164
|
"command",
|
|
159
165
|
"commandOrObservation",
|
|
160
166
|
"commandOrTest",
|
|
167
|
+
"commands",
|
|
161
168
|
"commitListQuote",
|
|
162
169
|
"complexity",
|
|
163
170
|
"componentId",
|
|
@@ -165,14 +172,18 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
165
172
|
"conformanceTests",
|
|
166
173
|
"coveredBy",
|
|
167
174
|
"createdAt",
|
|
175
|
+
"cwd",
|
|
168
176
|
"cycle",
|
|
169
177
|
"dataStore",
|
|
170
178
|
"date",
|
|
171
179
|
"dependsOn",
|
|
172
180
|
"diffSummaryQuote",
|
|
173
181
|
"direction",
|
|
182
|
+
"disposition",
|
|
174
183
|
"dispatches",
|
|
175
184
|
"executorWorktreePath",
|
|
185
|
+
"evidenceRefs",
|
|
186
|
+
"exitCode",
|
|
176
187
|
"existingPrUrl",
|
|
177
188
|
"externalSystem",
|
|
178
189
|
"featureId",
|
|
@@ -215,6 +226,7 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
215
226
|
"ownerComponentId",
|
|
216
227
|
"path",
|
|
217
228
|
"planFile",
|
|
229
|
+
"planItemIds",
|
|
218
230
|
"planStep",
|
|
219
231
|
"prepItemId",
|
|
220
232
|
"project",
|
|
@@ -223,11 +235,13 @@ STRUCTURAL_KEYS = frozenset({
|
|
|
223
235
|
"rawStat",
|
|
224
236
|
"readBoundary",
|
|
225
237
|
"readOnlyCommandLog",
|
|
238
|
+
"recommendedDisposition",
|
|
226
239
|
"relatedIds",
|
|
227
240
|
"relation",
|
|
228
241
|
"replacedInTest",
|
|
229
242
|
"replacedWith",
|
|
230
243
|
"reportPath",
|
|
244
|
+
"resultPath",
|
|
231
245
|
"requestKind",
|
|
232
246
|
"requestPath",
|
|
233
247
|
"requestVerbatim", # the user's own words, quoted as evidence
|
|
@@ -10,7 +10,9 @@ so the rules live here rather than inside either one — the same split
|
|
|
10
10
|
"""
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import json
|
|
13
14
|
import re
|
|
15
|
+
from collections.abc import Mapping, Sequence
|
|
14
16
|
from dataclasses import dataclass
|
|
15
17
|
from pathlib import Path
|
|
16
18
|
|
|
@@ -39,6 +41,29 @@ _EVIDENCE_READ_RE = re.compile(
|
|
|
39
41
|
r"^- Evidence read: `?(?P<path>[^`\n]+?)`?\s*$",
|
|
40
42
|
re.MULTILINE,
|
|
41
43
|
)
|
|
44
|
+
_EVIDENCE_COMMAND_RE = re.compile(
|
|
45
|
+
r"^- Evidence command:[ \t]*(?P<payload>.*)$", re.MULTILINE
|
|
46
|
+
)
|
|
47
|
+
_EVIDENCE_COMMAND_FIELDS = ("command", "cwd", "exitCode", "outputSummary")
|
|
48
|
+
_EVIDENCE_COMMAND_STRING_FIELDS = ("command", "cwd", "outputSummary")
|
|
49
|
+
_SENSITIVE_KEY_RE = re.compile(
|
|
50
|
+
r"(?i)(authorization|auth|credential|pass(?:wd|word)?|pwd|token|secret|"
|
|
51
|
+
r"api[_-]?key|access[_-]?key)"
|
|
52
|
+
)
|
|
53
|
+
_SENSITIVE_TEXT_RE = re.compile(
|
|
54
|
+
r"(?i)(authorization\s*:\s*\S+|"
|
|
55
|
+
r"\b(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:auth|credential|pass(?:wd|word)?|pwd|"
|
|
56
|
+
r"token|secret|api[_-]?key|access[_-]?key)[A-Za-z0-9_-]*\s*=|"
|
|
57
|
+
r"--[\w-]*(?:auth|credential|pass(?:wd|word)?|pwd|token|secret|"
|
|
58
|
+
r"api[-_]?key|access[-_]?key)[\w-]*(?:[ =])\S+|"
|
|
59
|
+
r"\w+://[^\s:/@]+:[^\s@/]+@)"
|
|
60
|
+
)
|
|
61
|
+
_KNOWN_SECRET_RE = re.compile(
|
|
62
|
+
r"(?:AKIA|ASIA)[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9_]{8,}|"
|
|
63
|
+
r"github_pat_[A-Za-z0-9_]{8,}|xox[abprs]-[A-Za-z0-9-]{8,}|"
|
|
64
|
+
r"\bsk-[A-Za-z0-9_-]{8,}\b|"
|
|
65
|
+
r"\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b"
|
|
66
|
+
)
|
|
42
67
|
_FILE_LINE_CITATION_RE = re.compile(
|
|
43
68
|
r"`(?P<path>(?!https?://)[^`\n]+?):(?P<line>\d+(?:-\d+)?)`"
|
|
44
69
|
)
|
|
@@ -85,6 +110,129 @@ def _audit_evidence_read_paths(content: str) -> set[str]:
|
|
|
85
110
|
}
|
|
86
111
|
|
|
87
112
|
|
|
113
|
+
@dataclass(frozen=True)
|
|
114
|
+
class EvidenceCommand:
|
|
115
|
+
command: str
|
|
116
|
+
cwd: str
|
|
117
|
+
exit_code: int
|
|
118
|
+
output_summary: str
|
|
119
|
+
|
|
120
|
+
def to_record(self) -> dict[str, object]:
|
|
121
|
+
return {
|
|
122
|
+
"command": self.command,
|
|
123
|
+
"cwd": self.cwd,
|
|
124
|
+
"exitCode": self.exit_code,
|
|
125
|
+
"outputSummary": self.output_summary,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _contains_sensitive_material(value: object) -> bool:
|
|
130
|
+
if isinstance(value, Mapping):
|
|
131
|
+
return any(
|
|
132
|
+
(
|
|
133
|
+
_SENSITIVE_KEY_RE.search(str(key)) is not None
|
|
134
|
+
and nested not in (None, "", False)
|
|
135
|
+
)
|
|
136
|
+
or _contains_sensitive_material(nested)
|
|
137
|
+
for key, nested in value.items()
|
|
138
|
+
)
|
|
139
|
+
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
|
140
|
+
return any(_contains_sensitive_material(item) for item in value)
|
|
141
|
+
if not isinstance(value, str):
|
|
142
|
+
return False
|
|
143
|
+
return (
|
|
144
|
+
_SENSITIVE_TEXT_RE.search(value) is not None
|
|
145
|
+
or _KNOWN_SECRET_RE.search(value) is not None
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _evidence_command_contract_failures(
|
|
150
|
+
payload: Mapping[str, object], row_number: int
|
|
151
|
+
) -> list[str]:
|
|
152
|
+
failures: list[str] = []
|
|
153
|
+
missing = [key for key in _EVIDENCE_COMMAND_FIELDS if key not in payload]
|
|
154
|
+
unexpected = sorted(set(payload) - set(_EVIDENCE_COMMAND_FIELDS))
|
|
155
|
+
if missing:
|
|
156
|
+
failures.append(
|
|
157
|
+
f"Evidence command row {row_number} is missing {', '.join(missing)}"
|
|
158
|
+
)
|
|
159
|
+
if unexpected:
|
|
160
|
+
failures.append(
|
|
161
|
+
f"Evidence command row {row_number} has unexpected "
|
|
162
|
+
f"{', '.join(unexpected)}"
|
|
163
|
+
)
|
|
164
|
+
for field_name in _EVIDENCE_COMMAND_STRING_FIELDS:
|
|
165
|
+
if field_name in payload and not isinstance(payload[field_name], str):
|
|
166
|
+
failures.append(
|
|
167
|
+
f"Evidence command row {row_number} {field_name} must be a string"
|
|
168
|
+
)
|
|
169
|
+
if "exitCode" in payload and type(payload["exitCode"]) is not int:
|
|
170
|
+
failures.append(
|
|
171
|
+
f"Evidence command row {row_number} exitCode must be an integer"
|
|
172
|
+
)
|
|
173
|
+
return failures
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _evidence_command_from_payload(
|
|
177
|
+
payload: Mapping[str, object], sensitive: bool
|
|
178
|
+
) -> EvidenceCommand:
|
|
179
|
+
if sensitive:
|
|
180
|
+
return EvidenceCommand(
|
|
181
|
+
command="[redacted: potential secret]",
|
|
182
|
+
cwd="[redacted: potential secret]",
|
|
183
|
+
exit_code=payload["exitCode"],
|
|
184
|
+
output_summary="omitted because potential secret material was detected",
|
|
185
|
+
)
|
|
186
|
+
return EvidenceCommand(
|
|
187
|
+
command=payload["command"],
|
|
188
|
+
cwd=payload["cwd"],
|
|
189
|
+
exit_code=payload["exitCode"],
|
|
190
|
+
output_summary=payload["outputSummary"],
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def parse_evidence_commands(
|
|
195
|
+
content: str,
|
|
196
|
+
) -> tuple[tuple[EvidenceCommand, ...], tuple[str, ...]]:
|
|
197
|
+
commands: list[EvidenceCommand] = []
|
|
198
|
+
failures: list[str] = []
|
|
199
|
+
for row_number, match in enumerate(
|
|
200
|
+
_EVIDENCE_COMMAND_RE.finditer(content), start=1
|
|
201
|
+
):
|
|
202
|
+
try:
|
|
203
|
+
payload = json.loads(match.group("payload"))
|
|
204
|
+
except json.JSONDecodeError:
|
|
205
|
+
failures.append(f"Evidence command row {row_number} is not valid JSON")
|
|
206
|
+
continue
|
|
207
|
+
if not isinstance(payload, Mapping):
|
|
208
|
+
failures.append(
|
|
209
|
+
f"Evidence command row {row_number} must be a JSON object"
|
|
210
|
+
)
|
|
211
|
+
continue
|
|
212
|
+
sensitive = _contains_sensitive_material(payload)
|
|
213
|
+
if sensitive:
|
|
214
|
+
failures.append(
|
|
215
|
+
f"Evidence command row {row_number} contains sensitive material"
|
|
216
|
+
)
|
|
217
|
+
contract_failures = _evidence_command_contract_failures(payload, row_number)
|
|
218
|
+
failures.extend(contract_failures)
|
|
219
|
+
if contract_failures:
|
|
220
|
+
continue
|
|
221
|
+
commands.append(_evidence_command_from_payload(payload, sensitive))
|
|
222
|
+
return tuple(commands), tuple(failures)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def read_evidence_commands(
|
|
226
|
+
audit_path: Path,
|
|
227
|
+
) -> tuple[tuple[EvidenceCommand, ...], tuple[str, ...]]:
|
|
228
|
+
try:
|
|
229
|
+
return parse_evidence_commands(audit_path.read_text(encoding="utf-8"))
|
|
230
|
+
except OSError as exc:
|
|
231
|
+
return (), (
|
|
232
|
+
f"worker audit sidecar unreadable: {audit_path.name} ({exc})",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
88
236
|
def _worker_prompt_path(
|
|
89
237
|
run_dir: Path,
|
|
90
238
|
worker_role: str,
|
|
@@ -276,6 +424,8 @@ def check_worker_results_audit(
|
|
|
276
424
|
)
|
|
277
425
|
continue
|
|
278
426
|
|
|
427
|
+
_, command_failures = read_evidence_commands(audit_path)
|
|
428
|
+
failures.extend(command_failures)
|
|
279
429
|
failures.extend(_evidence_read_ledger_failures(
|
|
280
430
|
run_dir=run_dir,
|
|
281
431
|
worker_role=worker_role,
|
|
@@ -171,6 +171,12 @@
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
|
+
"agentActivity": {
|
|
175
|
+
"type": "array",
|
|
176
|
+
"items": {
|
|
177
|
+
"$ref": "#/$defs/AgentActivityRow"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
174
180
|
"verificationScope": {
|
|
175
181
|
"enum": [
|
|
176
182
|
"whole-task",
|
|
@@ -2587,6 +2593,103 @@
|
|
|
2587
2593
|
}
|
|
2588
2594
|
],
|
|
2589
2595
|
"$defs": {
|
|
2596
|
+
"ActivityCommand": {
|
|
2597
|
+
"type": "object",
|
|
2598
|
+
"required": [
|
|
2599
|
+
"command",
|
|
2600
|
+
"cwd",
|
|
2601
|
+
"exitCode",
|
|
2602
|
+
"outputSummary"
|
|
2603
|
+
],
|
|
2604
|
+
"additionalProperties": false,
|
|
2605
|
+
"properties": {
|
|
2606
|
+
"command": {
|
|
2607
|
+
"type": "string"
|
|
2608
|
+
},
|
|
2609
|
+
"cwd": {
|
|
2610
|
+
"type": "string"
|
|
2611
|
+
},
|
|
2612
|
+
"exitCode": {
|
|
2613
|
+
"type": "integer"
|
|
2614
|
+
},
|
|
2615
|
+
"outputSummary": {
|
|
2616
|
+
"type": "string"
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
},
|
|
2620
|
+
"AgentActivityRow": {
|
|
2621
|
+
"type": "object",
|
|
2622
|
+
"required": [
|
|
2623
|
+
"activityId",
|
|
2624
|
+
"kind",
|
|
2625
|
+
"agent",
|
|
2626
|
+
"summary",
|
|
2627
|
+
"planItemIds",
|
|
2628
|
+
"resultPath",
|
|
2629
|
+
"commands",
|
|
2630
|
+
"evidenceRefs",
|
|
2631
|
+
"outcome"
|
|
2632
|
+
],
|
|
2633
|
+
"additionalProperties": false,
|
|
2634
|
+
"properties": {
|
|
2635
|
+
"activityId": {
|
|
2636
|
+
"type": "string",
|
|
2637
|
+
"pattern": "^A-\\d{3,}$"
|
|
2638
|
+
},
|
|
2639
|
+
"kind": {
|
|
2640
|
+
"type": "string",
|
|
2641
|
+
"enum": [
|
|
2642
|
+
"worker-dispatched",
|
|
2643
|
+
"worker-completed",
|
|
2644
|
+
"verification-round-completed",
|
|
2645
|
+
"self-fix-applied",
|
|
2646
|
+
"user-decision-required",
|
|
2647
|
+
"user-decision-evaluated"
|
|
2648
|
+
]
|
|
2649
|
+
},
|
|
2650
|
+
"agent": {
|
|
2651
|
+
"type": "string",
|
|
2652
|
+
"minLength": 1
|
|
2653
|
+
},
|
|
2654
|
+
"summary": {
|
|
2655
|
+
"type": "string",
|
|
2656
|
+
"minLength": 1
|
|
2657
|
+
},
|
|
2658
|
+
"planItemIds": {
|
|
2659
|
+
"type": "array",
|
|
2660
|
+
"items": {
|
|
2661
|
+
"type": "string",
|
|
2662
|
+
"minLength": 1
|
|
2663
|
+
}
|
|
2664
|
+
},
|
|
2665
|
+
"resultPath": {
|
|
2666
|
+
"type": "string"
|
|
2667
|
+
},
|
|
2668
|
+
"commands": {
|
|
2669
|
+
"type": "array",
|
|
2670
|
+
"items": {
|
|
2671
|
+
"$ref": "#/$defs/ActivityCommand"
|
|
2672
|
+
}
|
|
2673
|
+
},
|
|
2674
|
+
"evidenceRefs": {
|
|
2675
|
+
"type": "array",
|
|
2676
|
+
"items": {
|
|
2677
|
+
"type": "string",
|
|
2678
|
+
"minLength": 1
|
|
2679
|
+
}
|
|
2680
|
+
},
|
|
2681
|
+
"outcome": {
|
|
2682
|
+
"type": "string",
|
|
2683
|
+
"enum": [
|
|
2684
|
+
"pending",
|
|
2685
|
+
"completed",
|
|
2686
|
+
"failed",
|
|
2687
|
+
"blocked",
|
|
2688
|
+
"resolved"
|
|
2689
|
+
]
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
},
|
|
2590
2693
|
"TaskType": {
|
|
2591
2694
|
"enum": [
|
|
2592
2695
|
"requirements-discovery",
|
|
@@ -3553,6 +3656,86 @@
|
|
|
3553
3656
|
"deferred-no-interactive-session"
|
|
3554
3657
|
]
|
|
3555
3658
|
},
|
|
3659
|
+
"ApprovalDisposition": {
|
|
3660
|
+
"enum": [
|
|
3661
|
+
"select",
|
|
3662
|
+
"accept-risk",
|
|
3663
|
+
"request-revision",
|
|
3664
|
+
"reject"
|
|
3665
|
+
]
|
|
3666
|
+
},
|
|
3667
|
+
"ApprovalResolution": {
|
|
3668
|
+
"type": "object",
|
|
3669
|
+
"required": [
|
|
3670
|
+
"disposition",
|
|
3671
|
+
"userText",
|
|
3672
|
+
"checkRefs"
|
|
3673
|
+
],
|
|
3674
|
+
"additionalProperties": false,
|
|
3675
|
+
"properties": {
|
|
3676
|
+
"disposition": {
|
|
3677
|
+
"$ref": "#/$defs/ApprovalDisposition"
|
|
3678
|
+
},
|
|
3679
|
+
"userText": {
|
|
3680
|
+
"type": "string",
|
|
3681
|
+
"minLength": 1
|
|
3682
|
+
},
|
|
3683
|
+
"checkRefs": {
|
|
3684
|
+
"type": "array",
|
|
3685
|
+
"minItems": 1,
|
|
3686
|
+
"items": {
|
|
3687
|
+
"type": "string",
|
|
3688
|
+
"pattern": "^A-\\d{3,}$"
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
},
|
|
3693
|
+
"ApprovalContext": {
|
|
3694
|
+
"type": "object",
|
|
3695
|
+
"required": [
|
|
3696
|
+
"classification",
|
|
3697
|
+
"planItemIds",
|
|
3698
|
+
"activityIds",
|
|
3699
|
+
"unblockCondition",
|
|
3700
|
+
"recommendedDisposition"
|
|
3701
|
+
],
|
|
3702
|
+
"additionalProperties": false,
|
|
3703
|
+
"properties": {
|
|
3704
|
+
"classification": {
|
|
3705
|
+
"enum": [
|
|
3706
|
+
"user-decision",
|
|
3707
|
+
"noncritical-dissent",
|
|
3708
|
+
"correctness-critical"
|
|
3709
|
+
]
|
|
3710
|
+
},
|
|
3711
|
+
"planItemIds": {
|
|
3712
|
+
"type": "array",
|
|
3713
|
+
"minItems": 1,
|
|
3714
|
+
"items": {
|
|
3715
|
+
"type": "string",
|
|
3716
|
+
"pattern": "^P-[A-Za-z]+-.+"
|
|
3717
|
+
}
|
|
3718
|
+
},
|
|
3719
|
+
"activityIds": {
|
|
3720
|
+
"type": "array",
|
|
3721
|
+
"minItems": 1,
|
|
3722
|
+
"items": {
|
|
3723
|
+
"type": "string",
|
|
3724
|
+
"pattern": "^A-\\d{3,}$"
|
|
3725
|
+
}
|
|
3726
|
+
},
|
|
3727
|
+
"unblockCondition": {
|
|
3728
|
+
"type": "string",
|
|
3729
|
+
"minLength": 1
|
|
3730
|
+
},
|
|
3731
|
+
"recommendedDisposition": {
|
|
3732
|
+
"$ref": "#/$defs/ApprovalDisposition"
|
|
3733
|
+
},
|
|
3734
|
+
"resolution": {
|
|
3735
|
+
"$ref": "#/$defs/ApprovalResolution"
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
},
|
|
3556
3739
|
"ClarificationOption": {
|
|
3557
3740
|
"type": "object",
|
|
3558
3741
|
"required": [
|
|
@@ -3593,6 +3776,9 @@
|
|
|
3593
3776
|
"directionChange": {
|
|
3594
3777
|
"type": "string",
|
|
3595
3778
|
"minLength": 1
|
|
3779
|
+
},
|
|
3780
|
+
"disposition": {
|
|
3781
|
+
"$ref": "#/$defs/ApprovalDisposition"
|
|
3596
3782
|
}
|
|
3597
3783
|
}
|
|
3598
3784
|
},
|
|
@@ -8421,6 +8607,9 @@
|
|
|
8421
8607
|
"items": {
|
|
8422
8608
|
"$ref": "#/$defs/ClarificationOption"
|
|
8423
8609
|
}
|
|
8610
|
+
},
|
|
8611
|
+
"approvalContext": {
|
|
8612
|
+
"$ref": "#/$defs/ApprovalContext"
|
|
8424
8613
|
}
|
|
8425
8614
|
}
|
|
8426
8615
|
},
|
|
@@ -73,7 +73,7 @@ okstra user-response show --report <reportPath>
|
|
|
73
73
|
|
|
74
74
|
Returns `{reportPath, rows: [{id, kind, blocks, status, statement, expectedForm, options, contextRefs, resolvedRefs}]}`.
|
|
75
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
|
|
76
|
+
Each entry of `options[]` is `{role, answer, rationale, scopeImpact, addedWork, directionChange, disposition}`. `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. Activity-contract v1 approval options also carry the gate action in `disposition`. A legacy schema-v1 report has nowhere to record impact or a gate action, so those fields arrive empty — the CLI reconstructs only the answers from the report's `Expected form` cell.
|
|
77
77
|
|
|
78
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).
|
|
79
79
|
|
|
@@ -127,7 +127,7 @@ Record one entry — `{id, kind, value, rationale?, disposition}`, `kind` copied
|
|
|
127
127
|
|
|
128
128
|
| The user picks | `value` | `disposition` |
|
|
129
129
|
|---|---|---|
|
|
130
|
-
| One of the `options[]` entries | that option's `answer` text, not its short label | `answer` |
|
|
130
|
+
| One of the `options[]` entries | that option's `answer` text, not its short label | that option's `disposition` (`answer` only when absent on a legacy option) |
|
|
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
|
|
|
@@ -110,6 +110,14 @@ schema-version: {{ schemaVersion | yaml_scalar }}
|
|
|
110
110
|
{{ md_rest(aiTaskProperty, 3) }}
|
|
111
111
|
{% endif %}
|
|
112
112
|
|
|
113
|
+
{% if md_has("agentActivity") %}
|
|
114
|
+
## {{ t("agentActivity.heading") }}
|
|
115
|
+
|
|
116
|
+
{{ t("agentActivity.intro") }}
|
|
117
|
+
|
|
118
|
+
{{ md("agentActivity", 3) }}
|
|
119
|
+
{% endif %}
|
|
120
|
+
|
|
113
121
|
## Cross Verification Audit
|
|
114
122
|
|
|
115
123
|
{{ md("crossVerification", 3) }}
|
|
@@ -108,6 +108,13 @@ 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; }
|
|
113
|
+
.activity-card > :first-child, .approval-context > :first-child { margin-top: 0; }
|
|
114
|
+
.activity-card > :last-child, .approval-context > :last-child { margin-bottom: 0; }
|
|
115
|
+
.activity-card details { margin-top: .8rem; }
|
|
116
|
+
.approval-context { margin: .8rem 0; padding: .8rem 1rem; border-left: 4px solid color-mix(in srgb, Highlight 55%, GrayText); background: color-mix(in srgb, Highlight 7%, Canvas); }
|
|
117
|
+
.clarification-option-disposition { color: GrayText; font-size: .85rem; }
|
|
111
118
|
.user-response-hint { flex-basis: 100%; margin: 0; color: GrayText; font-size: .9rem; }
|
|
112
119
|
button { padding: .5rem 1rem; border-radius: 8px; border: 1px solid color-mix(in srgb, CanvasText 28%, transparent); background: color-mix(in srgb, CanvasText 5%, Canvas); color: CanvasText; font: inherit; cursor: pointer; }
|
|
113
120
|
button:hover { background: color-mix(in srgb, CanvasText 12%, Canvas); }
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"questions-waiting-on-you": "Questions waiting on you",
|
|
151
151
|
"count-questions-waiting-on-you": "{count} questions waiting on you",
|
|
152
152
|
"your-answer-to-id": "Your answer to {id}",
|
|
153
|
+
"choose-one": "Choose one",
|
|
153
154
|
"recommended": "Recommended",
|
|
154
155
|
"scope-impact": "Scope",
|
|
155
156
|
"added-work": "Added work",
|
|
@@ -322,7 +323,11 @@
|
|
|
322
323
|
"open-decisions-before-approval": "Open decisions before approval",
|
|
323
324
|
"no-open-decision-is-holding-approval": "No open decision is holding approval.",
|
|
324
325
|
"uniform-verifier-label": "Uniform verifier",
|
|
325
|
-
"uniform-verifier-legend": "This verifier answered the same verdict to every item it voted on, so this round's refutation signal came from its peers alone — the gate above reads as a wider cross-check than it was. A unanimous round is a legitimate outcome; confirm from the worker's audit sidecar that it actually opened the cited evidence."
|
|
326
|
+
"uniform-verifier-legend": "This verifier answered the same verdict to every item it voted on, so this round's refutation signal came from its peers alone — the gate above reads as a wider cross-check than it was. A unanimous round is a legitimate outcome; confirm from the worker's audit sidecar that it actually opened the cited evidence.",
|
|
327
|
+
"what-each-agent-did": "What each agent did",
|
|
328
|
+
"commands-and-evidence": "Commands and evidence",
|
|
329
|
+
"unblock-condition": "Unblock condition:",
|
|
330
|
+
"agent-evidence": "Agent evidence:"
|
|
326
331
|
},
|
|
327
332
|
"implementation": {
|
|
328
333
|
"what-was-delivered": "What was delivered",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"questions-waiting-on-you": "답변을 기다리는 질문",
|
|
151
151
|
"count-questions-waiting-on-you": "답변을 기다리는 질문 {count}건",
|
|
152
152
|
"your-answer-to-id": "{id}에 대한 답변",
|
|
153
|
+
"choose-one": "하나를 선택하세요",
|
|
153
154
|
"recommended": "권장",
|
|
154
155
|
"scope-impact": "범위",
|
|
155
156
|
"added-work": "추가 작업",
|
|
@@ -322,7 +323,11 @@
|
|
|
322
323
|
"open-decisions-before-approval": "승인 전에 남은 결정",
|
|
323
324
|
"no-open-decision-is-holding-approval": "승인을 막는 미결 결정이 없습니다.",
|
|
324
325
|
"uniform-verifier-label": "단일 판정 검증자",
|
|
325
|
-
"uniform-verifier-legend": "이 검증자는 자기가 투표한 모든 항목에 같은 판정을 냈습니다. 그래서 이번 라운드의 반증 신호는 나머지 검증자에게서만 나왔고, 위의 게이트 값은 실제보다 넓은 교차 검증처럼 읽힙니다. 만장일치도 정당한 결과이므로, 그 워커의 audit 사이드카에서 인용한 근거를 실제로 열어봤는지 확인하십시오."
|
|
326
|
+
"uniform-verifier-legend": "이 검증자는 자기가 투표한 모든 항목에 같은 판정을 냈습니다. 그래서 이번 라운드의 반증 신호는 나머지 검증자에게서만 나왔고, 위의 게이트 값은 실제보다 넓은 교차 검증처럼 읽힙니다. 만장일치도 정당한 결과이므로, 그 워커의 audit 사이드카에서 인용한 근거를 실제로 열어봤는지 확인하십시오.",
|
|
327
|
+
"what-each-agent-did": "에이전트별 수행 내용",
|
|
328
|
+
"commands-and-evidence": "명령과 근거",
|
|
329
|
+
"unblock-condition": "승인 해제 조건:",
|
|
330
|
+
"agent-evidence": "에이전트 근거:"
|
|
326
331
|
},
|
|
327
332
|
"implementation": {
|
|
328
333
|
"what-was-delivered": "무엇을 만들었나",
|
|
@@ -42,15 +42,27 @@
|
|
|
42
42
|
<h2>{{ t('macros.forms.count-questions-waiting-on-you') | replace('{count}', items | length) }}</h2>
|
|
43
43
|
<p class="clarification-lede">{{ t('macros.forms.the-code-alone-could-not-settle-these-fill-i') }} <strong>{{ t('macros.forms.export-my-answers') }}</strong> {{ t('macros.forms.at-the-foot-of-the-page') }}</p>
|
|
44
44
|
{% for row in items %}
|
|
45
|
-
|
|
45
|
+
{% set is_closed = row.status in ['resolved', 'obsolete'] %}
|
|
46
|
+
{% set approval_context = row.approvalContext | default(None) %}
|
|
47
|
+
<article class="clarification-item" id="id-{{ row.id }}" data-response-id="{{ row.id }}" data-kind="{{ row.kind }}" data-status="{{ row.status }}">
|
|
46
48
|
<p class="eyebrow">{{ row.id }} · {{ row.kind }}</p>
|
|
47
49
|
{{ row.statement | paragraphs }}
|
|
48
50
|
<dl class="clarification-expected"><dt>{{ t('macros.forms.answer-as') }}</dt><dd>{{ row.expectedForm | inline_code }}</dd></dl>
|
|
51
|
+
{% if approval_context %}
|
|
52
|
+
<div class="approval-context" data-approval-classification="{{ approval_context.classification }}">
|
|
53
|
+
<p class="eyebrow">{{ approval_context.classification }}</p>
|
|
54
|
+
<p><strong>{{ t('tasks.implementation-planning.unblock-condition') }}</strong> {{ approval_context.unblockCondition | inline_code }}</p>
|
|
55
|
+
<p><strong>{{ t('tasks.implementation-planning.agent-evidence') }}</strong>
|
|
56
|
+
{% for activity_id in approval_context.activityIds %}<a href="#id-{{ activity_id }}">{{ activity_id }}</a>{% if not loop.last %}, {% endif %}{% endfor %}
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
{% endif %}
|
|
49
60
|
{% if row.options %}
|
|
50
61
|
<ol class="clarification-options">
|
|
51
62
|
{% for option in row.options %}
|
|
52
63
|
<li class="clarification-option{% if option.role == 'recommended' %} is-recommended{% endif %}">
|
|
53
64
|
<p class="clarification-option-answer">{{ option.answer }}{% if option.role == 'recommended' %} <span class="badge">{{ t('macros.forms.recommended') }}</span>{% endif %}</p>
|
|
65
|
+
{% if option.disposition | default(None) %}<p class="clarification-option-disposition"><code>{{ option.disposition }}</code></p>{% endif %}
|
|
54
66
|
<p class="clarification-option-rationale">{{ option.rationale }}</p>
|
|
55
67
|
<dl class="clarification-option-impact">
|
|
56
68
|
<dt>{{ t('macros.forms.scope-impact') }}</dt><dd>{{ option.scopeImpact | join(', ') }}</dd>
|
|
@@ -62,7 +74,14 @@
|
|
|
62
74
|
</ol>
|
|
63
75
|
{% endif %}
|
|
64
76
|
<label for="response-{{ row.id }}">{{ t('macros.forms.your-answer-to-id') | replace('{id}', row.id) }}</label>
|
|
65
|
-
|
|
77
|
+
{% if row.kind == 'decision' and approval_context and row.options %}
|
|
78
|
+
<select id="response-{{ row.id }}" data-response-id="{{ row.id }}"{% if is_closed %} disabled{% endif %}>
|
|
79
|
+
<option value="">{{ t('macros.forms.choose-one') }}</option>
|
|
80
|
+
{% for option in row.options %}<option value="{{ option.answer }}" data-disposition="{{ option.disposition }}"{% if option.role == 'recommended' %} data-recommended="true"{% endif %}{% if row.userInput | default('') == option.answer %} selected{% endif %}>{{ option.answer }}</option>{% endfor %}
|
|
81
|
+
</select>
|
|
82
|
+
{% else %}
|
|
83
|
+
<textarea id="response-{{ row.id }}" data-response-id="{{ row.id }}" rows="4"{% if is_closed %} disabled{% endif %}>{{ row.userInput | default('') }}</textarea>
|
|
84
|
+
{% endif %}
|
|
66
85
|
</article>
|
|
67
86
|
{% endfor %}
|
|
68
87
|
</section>
|
|
@@ -97,6 +97,31 @@
|
|
|
97
97
|
<table data-report-field="implementationPlanning.skippedAdrCandidates"><thead><tr><th>{{ t('tasks.implementation-planning.topic') }}</th><th>{{ t('tasks.implementation-planning.why') }}</th></tr></thead><tbody>{% for row in planning.skippedAdrCandidates %}<tr><td>{{ row.topic | inline_code }}</td><td>{{ row.reason | inline_code }}</td></tr>{% endfor %}</tbody></table>{% endif %}
|
|
98
98
|
</section>
|
|
99
99
|
|
|
100
|
+
{% if agentActivities %}
|
|
101
|
+
<section data-report-section="agent-activity">
|
|
102
|
+
<h2>{{ t('tasks.implementation-planning.what-each-agent-did') }}</h2>
|
|
103
|
+
<div class="activity-list">
|
|
104
|
+
{% for row in agentActivities %}
|
|
105
|
+
<article class="activity-card" id="id-{{ row.activityId }}">
|
|
106
|
+
<p class="eyebrow">{{ row.activityId }} · {{ row.agent }}</p>
|
|
107
|
+
<h3>{{ row.summary | inline_code }}</h3>
|
|
108
|
+
<p>{{ t('tasks.implementation-planning.result') }}: {{ row.outcome | inline_code }}</p>
|
|
109
|
+
<details>
|
|
110
|
+
<summary>{{ t('tasks.implementation-planning.commands-and-evidence') }}</summary>
|
|
111
|
+
<p><code>{{ row.kind }}</code></p>
|
|
112
|
+
{% if row.planItemIds %}<p>{{ row.planItemIds | join(', ') | inline_code }}</p>{% endif %}
|
|
113
|
+
{% for command in row.commands %}
|
|
114
|
+
<p><code>{{ command.command }}</code> · exit {{ command.exitCode }} · {{ command.outputSummary | inline_code }}</p>
|
|
115
|
+
{% endfor %}
|
|
116
|
+
{% if row.evidenceRefs %}<p>{{ row.evidenceRefs | join(', ') | inline_code }}</p>{% endif %}
|
|
117
|
+
{% if row.resultPath %}<p><code>{{ row.resultPath }}</code></p>{% endif %}
|
|
118
|
+
</details>
|
|
119
|
+
</article>
|
|
120
|
+
{% endfor %}
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
{% endif %}
|
|
124
|
+
|
|
100
125
|
<section data-report-section="open-decisions">
|
|
101
126
|
<h2>{{ t('tasks.implementation-planning.open-decisions-before-approval') }}</h2>
|
|
102
127
|
<div class="summary-grid">{% for row in openDecisions %}{{ summary_card(row.id ~ " · " ~ row.kind, row.statement, "important", anchor=row.id) }}{% else %}<p>{{ t('tasks.implementation-planning.no-open-decision-is-holding-approval') }}</p>{% endfor %}</div>
|
|
@@ -60,6 +60,10 @@
|
|
|
60
60
|
"readerSummary": "The shortest reader path: what was decided, what a human must do next, and which audit details can wait until a deeper review.",
|
|
61
61
|
"endStateCoverage": "One row per end-state id the brief pinned, and how this phase accounted for it. A row that is not `addressed` must say why."
|
|
62
62
|
},
|
|
63
|
+
"agentActivity": {
|
|
64
|
+
"heading": "Agent Activity Audit",
|
|
65
|
+
"intro": "Structured lead activity projected from this run's canonical event log."
|
|
66
|
+
},
|
|
63
67
|
"analysis": {
|
|
64
68
|
"basisIntro": "Immutable run snapshot, bounded scan scope, current-code facts, inferences, unknowns, and review disposition for this analysis.",
|
|
65
69
|
"projectIntro": "Project components, boundaries, entry points, dependencies, stores, external systems, and the shallow feature index.",
|