okstra 0.220.1 → 0.222.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/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -1
- package/docs/architecture.md +6 -4
- package/docs/cli.md +13 -9
- package/docs/project-structure-overview.md +5 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
- package/runtime/prompts/lead/phase-routing.md +1 -1
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +4 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +27 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +24 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +24 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +25 -0
- package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +20 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +3 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +14 -0
- package/runtime/python/okstra_ctl/business_flow/engine.py +1 -0
- package/runtime/python/okstra_ctl/convergence.py +155 -19
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +3 -2
- package/runtime/python/okstra_ctl/convergence_migration.py +10 -1
- package/runtime/python/okstra_ctl/convergence_provenance.py +69 -24
- package/runtime/python/okstra_ctl/convergence_reverify_prompt.py +4 -1
- package/runtime/python/okstra_ctl/correction_cli.py +50 -11
- package/runtime/python/okstra_ctl/correction_round.py +80 -11
- package/runtime/python/okstra_ctl/domain/mcp.py +43 -0
- package/runtime/python/okstra_ctl/domain/provider.py +2 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +89 -8
- package/runtime/python/okstra_ctl/file_size_cap.py +142 -0
- package/runtime/python/okstra_ctl/fix_cycles.py +14 -1
- package/runtime/python/okstra_ctl/git_reconcile.py +62 -8
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +3 -6
- package/runtime/python/okstra_ctl/manager_cli.py +11 -1
- package/runtime/python/okstra_ctl/manager_sync.py +38 -2
- package/runtime/python/okstra_ctl/mcp_cli.py +179 -0
- package/runtime/python/okstra_ctl/mcp_servers.py +421 -0
- package/runtime/python/okstra_ctl/next_phase.py +22 -9
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +4 -2
- package/runtime/python/okstra_ctl/phases/implementation/entry.py +6 -2
- package/runtime/python/okstra_ctl/phases/implementation/host-rules.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +5 -4
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-executor.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-self-check.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-verifier.md +3 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +19 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +14 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +2 -1
- package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +37 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +3 -3
- package/runtime/python/okstra_ctl/process_group.py +4 -0
- package/runtime/python/okstra_ctl/render.py +7 -48
- package/runtime/python/okstra_ctl/report_assembly.py +50 -13
- package/runtime/python/okstra_ctl/report_html/common.py +3 -1
- package/runtime/python/okstra_ctl/report_language.py +4 -5
- package/runtime/python/okstra_ctl/report_projections.py +45 -4
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +22 -1
- package/runtime/python/okstra_ctl/run.py +36 -5
- package/runtime/python/okstra_ctl/stage_done.py +37 -3
- package/runtime/python/okstra_ctl/stage_reconcile.py +49 -1
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +3 -3
- package/runtime/schemas/final-report-v3.0.schema.json +36 -0
- package/runtime/skills/_fragments/wizard-lead-takeover.md +1 -1
- package/runtime/skills/_fragments/wizard-relay-protocol.md +2 -2
- package/runtime/skills/okstra-discussion/SKILL.md +3 -3
- package/runtime/skills/okstra-manager/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +4 -3
- package/runtime/skills/okstra-setup/SKILL.md +2 -0
- package/runtime/skills/okstra-setup/references/project-config.md +34 -0
- package/runtime/templates/report-writer-prompt-preamble.md +1 -1
- package/runtime/templates/reports/html/base.template.html +6 -1
- package/runtime/templates/reports/html/i18n/en.json +1 -0
- package/runtime/templates/reports/html/i18n/ko.json +1 -0
- package/runtime/templates/reports/task-brief.template.md +3 -3
- package/runtime/validators/validate-run.py +18 -2
- package/runtime/validators/validate_session_conformance.py +19 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Bundled Antigravity provider catalog."""
|
|
2
2
|
from typing import Any, Mapping
|
|
3
3
|
|
|
4
|
+
from okstra_ctl.domain.mcp import McpInventory, McpInventoryProbe, VisibleMcpServer
|
|
4
5
|
from okstra_ctl.domain.provider import (
|
|
5
6
|
LeadLaunchSpec,
|
|
6
7
|
ModelSpec,
|
|
@@ -294,6 +295,31 @@ class AntigravityExecution:
|
|
|
294
295
|
)
|
|
295
296
|
|
|
296
297
|
|
|
298
|
+
_AGY_TABLE_HEADER = ("NAME", "TYPE", "STATUS", "COMMAND/URL")
|
|
299
|
+
_AGY_STATES = {"enabled": "enabled", "disabled": "disabled"}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def parse_mcp_list(stdout: str) -> McpInventory:
|
|
303
|
+
"""JSON 출력이 없어 실측한 두 형태만 읽는다(agy 1.4.2): 빈 목록 메시지와
|
|
304
|
+
`NAME TYPE STATUS COMMAND/URL` 표. 그 밖의 출력은 판정 불가로 돌린다."""
|
|
305
|
+
if stdout.strip() == "No MCP servers configured.":
|
|
306
|
+
return McpInventory(listed=True)
|
|
307
|
+
lines = [line for line in stdout.splitlines() if line.strip()]
|
|
308
|
+
if not lines or tuple(lines[0].split()) != _AGY_TABLE_HEADER:
|
|
309
|
+
return McpInventory.unavailable("agy mcp list output format is not recognised")
|
|
310
|
+
servers = []
|
|
311
|
+
for line in lines[1:]:
|
|
312
|
+
cells = line.split()
|
|
313
|
+
state = _AGY_STATES.get(cells[2]) if len(cells) >= 4 else None
|
|
314
|
+
if state is None:
|
|
315
|
+
return McpInventory.unavailable(f"agy mcp list row is not recognised: {line.strip()}")
|
|
316
|
+
servers.append(VisibleMcpServer(cells[0], state))
|
|
317
|
+
return McpInventory(listed=True, servers=tuple(servers))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
MCP_INVENTORY = McpInventoryProbe(argv=("agy", "mcp", "list"), parse=parse_mcp_list)
|
|
321
|
+
|
|
322
|
+
|
|
297
323
|
def create_provider() -> ProviderSpec:
|
|
298
324
|
return ProviderSpec(
|
|
299
325
|
provider="antigravity",
|
|
@@ -321,6 +347,7 @@ def create_provider() -> ProviderSpec:
|
|
|
321
347
|
executable="agy", model_flag="--model", prompt_flag="--prompt-interactive"
|
|
322
348
|
),
|
|
323
349
|
exec_strategy=AntigravityExecution(),
|
|
350
|
+
mcp_inventory=MCP_INVENTORY,
|
|
324
351
|
execution_normalizer=AntigravityExecutionNormalizer(),
|
|
325
352
|
served_model_normalizer=normalise_served_model,
|
|
326
353
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Bundled Claude provider catalog."""
|
|
2
2
|
from typing import Any, Mapping
|
|
3
|
+
from okstra_ctl.domain.mcp import McpInventory, McpInventoryProbe, VisibleMcpServer
|
|
3
4
|
from okstra_ctl.domain.provider import (
|
|
4
5
|
LeadLaunchSpec,
|
|
5
6
|
ModelSpec,
|
|
@@ -154,6 +155,28 @@ class ClaudeExecution:
|
|
|
154
155
|
)
|
|
155
156
|
|
|
156
157
|
|
|
158
|
+
def parse_mcp_list(stdout: str) -> McpInventory:
|
|
159
|
+
"""행 모양은 `<name>: <target> - <status>` 다. 이름 자체에 콜론이 들어가므로
|
|
160
|
+
(`plugin:design:slack`) 첫 `": "` 에서 자르고 상태는 마지막 `" - "` 뒤를 읽는다."""
|
|
161
|
+
servers: list[VisibleMcpServer] = []
|
|
162
|
+
for line in stdout.splitlines():
|
|
163
|
+
head, sep, rest = line.partition(": ")
|
|
164
|
+
if not sep or " - " not in rest:
|
|
165
|
+
continue
|
|
166
|
+
status = rest.rpartition(" - ")[2]
|
|
167
|
+
if "Connected" in status and not status.startswith("✗"):
|
|
168
|
+
state = "enabled"
|
|
169
|
+
elif "Needs authentication" in status:
|
|
170
|
+
state = "needs-auth"
|
|
171
|
+
else:
|
|
172
|
+
state = "disabled"
|
|
173
|
+
servers.append(VisibleMcpServer(head.strip(), state))
|
|
174
|
+
return McpInventory(listed=True, servers=tuple(servers))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
MCP_INVENTORY = McpInventoryProbe(argv=("claude", "mcp", "list"), parse=parse_mcp_list)
|
|
178
|
+
|
|
179
|
+
|
|
157
180
|
def create_provider() -> ProviderSpec:
|
|
158
181
|
return ProviderSpec(
|
|
159
182
|
provider="claude",
|
|
@@ -188,4 +211,5 @@ def create_provider() -> ProviderSpec:
|
|
|
188
211
|
),
|
|
189
212
|
exec_strategy=ClaudeExecution(),
|
|
190
213
|
served_model_normalizer=normalise_served_model,
|
|
214
|
+
mcp_inventory=MCP_INVENTORY,
|
|
191
215
|
)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"""Bundled Codex provider catalog."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from okstra_ctl.domain.mcp import McpInventory, McpInventoryProbe, VisibleMcpServer
|
|
2
5
|
from okstra_ctl.domain.provider import (
|
|
3
6
|
LeadLaunchSpec,
|
|
4
7
|
ModelSpec,
|
|
@@ -126,6 +129,26 @@ class CodexExecution:
|
|
|
126
129
|
)
|
|
127
130
|
|
|
128
131
|
|
|
132
|
+
def parse_mcp_list(stdout: str) -> McpInventory:
|
|
133
|
+
try:
|
|
134
|
+
rows = json.loads(stdout)
|
|
135
|
+
except json.JSONDecodeError as exc:
|
|
136
|
+
return McpInventory.unavailable(f"codex mcp list --json is not JSON ({exc})")
|
|
137
|
+
if not isinstance(rows, list):
|
|
138
|
+
return McpInventory.unavailable("codex mcp list --json is not a list")
|
|
139
|
+
servers = tuple(
|
|
140
|
+
VisibleMcpServer(row["name"], "enabled" if row.get("enabled") else "disabled")
|
|
141
|
+
for row in rows
|
|
142
|
+
if isinstance(row, dict) and isinstance(row.get("name"), str)
|
|
143
|
+
)
|
|
144
|
+
return McpInventory(listed=True, servers=servers)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
MCP_INVENTORY = McpInventoryProbe(
|
|
148
|
+
argv=("codex", "mcp", "list", "--json"), parse=parse_mcp_list,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
129
152
|
def create_provider() -> ProviderSpec:
|
|
130
153
|
return ProviderSpec(
|
|
131
154
|
provider="codex",
|
|
@@ -159,4 +182,5 @@ def create_provider() -> ProviderSpec:
|
|
|
159
182
|
exec_strategy=CodexExecution(),
|
|
160
183
|
execution_normalizer=CodexExecutionNormalizer(),
|
|
161
184
|
served_model_normalizer=normalise_served_model,
|
|
185
|
+
mcp_inventory=MCP_INVENTORY,
|
|
162
186
|
)
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any, Mapping
|
|
5
5
|
from urllib.parse import quote
|
|
6
6
|
|
|
7
|
+
from okstra_ctl.domain.mcp import McpInventory, McpInventoryProbe, VisibleMcpServer
|
|
7
8
|
from okstra_ctl.domain.provider import (
|
|
8
9
|
LeadLaunchSpec,
|
|
9
10
|
ModelSpec,
|
|
@@ -292,6 +293,29 @@ class GrokExecution:
|
|
|
292
293
|
)
|
|
293
294
|
|
|
294
295
|
|
|
296
|
+
def parse_mcp_list(stdout: str) -> McpInventory:
|
|
297
|
+
"""실측은 빈 목록뿐이었다(grok 1.0.46). 행 형식을 모르므로 `name` 이 없는
|
|
298
|
+
행은 빈 목록이 아니라 판정 불가로 돌린다."""
|
|
299
|
+
try:
|
|
300
|
+
rows = json.loads(stdout)
|
|
301
|
+
except json.JSONDecodeError as exc:
|
|
302
|
+
return McpInventory.unavailable(f"grok mcp list --json is not JSON ({exc})")
|
|
303
|
+
if not isinstance(rows, list):
|
|
304
|
+
return McpInventory.unavailable("grok mcp list --json is not a list")
|
|
305
|
+
servers: list[VisibleMcpServer] = []
|
|
306
|
+
for row in rows:
|
|
307
|
+
if not isinstance(row, dict) or not isinstance(row.get("name"), str):
|
|
308
|
+
return McpInventory.unavailable("grok mcp list --json row has no name field")
|
|
309
|
+
enabled = row.get("enabled", True) is not False
|
|
310
|
+
servers.append(VisibleMcpServer(row["name"], "enabled" if enabled else "disabled"))
|
|
311
|
+
return McpInventory(listed=True, servers=tuple(servers))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
MCP_INVENTORY = McpInventoryProbe(
|
|
315
|
+
argv=("grok", "mcp", "list", "--json"), parse=parse_mcp_list,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
295
319
|
def create_provider() -> ProviderSpec:
|
|
296
320
|
return ProviderSpec(
|
|
297
321
|
provider="grok",
|
|
@@ -324,4 +348,5 @@ def create_provider() -> ProviderSpec:
|
|
|
324
348
|
lead_launch=GROK_LEAD_LAUNCH,
|
|
325
349
|
exec_strategy=GrokExecution(),
|
|
326
350
|
served_model_normalizer=normalise_served_model,
|
|
351
|
+
mcp_inventory=MCP_INVENTORY,
|
|
327
352
|
)
|
|
@@ -7,7 +7,11 @@ from dataclasses import replace
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from okstra_ctl.adapters.providers.claude.adapter import
|
|
10
|
+
from okstra_ctl.adapters.providers.claude.adapter import (
|
|
11
|
+
ClaudeExecution,
|
|
12
|
+
parse_mcp_list as _claude_mcp_list,
|
|
13
|
+
)
|
|
14
|
+
from okstra_ctl.domain.mcp import McpInventory, McpInventoryProbe
|
|
11
15
|
from okstra_ctl.domain.provider import ModelSpec, ProviderSpec, ServedModelAttestation
|
|
12
16
|
from okstra_ctl.domain.role import ROLE_DEFINITIONS
|
|
13
17
|
from okstra_ctl.domain.worker_exec import ExecCommand, PolicySupport, WorkerExecRequest
|
|
@@ -111,6 +115,20 @@ class ZaiExecution:
|
|
|
111
115
|
return ClaudeExecution().policy_support()
|
|
112
116
|
|
|
113
117
|
|
|
118
|
+
def parse_mcp_list(stdout: str) -> McpInventory:
|
|
119
|
+
"""워커는 `--setting-sources ""` 로 떠서 사용자 범위 서버는 빠지고 claude.ai
|
|
120
|
+
커넥터만 남는다(2026-10-18, claude 2.1.302 실측)."""
|
|
121
|
+
listed = _claude_mcp_list(stdout)
|
|
122
|
+
return McpInventory(
|
|
123
|
+
listed=True,
|
|
124
|
+
servers=tuple(s for s in listed.servers if s.name.startswith("claude.ai ")),
|
|
125
|
+
note="calls through the z.ai backend are not verified",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
MCP_INVENTORY = McpInventoryProbe(argv=("claude", "mcp", "list"), parse=parse_mcp_list)
|
|
130
|
+
|
|
131
|
+
|
|
114
132
|
def create_provider() -> ProviderSpec:
|
|
115
133
|
roles = frozenset(role.id for role in ROLE_DEFINITIONS if role.id != "leader")
|
|
116
134
|
return ProviderSpec(
|
|
@@ -126,4 +144,5 @@ def create_provider() -> ProviderSpec:
|
|
|
126
144
|
}),
|
|
127
145
|
exec_strategy=ZaiExecution(),
|
|
128
146
|
served_model_normalizer=normalise_served_model,
|
|
147
|
+
mcp_inventory=MCP_INVENTORY,
|
|
129
148
|
)
|
|
@@ -340,6 +340,9 @@ def _materialize_run(
|
|
|
340
340
|
),
|
|
341
341
|
)
|
|
342
342
|
if args.audience == "report-writer" and uses_report_contract_v3(manifest):
|
|
343
|
+
# 조립이 `meta.reportLanguage` 로 옮기는 값과 같은 출처여야 한다.
|
|
344
|
+
report_language = str(manifest.get("reportLanguage") or "en")
|
|
345
|
+
anchor_lines = anchor_lines + (f"**Report Language:** {report_language}",)
|
|
343
346
|
body = _with_report_writer_sections(
|
|
344
347
|
args,
|
|
345
348
|
project_root,
|
|
@@ -65,6 +65,7 @@ WORKER_PROFILE_SECTIONS_BY_TASK_TYPE = {
|
|
|
65
65
|
"Worker direction-selection procedure",
|
|
66
66
|
"Pre-selection context exploration",
|
|
67
67
|
"Option evaluation rules",
|
|
68
|
+
"Technical evidence loop",
|
|
68
69
|
),
|
|
69
70
|
"implementation-planning": (
|
|
70
71
|
"Brief consumption",
|
|
@@ -114,6 +115,8 @@ def build_analysis_packet(
|
|
|
114
115
|
business_knowledge_text: str = "",
|
|
115
116
|
coverage_census_text: str = "",
|
|
116
117
|
has_selected_direction: bool = False,
|
|
118
|
+
mcp_servers_text: str = "",
|
|
119
|
+
technical_evidence_text: str = "",
|
|
117
120
|
) -> str:
|
|
118
121
|
"""Return the primary compact input for Claude/Codex/Antigravity analysers.
|
|
119
122
|
|
|
@@ -130,6 +133,12 @@ def build_analysis_packet(
|
|
|
130
133
|
|
|
131
134
|
`coverage_census_text` is the `## Coverage Census` block
|
|
132
135
|
`coverage_census.render_census_section` rendered; empty means no census.
|
|
136
|
+
|
|
137
|
+
`mcp_servers_text` 는 prepare 가 provider 별 도달성까지 렌더한 선언 서버
|
|
138
|
+
목록이다. 빈 문자열이면 선언이 없는 것이고 절을 싣지 않는다.
|
|
139
|
+
|
|
140
|
+
`technical_evidence_text` 는 carry-in 이 technical-verification 결과일 때
|
|
141
|
+
`technical_evidence_packet_block` 이 렌더한 증거 위치 절이다.
|
|
133
142
|
"""
|
|
134
143
|
brief_text = task_brief_path.read_text(encoding="utf-8")
|
|
135
144
|
group_context_text = _read_optional(group_context_path) if group_context_path else ""
|
|
@@ -150,6 +159,9 @@ def build_analysis_packet(
|
|
|
150
159
|
has_selected_direction,
|
|
151
160
|
)
|
|
152
161
|
)
|
|
162
|
+
# 짧은 포인터 절이라 46KB 에서 잘리는 읽기에서도 도착하도록 브리프보다 앞에 둔다.
|
|
163
|
+
if technical_evidence_text:
|
|
164
|
+
parts.extend(["", technical_evidence_text])
|
|
153
165
|
# 그룹 문서는 사람 절과 okstra 메모리 영역으로 갈라 싣는다. 사람 절("왜")은
|
|
154
166
|
# 브리프 발췌보다 앞 — 짧고, 46KB 에서 잘리는 읽기(agy view_file)에서도
|
|
155
167
|
# 도착해야 한다. 형제 task 의 결론은 브리프 뒤 — 브리프를 밀어내면 안 된다.
|
|
@@ -158,6 +170,8 @@ def build_analysis_packet(
|
|
|
158
170
|
)
|
|
159
171
|
parts.extend(_group_context_block(group_human))
|
|
160
172
|
parts.extend(_brief_block(brief_text))
|
|
173
|
+
if mcp_servers_text:
|
|
174
|
+
parts.extend(["", "## Available MCP Servers", "", mcp_servers_text])
|
|
161
175
|
if direct_work_text:
|
|
162
176
|
parts.extend(["", direct_work_text, ""])
|
|
163
177
|
parts.extend(_group_memory_block(
|
|
@@ -388,6 +388,7 @@ def _instructions(
|
|
|
388
388
|
"In relationships, from and to must each be a projectId listed under projects in the Source Material; put modules, files and planned work in data, not in from/to.",
|
|
389
389
|
"Cite evidence only from files Git tracks or would add in a candidate root. Git-ignored files (for example .project-docs/) are outside the captured source inventory and are rejected; name what you learned from them in gaps instead.",
|
|
390
390
|
"Explain the product business from start to finish for a new developer: purpose, rules, input/output, states, failure, retry and recovery. Link each step and relationship to factKeys.",
|
|
391
|
+
"factKeys in steps and relationships may name only facts returned in this response. Stored knowledge claims are not accepted by key; to link one, re-read its source and return it as a fact with evidence.",
|
|
391
392
|
"Use stable keys in business/subject/attribute form. Facts are source-backed assertions; expected changes remain expected. Cite inspected evidence with one-based line/endLine; each excerpt must copy the whole line..endLine range verbatim, including indentation, with no elision or omitted lines. Tests are static evidence unless a supplied existing verification record supports the execution claim.",
|
|
392
393
|
"For post mode, use the supplied project-specific baseline. Never invent missing before-state. Describe the actual delta and distinguish confirmed side effects from risks.",
|
|
393
394
|
"For contribute mode, extract newly observed business facts from the supplied validated task record and inspect source to confirm them. Empty facts are valid. Do not copy the report narrative as established truth.",
|