akernel-runtime 0.1.0__py3-none-any.whl
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.
- akernel_runtime-0.1.0.dist-info/METADATA +270 -0
- akernel_runtime-0.1.0.dist-info/RECORD +40 -0
- akernel_runtime-0.1.0.dist-info/WHEEL +5 -0
- akernel_runtime-0.1.0.dist-info/entry_points.txt +2 -0
- akernel_runtime-0.1.0.dist-info/licenses/LICENSE +201 -0
- akernel_runtime-0.1.0.dist-info/licenses/NOTICE +4 -0
- akernel_runtime-0.1.0.dist-info/top_level.txt +1 -0
- context_kernel/__init__.py +4 -0
- context_kernel/__main__.py +5 -0
- context_kernel/agent_reports.py +188 -0
- context_kernel/benchmarks.py +493 -0
- context_kernel/budget.py +72 -0
- context_kernel/cli.py +2953 -0
- context_kernel/context.py +161 -0
- context_kernel/evals.py +347 -0
- context_kernel/global_memory.py +126 -0
- context_kernel/loop.py +1617 -0
- context_kernel/marketplace.py +194 -0
- context_kernel/marketplace_data/skills/context_budget.json +27 -0
- context_kernel/marketplace_data/skills/context_compaction.json +27 -0
- context_kernel/marketplace_data/skills/edit_file.json +27 -0
- context_kernel/marketplace_data/skills/index.json +66 -0
- context_kernel/marketplace_data/skills/long_task_planning.json +27 -0
- context_kernel/marketplace_data/skills/multi_file_bugfix.json +28 -0
- context_kernel/memory.py +515 -0
- context_kernel/models.py +144 -0
- context_kernel/planner.py +155 -0
- context_kernel/policy.py +271 -0
- context_kernel/project.py +317 -0
- context_kernel/providers.py +1264 -0
- context_kernel/report_costs.py +375 -0
- context_kernel/runner.py +78 -0
- context_kernel/skills.py +318 -0
- context_kernel/state_writer.py +108 -0
- context_kernel/storage.py +171 -0
- context_kernel/tasks.py +549 -0
- context_kernel/text.py +42 -0
- context_kernel/tokenizer.py +22 -0
- context_kernel/tools.py +544 -0
- context_kernel/verifier.py +77 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urljoin, urlparse
|
|
7
|
+
from urllib.request import url2pathname, urlopen
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .models import Skill
|
|
11
|
+
from .storage import Workspace
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SUPPORTED_INDEX_VERSION = 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def default_marketplace_index() -> Path:
|
|
18
|
+
return Path(__file__).resolve().parent / "marketplace_data" / "skills" / "index.json"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_marketplace(index: str | Path | None = None, *, allow_remote: bool = True) -> dict[str, Any]:
|
|
22
|
+
reference = str(index) if index is not None else str(default_marketplace_index())
|
|
23
|
+
data = load_json_reference(reference, allow_remote=allow_remote)
|
|
24
|
+
entries = data.get("skills", [])
|
|
25
|
+
if not isinstance(entries, list):
|
|
26
|
+
raise ValueError(f"Marketplace index has invalid skills array: {reference}")
|
|
27
|
+
index_version = int(data.get("version", 1))
|
|
28
|
+
if index_version > SUPPORTED_INDEX_VERSION:
|
|
29
|
+
raise ValueError(f"Marketplace index version {index_version} is newer than supported version {SUPPORTED_INDEX_VERSION}.")
|
|
30
|
+
return {
|
|
31
|
+
"path": reference,
|
|
32
|
+
"version": index_version,
|
|
33
|
+
"name": data.get("name", "Unnamed marketplace"),
|
|
34
|
+
"trusted": not is_remote_reference(reference),
|
|
35
|
+
"skills": [normalize_marketplace_entry(entry, reference) for entry in entries if isinstance(entry, dict)],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def list_marketplace_skills(index: str | Path | None = None) -> list[dict[str, Any]]:
|
|
40
|
+
return list(load_marketplace(index)["skills"])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def install_marketplace_skill(
|
|
44
|
+
workspace: Workspace,
|
|
45
|
+
skill_id: str,
|
|
46
|
+
*,
|
|
47
|
+
index: str | Path | None = None,
|
|
48
|
+
trust_remote: bool = False,
|
|
49
|
+
ignore_compat: bool = False,
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
market = load_marketplace(index)
|
|
52
|
+
entries = {str(item.get("id")): item for item in market["skills"] if isinstance(item, dict)}
|
|
53
|
+
entry = entries.get(skill_id)
|
|
54
|
+
if not entry:
|
|
55
|
+
raise KeyError(f"Unknown marketplace skill: {skill_id}")
|
|
56
|
+
compatibility = check_entry_compatibility(entry)
|
|
57
|
+
if not compatibility["ok"] and not ignore_compat:
|
|
58
|
+
raise ValueError(f"Marketplace skill is not compatible: {skill_id}: {'; '.join(compatibility['warnings'])}")
|
|
59
|
+
source_ref = str(entry.get("resolved_path") or entry.get("path") or "")
|
|
60
|
+
if is_remote_reference(source_ref) and not trust_remote:
|
|
61
|
+
raise PermissionError(f"Installing remote marketplace skill requires --trust-remote: {source_ref}")
|
|
62
|
+
skill_data, source_label = load_skill_source(source_ref)
|
|
63
|
+
validation = validate_skill_data(skill_data)
|
|
64
|
+
if not validation["ok"]:
|
|
65
|
+
raise ValueError(f"Marketplace skill is invalid: {skill_id}")
|
|
66
|
+
skill = Skill.from_dict(skill_data)
|
|
67
|
+
destination = workspace.skills_dir / f"{skill.id}.json"
|
|
68
|
+
Workspace.write_json(destination, skill.to_dict())
|
|
69
|
+
return {
|
|
70
|
+
"id": skill.id,
|
|
71
|
+
"name": skill.name,
|
|
72
|
+
"version": entry.get("version", "0.0.0"),
|
|
73
|
+
"source": source_label,
|
|
74
|
+
"marketplace": market["name"],
|
|
75
|
+
"compatibility": compatibility,
|
|
76
|
+
"workspace": str(workspace.root),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def normalize_marketplace_entry(entry: dict[str, Any], index_ref: str) -> dict[str, Any]:
|
|
81
|
+
normalized = dict(entry)
|
|
82
|
+
normalized.setdefault("version", "0.0.0")
|
|
83
|
+
normalized.setdefault("license", "unknown")
|
|
84
|
+
normalized.setdefault("trust", "packaged" if not is_remote_reference(index_ref) else "remote")
|
|
85
|
+
path = str(normalized.get("path", ""))
|
|
86
|
+
normalized["resolved_path"] = resolve_marketplace_path(index_ref, path)
|
|
87
|
+
normalized["remote"] = is_remote_reference(str(normalized["resolved_path"]))
|
|
88
|
+
normalized["compatibility"] = normalize_compatibility(normalized.get("compatibility"))
|
|
89
|
+
normalized["compatibility_check"] = check_entry_compatibility(normalized)
|
|
90
|
+
return normalized
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def normalize_compatibility(data: Any) -> dict[str, str]:
|
|
94
|
+
if not isinstance(data, dict):
|
|
95
|
+
return {"context_kernel": ">=0.1.0"}
|
|
96
|
+
value = data.get("context_kernel") or data.get("context-kernel") or ">=0.1.0"
|
|
97
|
+
return {"context_kernel": str(value)}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def check_entry_compatibility(entry: dict[str, Any]) -> dict[str, Any]:
|
|
101
|
+
requirement = normalize_compatibility(entry.get("compatibility")).get("context_kernel", ">=0.1.0")
|
|
102
|
+
ok = version_satisfies(__version__, requirement)
|
|
103
|
+
warnings = [] if ok else [f"context_kernel {__version__} does not satisfy {requirement}"]
|
|
104
|
+
return {"ok": ok, "context_kernel": requirement, "current": __version__, "warnings": warnings}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def version_satisfies(current: str, requirement: str) -> bool:
|
|
108
|
+
requirement = requirement.strip()
|
|
109
|
+
if not requirement or requirement == "*":
|
|
110
|
+
return True
|
|
111
|
+
for operator in [">=", "<=", "==", ">", "<"]:
|
|
112
|
+
if requirement.startswith(operator):
|
|
113
|
+
expected = requirement[len(operator) :].strip()
|
|
114
|
+
comparison = compare_versions(current, expected)
|
|
115
|
+
return {
|
|
116
|
+
">=": comparison >= 0,
|
|
117
|
+
"<=": comparison <= 0,
|
|
118
|
+
"==": comparison == 0,
|
|
119
|
+
">": comparison > 0,
|
|
120
|
+
"<": comparison < 0,
|
|
121
|
+
}[operator]
|
|
122
|
+
return compare_versions(current, requirement) == 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def compare_versions(left: str, right: str) -> int:
|
|
126
|
+
left_parts = parse_version(left)
|
|
127
|
+
right_parts = parse_version(right)
|
|
128
|
+
max_len = max(len(left_parts), len(right_parts))
|
|
129
|
+
left_parts.extend([0] * (max_len - len(left_parts)))
|
|
130
|
+
right_parts.extend([0] * (max_len - len(right_parts)))
|
|
131
|
+
return (left_parts > right_parts) - (left_parts < right_parts)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def parse_version(version: str) -> list[int]:
|
|
135
|
+
parts: list[int] = []
|
|
136
|
+
for part in version.replace("-", ".").split("."):
|
|
137
|
+
digits = "".join(ch for ch in part if ch.isdigit())
|
|
138
|
+
parts.append(int(digits or 0))
|
|
139
|
+
return parts or [0]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def resolve_marketplace_path(index_ref: str, path: str) -> str:
|
|
143
|
+
if not path:
|
|
144
|
+
return path
|
|
145
|
+
if is_remote_reference(path) or is_file_url(path):
|
|
146
|
+
return path
|
|
147
|
+
if is_remote_reference(index_ref):
|
|
148
|
+
return urljoin(index_ref, path)
|
|
149
|
+
base = Path(file_url_to_path(index_ref) if is_file_url(index_ref) else index_ref)
|
|
150
|
+
return str((base.parent / path).resolve())
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def load_skill_source(reference: str) -> tuple[dict[str, Any], str]:
|
|
154
|
+
data = load_json_reference(reference, allow_remote=True)
|
|
155
|
+
return data, reference
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def validate_skill_data(data: dict[str, Any]) -> dict[str, Any]:
|
|
159
|
+
skill = Skill.from_dict(data)
|
|
160
|
+
return {
|
|
161
|
+
"ok": True,
|
|
162
|
+
"id": skill.id,
|
|
163
|
+
"name": skill.name,
|
|
164
|
+
"warnings": [],
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def load_json_reference(reference: str, *, allow_remote: bool) -> dict[str, Any]:
|
|
169
|
+
if is_remote_reference(reference):
|
|
170
|
+
if not allow_remote:
|
|
171
|
+
raise PermissionError(f"Remote marketplace references are not allowed: {reference}")
|
|
172
|
+
with urlopen(reference, timeout=15) as response:
|
|
173
|
+
return json.loads(response.read().decode("utf-8-sig"))
|
|
174
|
+
path = Path(file_url_to_path(reference) if is_file_url(reference) else reference)
|
|
175
|
+
return Workspace.read_json(path)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_remote_reference(reference: str) -> bool:
|
|
179
|
+
parsed = urlparse(str(reference))
|
|
180
|
+
return parsed.scheme in {"http", "https"}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def is_file_url(reference: str) -> bool:
|
|
184
|
+
return urlparse(str(reference)).scheme == "file"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def file_url_to_path(reference: str) -> str:
|
|
188
|
+
parsed = urlparse(reference)
|
|
189
|
+
path = url2pathname(parsed.path)
|
|
190
|
+
if parsed.netloc:
|
|
191
|
+
return f"//{parsed.netloc}{path}"
|
|
192
|
+
if len(path) >= 3 and path[0] == "/" and path[2] == ":":
|
|
193
|
+
return path[1:]
|
|
194
|
+
return path
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "context_budget",
|
|
3
|
+
"name": "Context Budget",
|
|
4
|
+
"summary": "Assemble minimal context under a fixed token budget.",
|
|
5
|
+
"intent": "Use when a request asks for context selection, prompt reduction, token savings, or budget-aware routing.",
|
|
6
|
+
"inputs": ["request", "token_budget", "available_context"],
|
|
7
|
+
"outputs": ["context_packet", "budget_report", "omissions"],
|
|
8
|
+
"constraints": [
|
|
9
|
+
"Include task-critical evidence before convenience context.",
|
|
10
|
+
"Prefer skill contracts over full procedures.",
|
|
11
|
+
"Explain important omissions."
|
|
12
|
+
],
|
|
13
|
+
"failure_modes": [
|
|
14
|
+
"Budget is too small for the requested task.",
|
|
15
|
+
"Relevant memory cannot be found.",
|
|
16
|
+
"Skill contracts are too vague to route safely."
|
|
17
|
+
],
|
|
18
|
+
"procedure": [
|
|
19
|
+
"Estimate request cost.",
|
|
20
|
+
"Reserve space for model response.",
|
|
21
|
+
"Select relevant memory and skills.",
|
|
22
|
+
"Check the final packet against the budget."
|
|
23
|
+
],
|
|
24
|
+
"examples": [
|
|
25
|
+
"Build a 1200-token packet for a CLI implementation task."
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "context_compaction",
|
|
3
|
+
"name": "Context Compaction",
|
|
4
|
+
"summary": "Compress task state and memory without losing required evidence.",
|
|
5
|
+
"intent": "Use when context pressure is high, memory is noisy, or a long task needs a compact resume brief.",
|
|
6
|
+
"inputs": ["task_brief", "memory_records", "token_budget", "required_evidence"],
|
|
7
|
+
"outputs": ["compact_brief", "kept_records", "omitted_records", "risk_notes"],
|
|
8
|
+
"constraints": [
|
|
9
|
+
"Do not omit facts required to verify the next action.",
|
|
10
|
+
"Prefer typed memory over chat transcript fragments.",
|
|
11
|
+
"Keep omissions explicit and auditable."
|
|
12
|
+
],
|
|
13
|
+
"failure_modes": [
|
|
14
|
+
"Compaction removes the evidence needed for a safe edit.",
|
|
15
|
+
"Stale memories outrank recent project state.",
|
|
16
|
+
"The brief preserves too much transcript-like detail."
|
|
17
|
+
],
|
|
18
|
+
"procedure": [
|
|
19
|
+
"Identify the next action and required evidence.",
|
|
20
|
+
"Keep decisions, preferences, and active blockers before stale task notes.",
|
|
21
|
+
"Summarize repeated state into one durable record.",
|
|
22
|
+
"Prune lower-priority memory records only when they are recoverable from traces."
|
|
23
|
+
],
|
|
24
|
+
"examples": [
|
|
25
|
+
"Compact a long implementation task into a resume brief under 1200 tokens."
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "edit_file",
|
|
3
|
+
"name": "Edit File",
|
|
4
|
+
"summary": "Modify local files with scoped, reviewable changes.",
|
|
5
|
+
"intent": "Use when a request requires changing source files, documentation, or project configuration.",
|
|
6
|
+
"inputs": ["file_path", "change_request"],
|
|
7
|
+
"outputs": ["patch", "verification_result"],
|
|
8
|
+
"constraints": [
|
|
9
|
+
"Preserve unrelated changes.",
|
|
10
|
+
"Prefer patch-based edits.",
|
|
11
|
+
"Verify behavior with the smallest relevant command."
|
|
12
|
+
],
|
|
13
|
+
"failure_modes": [
|
|
14
|
+
"Target file is ambiguous.",
|
|
15
|
+
"Existing changes conflict with the requested edit.",
|
|
16
|
+
"Verification command is unavailable."
|
|
17
|
+
],
|
|
18
|
+
"procedure": [
|
|
19
|
+
"Inspect the relevant files.",
|
|
20
|
+
"Make the smallest focused edit.",
|
|
21
|
+
"Run targeted verification.",
|
|
22
|
+
"Report changed files and verification result."
|
|
23
|
+
],
|
|
24
|
+
"examples": [
|
|
25
|
+
"Update a CLI help string and run the command smoke test."
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 2,
|
|
3
|
+
"name": "Context Kernel packaged skill marketplace",
|
|
4
|
+
"skills": [
|
|
5
|
+
{
|
|
6
|
+
"id": "context_budget",
|
|
7
|
+
"name": "Context Budget",
|
|
8
|
+
"summary": "Plan and inspect token-aware context budget work.",
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"publisher": "context-kernel",
|
|
12
|
+
"compatibility": {
|
|
13
|
+
"context_kernel": ">=0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"path": "context_budget.json"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "edit_file",
|
|
19
|
+
"name": "Edit File",
|
|
20
|
+
"summary": "Make focused file edits while preserving unrelated changes.",
|
|
21
|
+
"version": "0.1.0",
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"publisher": "context-kernel",
|
|
24
|
+
"compatibility": {
|
|
25
|
+
"context_kernel": ">=0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"path": "edit_file.json"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "multi_file_bugfix",
|
|
31
|
+
"name": "Multi File Bugfix",
|
|
32
|
+
"summary": "Investigate and repair bugs that may span multiple files.",
|
|
33
|
+
"version": "0.1.0",
|
|
34
|
+
"license": "Apache-2.0",
|
|
35
|
+
"publisher": "context-kernel",
|
|
36
|
+
"compatibility": {
|
|
37
|
+
"context_kernel": ">=0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"path": "multi_file_bugfix.json"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "long_task_planning",
|
|
43
|
+
"name": "Long Task Planning",
|
|
44
|
+
"summary": "Break long-running work into resumable checkpoints.",
|
|
45
|
+
"version": "0.1.0",
|
|
46
|
+
"license": "Apache-2.0",
|
|
47
|
+
"publisher": "context-kernel",
|
|
48
|
+
"compatibility": {
|
|
49
|
+
"context_kernel": ">=0.1.0"
|
|
50
|
+
},
|
|
51
|
+
"path": "long_task_planning.json"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "context_compaction",
|
|
55
|
+
"name": "Context Compaction",
|
|
56
|
+
"summary": "Compress task state and memory without losing required evidence.",
|
|
57
|
+
"version": "0.1.0",
|
|
58
|
+
"license": "Apache-2.0",
|
|
59
|
+
"publisher": "context-kernel",
|
|
60
|
+
"compatibility": {
|
|
61
|
+
"context_kernel": ">=0.1.0"
|
|
62
|
+
},
|
|
63
|
+
"path": "context_compaction.json"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "long_task_planning",
|
|
3
|
+
"name": "Long Task Planning",
|
|
4
|
+
"summary": "Break long-running work into resumable checkpoints.",
|
|
5
|
+
"intent": "Use when a task spans multiple agent runs, milestones, or verification phases.",
|
|
6
|
+
"inputs": ["goal", "current_task_state", "constraints", "known_blockers"],
|
|
7
|
+
"outputs": ["milestones", "next_checkpoint", "risks", "resume_brief"],
|
|
8
|
+
"constraints": [
|
|
9
|
+
"Keep checkpoints compact enough to enter future context packets.",
|
|
10
|
+
"Record decisions separately from transient notes.",
|
|
11
|
+
"Surface blockers early instead of hiding them in a long plan."
|
|
12
|
+
],
|
|
13
|
+
"failure_modes": [
|
|
14
|
+
"Plan becomes a verbose transcript instead of durable state.",
|
|
15
|
+
"Milestones are too broad to verify.",
|
|
16
|
+
"Important assumptions are not recorded."
|
|
17
|
+
],
|
|
18
|
+
"procedure": [
|
|
19
|
+
"State the target outcome in one sentence.",
|
|
20
|
+
"Create small milestones with verification signals.",
|
|
21
|
+
"Write only the next actionable checkpoint into task state.",
|
|
22
|
+
"Archive or prune stale task-state memories after completion."
|
|
23
|
+
],
|
|
24
|
+
"examples": [
|
|
25
|
+
"Plan a multi-day CLI runtime refactor without replaying the full conversation."
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "multi_file_bugfix",
|
|
3
|
+
"name": "Multi File Bugfix",
|
|
4
|
+
"summary": "Investigate and repair bugs that may span multiple files.",
|
|
5
|
+
"intent": "Use when a failing behavior, test, or stack trace may require edits across more than one source file.",
|
|
6
|
+
"inputs": ["failure_output", "project_profile", "relevant_files", "verification_command"],
|
|
7
|
+
"outputs": ["root_cause", "patch_plan", "applied_edits", "verification_result"],
|
|
8
|
+
"constraints": [
|
|
9
|
+
"Read failure evidence before editing.",
|
|
10
|
+
"Prefer the smallest coherent multi-file patch.",
|
|
11
|
+
"Do not rewrite unrelated modules.",
|
|
12
|
+
"Run the project verification command after edits when policy allows it."
|
|
13
|
+
],
|
|
14
|
+
"failure_modes": [
|
|
15
|
+
"Failure output does not identify a useful file or symbol.",
|
|
16
|
+
"Patch requires behavior knowledge outside the available context.",
|
|
17
|
+
"Verification command is blocked or unavailable."
|
|
18
|
+
],
|
|
19
|
+
"procedure": [
|
|
20
|
+
"Map the failure to likely files and symbols.",
|
|
21
|
+
"Read only the minimum files needed to understand the cause.",
|
|
22
|
+
"Patch related files as a single bounded batch when edits are coupled.",
|
|
23
|
+
"Run verification and stop with a diagnostic if the failure changes unexpectedly."
|
|
24
|
+
],
|
|
25
|
+
"examples": [
|
|
26
|
+
"Fix a failing parser test where the implementation and expected fixture both need inspection."
|
|
27
|
+
]
|
|
28
|
+
}
|