@homericintelligence/athena-opencode 0.4.4
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/LICENSE +29 -0
- package/NOTICE +22 -0
- package/README.md +45 -0
- package/package.json +29 -0
- package/plugin.js +54 -0
- package/skills/THIRD_PARTY_LICENSES.md +50 -0
- package/skills/_cli.py +152 -0
- package/skills/advise/SKILL.md +62 -0
- package/skills/advise/scripts/list_retrievable_skills.py +49 -0
- package/skills/brainstorm/SKILL.md +110 -0
- package/skills/change-review/SKILL.md +68 -0
- package/skills/change-review/references/scope-resolution.md +52 -0
- package/skills/change-review/scripts/resolve_scope.py +1219 -0
- package/skills/finalize-plan/SKILL.md +129 -0
- package/skills/git-worktrees/SKILL.md +113 -0
- package/skills/git-worktrees/scripts/prepare_worktree.py +153 -0
- package/skills/issue-review/SKILL.md +67 -0
- package/skills/learn/SKILL.md +208 -0
- package/skills/myrmidon-swarm/SKILL.md +93 -0
- package/skills/plan-issue/SKILL.md +70 -0
- package/skills/pr-review/SKILL.md +114 -0
- package/skills/pr-review/references/criteria.md +26 -0
- package/skills/pr-review/references/delivery.md +135 -0
- package/skills/pr-review/references/evidence.md +233 -0
- package/skills/pr-review/references/prevalidated.md +155 -0
- package/skills/pr-review/scripts/collect_evidence.py +1478 -0
- package/skills/pr-review/scripts/diff_context.py +74 -0
- package/skills/pr-review/scripts/materialize_snapshot.py +731 -0
- package/skills/pr-review/scripts/pr_identity.py +80 -0
- package/skills/pr-review/scripts/resolve_pr.py +258 -0
- package/skills/repo-review/SKILL.md +119 -0
- package/skills/systematic-debugging/SKILL.md +199 -0
- package/skills/systematic-debugging/scripts/repository_evidence.py +77 -0
- package/skills/test-driven-development/SKILL.md +75 -0
- package/skills/tidy/SKILL.md +71 -0
- package/skills/tidy/scripts/run_tidy.py +43 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Validate pull-request identifiers shared by PR review helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
PR_URL = re.compile(r"https://github\.com/[^/\s]+/[^/\s]+/pull/[1-9][0-9]*")
|
|
9
|
+
COMMIT_OID = re.compile(r"[0-9a-f]{40}\Z")
|
|
10
|
+
GITHUB_REPOSITORY = re.compile(r"[A-Za-z0-9][A-Za-z0-9-]*/[A-Za-z0-9._-]+\Z")
|
|
11
|
+
GITHUB_HOST = "github.com"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_pr_identifier(value: str) -> None:
|
|
15
|
+
"""Require a positive PR number or canonical GitHub pull-request URL."""
|
|
16
|
+
if (value.isascii() and value.isdigit() and int(value) > 0) or PR_URL.fullmatch(
|
|
17
|
+
value
|
|
18
|
+
):
|
|
19
|
+
return
|
|
20
|
+
raise RuntimeError(f"invalid pull-request identifier: {value!r}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def require_commit_oid(value: object, label: str) -> str:
|
|
24
|
+
"""Return one canonical immutable commit OID or fail closed."""
|
|
25
|
+
if not isinstance(value, str) or COMMIT_OID.fullmatch(value) is None:
|
|
26
|
+
raise RuntimeError(f"{label} must be a lowercase 40-hex Git commit OID")
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def require_github_repository(value: object, label: str) -> str:
|
|
31
|
+
"""Return a canonical owner/repository target or reject unsafe input."""
|
|
32
|
+
if not isinstance(value, str) or GITHUB_REPOSITORY.fullmatch(value) is None:
|
|
33
|
+
raise RuntimeError(f"{label} must be a canonical GitHub owner/repository")
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def require_github_host(value: object, label: str) -> str:
|
|
38
|
+
"""Return the supported canonical GitHub hostname or fail closed."""
|
|
39
|
+
if value != GITHUB_HOST:
|
|
40
|
+
raise RuntimeError(f"{label} must be {GITHUB_HOST}")
|
|
41
|
+
return GITHUB_HOST
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def canonical_pull_request_url(repository: object, number: object) -> str:
|
|
45
|
+
"""Return one exact public-GitHub pull-request URL from trusted identity."""
|
|
46
|
+
canonical_repository = require_github_repository(repository, "repository")
|
|
47
|
+
if isinstance(number, bool) or not isinstance(number, int) or number < 1:
|
|
48
|
+
raise RuntimeError("pull-request number must be a positive integer")
|
|
49
|
+
return f"https://{GITHUB_HOST}/{canonical_repository}/pull/{number}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def pull_request_number(value: str) -> int:
|
|
53
|
+
"""Return the positive number encoded by a validated PR identifier."""
|
|
54
|
+
validate_pr_identifier(value)
|
|
55
|
+
if value.isdigit():
|
|
56
|
+
return int(value)
|
|
57
|
+
path = urlparse(value).path.rstrip("/")
|
|
58
|
+
return int(path.rsplit("/", maxsplit=1)[-1])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def require_canonical_pull_request_url(
|
|
62
|
+
value: object, repository: object, number: object, label: str
|
|
63
|
+
) -> str:
|
|
64
|
+
"""Require the exact canonical URL for a retained pull-request identity."""
|
|
65
|
+
canonical_url = canonical_pull_request_url(repository, number)
|
|
66
|
+
if value != canonical_url:
|
|
67
|
+
raise RuntimeError(f"{label} must be the canonical GitHub pull-request URL")
|
|
68
|
+
return canonical_url
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def repository_from_pr_url(url: str, number: int) -> str:
|
|
72
|
+
"""Return owner/repository after validating a canonical PR URL and number."""
|
|
73
|
+
parsed_url = urlparse(url)
|
|
74
|
+
path_parts = parsed_url.path.strip("/").split("/")
|
|
75
|
+
if len(path_parts) != 4 or path_parts[2] != "pull" or path_parts[3] != str(number):
|
|
76
|
+
raise RuntimeError(f"GitHub returned invalid pull-request URL: {url}")
|
|
77
|
+
repository = "/".join(path_parts[:2])
|
|
78
|
+
if url != canonical_pull_request_url(repository, number):
|
|
79
|
+
raise RuntimeError(f"GitHub returned invalid pull-request URL: {url}")
|
|
80
|
+
return repository
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Resolve an explicit PR or the sole open PR for the current branch."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
if __package__ in {None, ""}:
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
|
|
15
|
+
|
|
16
|
+
from pr_identity import (
|
|
17
|
+
canonical_pull_request_url,
|
|
18
|
+
pull_request_number,
|
|
19
|
+
repository_from_pr_url,
|
|
20
|
+
require_commit_oid,
|
|
21
|
+
require_github_host,
|
|
22
|
+
require_github_repository,
|
|
23
|
+
validate_pr_identifier,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from skills._cli import (
|
|
27
|
+
argument_parser,
|
|
28
|
+
git_read_arguments,
|
|
29
|
+
git_read_environment,
|
|
30
|
+
run_command,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
FIELDS = "number,url,state,headRefName,baseRefName,headRefOid,baseRefOid"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class RepositoryTarget:
|
|
38
|
+
"""A forge target supplied without ambient CLI or checkout inference."""
|
|
39
|
+
|
|
40
|
+
host: str
|
|
41
|
+
repository: str
|
|
42
|
+
|
|
43
|
+
def repository_argument(self) -> str:
|
|
44
|
+
"""Return the fully qualified repository target accepted by gh."""
|
|
45
|
+
return f"{self.host}/{self.repository}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def command(*arguments: str) -> str:
|
|
49
|
+
result = run_command(arguments, capture_output=True, text=True, check=False)
|
|
50
|
+
if result.returncode != 0:
|
|
51
|
+
message = result.stderr.strip() or f"command failed: {' '.join(arguments)}"
|
|
52
|
+
raise RuntimeError(message)
|
|
53
|
+
return result.stdout
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def current_branch() -> str:
|
|
57
|
+
"""Return the current branch through the hermetic Git read boundary."""
|
|
58
|
+
result = run_command(
|
|
59
|
+
["git", *git_read_arguments(), "branch", "--show-current"],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
env=git_read_environment(),
|
|
62
|
+
text=True,
|
|
63
|
+
check=False,
|
|
64
|
+
)
|
|
65
|
+
if result.returncode != 0:
|
|
66
|
+
message = result.stderr.strip() or "git branch --show-current failed"
|
|
67
|
+
raise RuntimeError(message)
|
|
68
|
+
return result.stdout.strip()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_object(output: str) -> dict[str, Any]:
|
|
72
|
+
value = json.loads(output)
|
|
73
|
+
if not isinstance(value, dict):
|
|
74
|
+
raise TypeError("GitHub returned an invalid pull-request object")
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def target_from_arguments(
|
|
79
|
+
parser: Any,
|
|
80
|
+
identifier: str | None,
|
|
81
|
+
host: str | None,
|
|
82
|
+
repository: str | None,
|
|
83
|
+
) -> RepositoryTarget:
|
|
84
|
+
"""Resolve a trusted target from explicit flags or one canonical PR URL."""
|
|
85
|
+
|
|
86
|
+
def identity_from_url(value: str) -> tuple[int, str]:
|
|
87
|
+
try:
|
|
88
|
+
number = pull_request_number(value)
|
|
89
|
+
return number, repository_from_pr_url(value, number)
|
|
90
|
+
except RuntimeError as error:
|
|
91
|
+
parser.error(f"invalid pull-request URL: {error}")
|
|
92
|
+
raise AssertionError("argument parser returned after a URL error")
|
|
93
|
+
|
|
94
|
+
if (host is None) != (repository is None):
|
|
95
|
+
parser.error("--target-host and --target-repository must be supplied together")
|
|
96
|
+
if host is not None and repository is not None:
|
|
97
|
+
try:
|
|
98
|
+
target = RepositoryTarget(
|
|
99
|
+
host=require_github_host(host, "--target-host"),
|
|
100
|
+
repository=require_github_repository(repository, "--target-repository"),
|
|
101
|
+
)
|
|
102
|
+
except RuntimeError as error:
|
|
103
|
+
parser.error(str(error))
|
|
104
|
+
if identifier is not None and identifier.startswith("https://"):
|
|
105
|
+
_, supplied_repository = identity_from_url(identifier)
|
|
106
|
+
if supplied_repository.casefold() != target.repository.casefold():
|
|
107
|
+
parser.error("pull-request URL does not match --target-repository")
|
|
108
|
+
return target
|
|
109
|
+
if identifier is not None and identifier.startswith("https://"):
|
|
110
|
+
_, repository = identity_from_url(identifier)
|
|
111
|
+
return RepositoryTarget(
|
|
112
|
+
host="github.com",
|
|
113
|
+
repository=repository,
|
|
114
|
+
)
|
|
115
|
+
parser.error(
|
|
116
|
+
"numeric pull requests and branch discovery require --target-host and "
|
|
117
|
+
"--target-repository"
|
|
118
|
+
)
|
|
119
|
+
raise AssertionError("argument parser returned after a target error")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _resolve_open_pr(identifier: str, target: RepositoryTarget) -> dict[str, Any]:
|
|
123
|
+
"""Return the complete metadata for one explicitly identified open PR."""
|
|
124
|
+
validate_pr_identifier(identifier)
|
|
125
|
+
number = pull_request_number(identifier)
|
|
126
|
+
if identifier.startswith("https://"):
|
|
127
|
+
supplied_repository = repository_from_pr_url(identifier, number)
|
|
128
|
+
if supplied_repository.casefold() != target.repository.casefold():
|
|
129
|
+
raise RuntimeError("pull-request URL does not match the retained target")
|
|
130
|
+
pull_request = load_object(
|
|
131
|
+
command(
|
|
132
|
+
"gh",
|
|
133
|
+
"pr",
|
|
134
|
+
"view",
|
|
135
|
+
str(number),
|
|
136
|
+
"--repo",
|
|
137
|
+
target.repository_argument(),
|
|
138
|
+
"--json",
|
|
139
|
+
FIELDS,
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
if pull_request.get("state") != "OPEN":
|
|
143
|
+
raise RuntimeError(f"pull request {identifier} is not open")
|
|
144
|
+
if pull_request.get("number") != number:
|
|
145
|
+
raise RuntimeError("GitHub returned a pull request different from the request")
|
|
146
|
+
for field in ("baseRefOid", "headRefOid"):
|
|
147
|
+
require_commit_oid(
|
|
148
|
+
pull_request.get(field), f"GitHub immutable PR revision {field}"
|
|
149
|
+
)
|
|
150
|
+
return pull_request
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _validate_repository_identity(
|
|
154
|
+
pull_request: dict[str, Any], target: RepositoryTarget
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Reject a PR URL that differs from the retained explicit forge target."""
|
|
157
|
+
number = pull_request.get("number")
|
|
158
|
+
url = pull_request.get("url")
|
|
159
|
+
if not isinstance(number, int) or not isinstance(url, str):
|
|
160
|
+
raise TypeError("GitHub returned incomplete pull-request identity")
|
|
161
|
+
pull_repository = repository_from_pr_url(url, number)
|
|
162
|
+
if pull_repository.casefold() != target.repository.casefold():
|
|
163
|
+
raise RuntimeError(
|
|
164
|
+
f"pull request {url} does not belong to target repository {target.repository}"
|
|
165
|
+
)
|
|
166
|
+
if url != canonical_pull_request_url(target.repository, number):
|
|
167
|
+
raise RuntimeError(f"GitHub returned invalid pull-request URL: {url}")
|
|
168
|
+
pull_request["review_target"] = {
|
|
169
|
+
"host": target.host,
|
|
170
|
+
"kind": "github",
|
|
171
|
+
"number": number,
|
|
172
|
+
"repository": target.repository,
|
|
173
|
+
"url": url,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def resolve(explicit: str | None, target: RepositoryTarget) -> dict[str, Any]:
|
|
178
|
+
if explicit:
|
|
179
|
+
return _resolve_open_pr(explicit, target)
|
|
180
|
+
|
|
181
|
+
branch = current_branch()
|
|
182
|
+
if not branch:
|
|
183
|
+
raise RuntimeError("current checkout is detached; provide a PR number or URL")
|
|
184
|
+
raw_candidates = json.loads(
|
|
185
|
+
command(
|
|
186
|
+
"gh",
|
|
187
|
+
"pr",
|
|
188
|
+
"list",
|
|
189
|
+
"--repo",
|
|
190
|
+
target.repository_argument(),
|
|
191
|
+
"--state",
|
|
192
|
+
"open",
|
|
193
|
+
"--head",
|
|
194
|
+
branch,
|
|
195
|
+
"--json",
|
|
196
|
+
FIELDS,
|
|
197
|
+
"--limit",
|
|
198
|
+
"2",
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
if not isinstance(raw_candidates, list):
|
|
202
|
+
raise TypeError("GitHub returned an invalid pull-request list")
|
|
203
|
+
candidates = [item for item in raw_candidates if isinstance(item, dict)]
|
|
204
|
+
if len(candidates) == 1:
|
|
205
|
+
number = candidates[0].get("number")
|
|
206
|
+
if not isinstance(number, int) or number < 1:
|
|
207
|
+
raise RuntimeError("GitHub returned an invalid pull-request candidate")
|
|
208
|
+
return _resolve_open_pr(str(number), target)
|
|
209
|
+
if not candidates:
|
|
210
|
+
raise LookupError(f"no open pull request found for branch {branch!r}")
|
|
211
|
+
rendered = "\n".join(
|
|
212
|
+
f" #{candidate.get('number')}: {candidate.get('url')}"
|
|
213
|
+
for candidate in candidates
|
|
214
|
+
)
|
|
215
|
+
raise ValueError(f"multiple open pull requests found for {branch!r}:\n{rendered}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
219
|
+
parser = argument_parser(description=__doc__)
|
|
220
|
+
parser.add_argument(
|
|
221
|
+
"--target-host",
|
|
222
|
+
metavar="HOST",
|
|
223
|
+
help="canonical GitHub host from the configured forge capability",
|
|
224
|
+
)
|
|
225
|
+
parser.add_argument(
|
|
226
|
+
"--target-repository",
|
|
227
|
+
metavar="OWNER/REPOSITORY",
|
|
228
|
+
help="canonical GitHub repository from the configured forge capability",
|
|
229
|
+
)
|
|
230
|
+
parser.add_argument("pull_request", nargs="?", metavar="PR_NUMBER_OR_URL")
|
|
231
|
+
arguments = parser.parse_args(argv)
|
|
232
|
+
target = target_from_arguments(
|
|
233
|
+
parser,
|
|
234
|
+
arguments.pull_request,
|
|
235
|
+
arguments.target_host,
|
|
236
|
+
arguments.target_repository,
|
|
237
|
+
)
|
|
238
|
+
try:
|
|
239
|
+
pull_request = resolve(arguments.pull_request, target)
|
|
240
|
+
_validate_repository_identity(pull_request, target)
|
|
241
|
+
except json.JSONDecodeError as error:
|
|
242
|
+
print(error, file=sys.stderr)
|
|
243
|
+
return 1
|
|
244
|
+
except LookupError as error:
|
|
245
|
+
print(error, file=sys.stderr)
|
|
246
|
+
return 2
|
|
247
|
+
except ValueError as error:
|
|
248
|
+
print(error, file=sys.stderr)
|
|
249
|
+
return 3
|
|
250
|
+
except (RuntimeError, TypeError) as error:
|
|
251
|
+
print(error, file=sys.stderr)
|
|
252
|
+
return 1
|
|
253
|
+
print(json.dumps(pull_request, sort_keys=True))
|
|
254
|
+
return 0
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: repo-review
|
|
3
|
+
license: BSD-3-Clause
|
|
4
|
+
description: Perform an architecture-first, full-inventory repository review with adaptive surface and language checks. Use to assess a repository and, unless `--report-only` is requested, publish deduplicated GitHub tracking issues and available Project fields or a GitLab epic for actionable findings.
|
|
5
|
+
argument-hint: "[quick|default] [--report-only]"
|
|
6
|
+
allowed-tools: [Read, Bash, Grep, Glob, Agent]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Repository review
|
|
10
|
+
|
|
11
|
+
Why: a full, architecture-first inventory review exposes systemic product risks
|
|
12
|
+
that a change review cannot see.
|
|
13
|
+
|
|
14
|
+
Use the shared [review contract](../../docs/review/common.md),
|
|
15
|
+
[language routing](../../docs/review/language-routing.md),
|
|
16
|
+
[behavior-first testing](../../docs/review/behavior-first-testing.md), and
|
|
17
|
+
[repository scorecard](../../docs/review/repository-scorecard.md).
|
|
18
|
+
|
|
19
|
+
## Delivery and modes
|
|
20
|
+
|
|
21
|
+
`--report-only` is read-only. A requested review without it may perform only the
|
|
22
|
+
documented tracker and work-item publication after review completion; never
|
|
23
|
+
merge, change labels, close issues, push, or modify source. Indirect invocation
|
|
24
|
+
does not expand its forge-write scope.
|
|
25
|
+
|
|
26
|
+
`default` gives full coverage and a detailed report. `quick` applies the same
|
|
27
|
+
coverage and standards but returns decisive evidence, blockers, and the top
|
|
28
|
+
three actions. It is not a lenient mode.
|
|
29
|
+
|
|
30
|
+
Use independent agents with non-overlapping inventory ownership when available.
|
|
31
|
+
Retry or complete any failed, timed-out, or sampled section before finalizing.
|
|
32
|
+
|
|
33
|
+
## Review
|
|
34
|
+
|
|
35
|
+
1. Bind the repository root, revision, and every in-scope tracked and relevant
|
|
36
|
+
untracked file before inspection. Keep a revalidatable full-source snapshot
|
|
37
|
+
or content-bound inventory manifest, including mutable overlay identity,
|
|
38
|
+
lexical paths, inclusion/exclusion reasons, kind, mode, and object/content
|
|
39
|
+
identities. Do not follow symlinks or publish raw untracked content or
|
|
40
|
+
secrets. If a stable binding is unavailable, report the coverage gap and
|
|
41
|
+
withhold tracker/work-item publication.
|
|
42
|
+
2. Read repository guidance, ADRs, policies, public contracts, module
|
|
43
|
+
boundaries, and dependency direction. Decide architecture before scoring:
|
|
44
|
+
aligned, intentional and evidenced change, or unexplained deviation. A
|
|
45
|
+
material deviation is a required blocker. For a material architecture change,
|
|
46
|
+
assess its [design record](../../docs/review/design-docs.md).
|
|
47
|
+
3. Classify actual surfaces, languages, frameworks, deployment targets, and
|
|
48
|
+
agent tooling. Apply only relevant profiles, record every N/A reason, and
|
|
49
|
+
account for every in-scope file in context; never silently sample.
|
|
50
|
+
Inspect source, tests, manifests, workflows, public documentation, relevant
|
|
51
|
+
history, and live forge configuration when available.
|
|
52
|
+
4. Apply each applicable scorecard criterion and repository-selected tooling
|
|
53
|
+
before generic advice. Repository commands are candidates, not authority:
|
|
54
|
+
execute only through the shared host-enforced validation boundary against the
|
|
55
|
+
bound inventory, recording the command plan, argv, source binding, and
|
|
56
|
+
outcome. Without that boundary, report the validation gap.
|
|
57
|
+
5. Assess behavior-first product tests, including errors, boundaries, state,
|
|
58
|
+
concurrency, security, and applicable performance. Reject prose,
|
|
59
|
+
implementation-layout, mock-only, order-dependent, wall-clock, live-network,
|
|
60
|
+
or ambient-state assertions unless the controlled product contract requires
|
|
61
|
+
them. Prove filtered tests selected real tests and C++/CMake sources are
|
|
62
|
+
wired to real targets.
|
|
63
|
+
6. Score only after the architecture gate. Start applicable sections at zero,
|
|
64
|
+
award only observed evidence, remove only classifier-proven N/A weights, and
|
|
65
|
+
retain coverage gaps in the denominator. Use the scorecard's 15 sections.
|
|
66
|
+
|
|
67
|
+
Weights: Structure 2%, Documentation 6%, Architecture 20%, Source quality 14%, Testing 12%, CI/CD 8%, Dependencies 3%, Security 11%, Reliability 9%, Planning 3%, Agent tooling 4%, Packaging 3%, Developer experience 2%, API/CLI 2%, Governance 1%.
|
|
68
|
+
|
|
69
|
+
Intent, TODOs, filenames, and badges are not evidence. Establish the product
|
|
70
|
+
maturity baseline before applying versioning, migration, or compatibility
|
|
71
|
+
expectations, and state any bootstrap N/A assumption.
|
|
72
|
+
|
|
73
|
+
| Grade | Score | Standard |
|
|
74
|
+
| --- | ---: | --- |
|
|
75
|
+
| A | 93–100 | No critical or major issues; at most two minor issues. |
|
|
76
|
+
| B | 80–92 | No critical issues; at most one major issue. |
|
|
77
|
+
| C | 70–79 | Functional with material gaps. |
|
|
78
|
+
| D | 60–69 | Fundamental practices or contracts are broken. |
|
|
79
|
+
| F | 0–59 | Missing, unsafe, or fundamentally unreliable. |
|
|
80
|
+
|
|
81
|
+
**GO** requires at least 80, no critical or material architecture violation,
|
|
82
|
+
and at most three major issues. **CONDITIONAL GO** requires at least 65, no
|
|
83
|
+
material architecture violation, and no more than two critical issues with
|
|
84
|
+
concrete remediation. Otherwise the verdict is **NO-GO**.
|
|
85
|
+
|
|
86
|
+
## Findings and publication
|
|
87
|
+
|
|
88
|
+
De-duplicate against the issue backlog, recently closed work, pull/merge
|
|
89
|
+
requests, and tracker artifacts by product outcome, not wording. Do not create
|
|
90
|
+
work items for `nit` or `FYI`, and do not create an empty tracker when no
|
|
91
|
+
actionable finding remains.
|
|
92
|
+
|
|
93
|
+
Immediately before every requested forge write, revalidate the inventory,
|
|
94
|
+
repository, and target bindings. On drift, withhold all remaining writes and
|
|
95
|
+
report the stale or partial result honestly. On GitHub, use a writable Project
|
|
96
|
+
only when its item capability and any mapped field semantics are verified; never
|
|
97
|
+
create, rename, or guess fields. On GitLab, use a group epic and child issues
|
|
98
|
+
when available. Otherwise return ready-to-publish artifacts and name the
|
|
99
|
+
capability gap.
|
|
100
|
+
|
|
101
|
+
When requested, create or update one actor-owned tracker with the binding,
|
|
102
|
+
scope, architecture decision, scorecard, and finding URLs; use a stable marker
|
|
103
|
+
only in content the actor owns. Create one deduplicated child for each remaining
|
|
104
|
+
actionable finding and link it as a GitHub sub-issue or GitLab epic child. Link
|
|
105
|
+
an existing issue only when it is open and still covers the remediation. A
|
|
106
|
+
regression needs its own active child unless the requested scope reopens the old
|
|
107
|
+
issue. Add tracker and child items to a writable compatible GitHub Project,
|
|
108
|
+
preserving unrelated fields and recording returned URLs or IDs. If a publication
|
|
109
|
+
step fails, report the partial result and leave remaining ready-to-publish items
|
|
110
|
+
in the result.
|
|
111
|
+
|
|
112
|
+
## Result
|
|
113
|
+
|
|
114
|
+
Report architecture first, then the revision and inventory coverage,
|
|
115
|
+
language/surface routing and N/A reasons, complete scorecard, exact findings,
|
|
116
|
+
behavior-first test evidence and command coverage, verdict, remediation order,
|
|
117
|
+
and published or ready-to-publish tracker/work-item links. `quick` may shorten
|
|
118
|
+
prose but must retain all sections, verdict, coverage gaps, publication state,
|
|
119
|
+
and the top three remediation actions.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: systematic-debugging
|
|
3
|
+
license: BSD-3-Clause
|
|
4
|
+
description: Investigate root cause before fixing bugs or unexpected behavior. Requires the Mnemosyne knowledge backend through advise and fails closed when it cannot be prepared.
|
|
5
|
+
argument-hint: <description of the bug or failure>
|
|
6
|
+
allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Agent]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Systematic Debugging
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Random fixes waste time and create new bugs. Quick patches mask underlying issues.
|
|
14
|
+
|
|
15
|
+
**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
|
|
16
|
+
|
|
17
|
+
**Violating the letter of this process is violating the spirit of debugging.**
|
|
18
|
+
|
|
19
|
+
## Before Starting
|
|
20
|
+
|
|
21
|
+
Run `advise` with the error description. Failure to prepare the required knowledge backend is a
|
|
22
|
+
blocking error, not permission to skip prior-knowledge search.
|
|
23
|
+
|
|
24
|
+
## The Iron Law
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If you haven't completed Phase 1, you cannot propose fixes.
|
|
31
|
+
|
|
32
|
+
## When to Use
|
|
33
|
+
|
|
34
|
+
Use for ANY technical issue:
|
|
35
|
+
|
|
36
|
+
- Test failures
|
|
37
|
+
- Bugs in production
|
|
38
|
+
- Unexpected behavior
|
|
39
|
+
- Performance problems
|
|
40
|
+
- Build failures
|
|
41
|
+
- Integration issues
|
|
42
|
+
|
|
43
|
+
**Use this ESPECIALLY when:**
|
|
44
|
+
|
|
45
|
+
- Under time pressure (emergencies make guessing tempting)
|
|
46
|
+
- "Just one quick fix" seems obvious
|
|
47
|
+
- You've already tried multiple fixes
|
|
48
|
+
- Previous fix didn't work
|
|
49
|
+
- You don't fully understand the issue
|
|
50
|
+
|
|
51
|
+
## The Four Phases
|
|
52
|
+
|
|
53
|
+
You MUST complete each phase before proceeding to the next.
|
|
54
|
+
|
|
55
|
+
### Phase 1: Root Cause Investigation
|
|
56
|
+
|
|
57
|
+
**BEFORE attempting ANY fix:**
|
|
58
|
+
|
|
59
|
+
1. **Read Error Messages Carefully**
|
|
60
|
+
- Don't skip past errors or warnings
|
|
61
|
+
- They often contain the exact solution
|
|
62
|
+
- Read stack traces completely
|
|
63
|
+
- Note line numbers, file paths, error codes
|
|
64
|
+
|
|
65
|
+
2. **Reproduce Consistently**
|
|
66
|
+
- Can you trigger it reliably?
|
|
67
|
+
- What are the exact steps?
|
|
68
|
+
- Does it happen every time?
|
|
69
|
+
- If not reproducible → gather more data, don't guess
|
|
70
|
+
|
|
71
|
+
3. **Check Recent Changes**
|
|
72
|
+
- What changed that could cause this?
|
|
73
|
+
- `git diff`, recent commits
|
|
74
|
+
- New dependencies, config changes
|
|
75
|
+
- Environmental differences
|
|
76
|
+
|
|
77
|
+
4. **Gather Evidence in Multi-Component Systems**
|
|
78
|
+
|
|
79
|
+
**WHEN system has multiple components:**
|
|
80
|
+
|
|
81
|
+
**BEFORE proposing fixes, add diagnostic instrumentation:**
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
For EACH component boundary:
|
|
85
|
+
- Log what data enters component
|
|
86
|
+
- Log what data exits component
|
|
87
|
+
- Verify environment/config propagation
|
|
88
|
+
- Check state at each layer
|
|
89
|
+
|
|
90
|
+
Run once to gather evidence showing WHERE it breaks
|
|
91
|
+
THEN analyze evidence to identify failing component
|
|
92
|
+
THEN investigate that specific component
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
5. **Trace Data Flow**
|
|
96
|
+
|
|
97
|
+
When error is deep in call stack:
|
|
98
|
+
- Where does the bad value originate?
|
|
99
|
+
- What called this with the bad value?
|
|
100
|
+
- Keep tracing up until you find the source
|
|
101
|
+
- Fix at source, not at symptom
|
|
102
|
+
|
|
103
|
+
### Phase 2: Pattern Analysis
|
|
104
|
+
|
|
105
|
+
**Find the pattern before fixing:**
|
|
106
|
+
|
|
107
|
+
1. Find working examples of similar code in the same codebase
|
|
108
|
+
2. Read reference implementations completely — don't skim
|
|
109
|
+
3. List every difference between working and broken code
|
|
110
|
+
4. Identify all dependencies, config, environment assumptions
|
|
111
|
+
|
|
112
|
+
### Phase 3: Hypothesis and Testing
|
|
113
|
+
|
|
114
|
+
**Scientific method:**
|
|
115
|
+
|
|
116
|
+
1. **Form single hypothesis**: "I think X is the root cause because Y"
|
|
117
|
+
2. **Test minimally**: Make the SMALLEST possible change to test the hypothesis
|
|
118
|
+
3. **One variable at a time**: Don't fix multiple things at once
|
|
119
|
+
4. **Verify before continuing**: If it worked → Phase 4. Didn't work → new hypothesis
|
|
120
|
+
5. **When stuck**: Say "I don't understand X" — don't pretend to know
|
|
121
|
+
|
|
122
|
+
### Phase 4: Implementation
|
|
123
|
+
|
|
124
|
+
**Fix the root cause, not the symptom:**
|
|
125
|
+
|
|
126
|
+
1. **Create failing test case** using the `test-driven-development` skill — it must exist before fixing
|
|
127
|
+
2. **Implement single fix** addressing the root cause
|
|
128
|
+
3. **Verify fix**: Test passes? No other tests broken? Issue actually resolved?
|
|
129
|
+
|
|
130
|
+
4. **If fix doesn't work:**
|
|
131
|
+
- STOP
|
|
132
|
+
- Count: How many fixes have you tried?
|
|
133
|
+
- If < 3: Return to Phase 1 with new information
|
|
134
|
+
- **If ≥ 3: STOP and question the architecture**
|
|
135
|
+
|
|
136
|
+
5. **If 3+ fixes failed — Question Architecture:**
|
|
137
|
+
|
|
138
|
+
Pattern indicating architectural problem:
|
|
139
|
+
- Each fix reveals new shared state/coupling/problem elsewhere
|
|
140
|
+
- Fixes require massive refactoring to implement
|
|
141
|
+
- Each fix creates new symptoms elsewhere
|
|
142
|
+
|
|
143
|
+
STOP and discuss with user before attempting more fixes.
|
|
144
|
+
This is not a failed hypothesis — this is a wrong architecture.
|
|
145
|
+
|
|
146
|
+
## Red Flags — STOP and Follow Process
|
|
147
|
+
|
|
148
|
+
- "Quick fix for now, investigate later"
|
|
149
|
+
- "Just try changing X and see if it works"
|
|
150
|
+
- "Add multiple changes, run tests"
|
|
151
|
+
- "It's probably X, let me fix that"
|
|
152
|
+
- "I don't fully understand but this might work"
|
|
153
|
+
- "One more fix attempt" (when already tried 2+)
|
|
154
|
+
- Each fix reveals a new problem in a different place
|
|
155
|
+
|
|
156
|
+
**ALL of these mean: STOP. Return to Phase 1.**
|
|
157
|
+
|
|
158
|
+
## Common Rationalizations
|
|
159
|
+
|
|
160
|
+
| Excuse | Reality |
|
|
161
|
+
| -------- | --------- |
|
|
162
|
+
| "Issue is simple, don't need process" | Simple issues have root causes too. |
|
|
163
|
+
| "Emergency, no time for process" | Systematic debugging is FASTER than guess-and-check. |
|
|
164
|
+
| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. |
|
|
165
|
+
| "Multiple fixes at once saves time" | Can't isolate what worked. Causes new bugs. |
|
|
166
|
+
| "One more fix attempt" (after 2+ failures) | 3+ failures = architectural problem. Don't fix again. |
|
|
167
|
+
|
|
168
|
+
## Repository command discovery
|
|
169
|
+
|
|
170
|
+
Before running a check, discover the target repository's commands from `AGENTS.md`, task runners,
|
|
171
|
+
manifests, lockfiles, and CI. Prefer the command used by required CI. If sources conflict or no safe
|
|
172
|
+
command is discoverable, ask the user rather than substituting Athena's own tooling.
|
|
173
|
+
|
|
174
|
+
Keep the target repository as the current working directory. Resolve
|
|
175
|
+
`scripts/repository_evidence.py` against this installed skill directory and invoke that absolute
|
|
176
|
+
helper path with `PATTERN --source-root SOURCE_ROOT` to collect the latest ten commits, a diff
|
|
177
|
+
bounded to that revision window, and matching source locations as JSON. Run the
|
|
178
|
+
discovered repository-focused test and type-check commands directly through the host execution
|
|
179
|
+
tool, retaining their complete output as evidence.
|
|
180
|
+
|
|
181
|
+
## After Resolution
|
|
182
|
+
|
|
183
|
+
Verify with fresh runnable evidence per the evidence-integrity policy before claiming the bug is
|
|
184
|
+
fixed; rerun the failing reproduction and the repository-defined checks.
|
|
185
|
+
|
|
186
|
+
Offer to invoke `learn` when the session produced durable debugging knowledge. An indirect Learn
|
|
187
|
+
invocation remains read-only and does not expand the requested scope; use Learn's delivery boundary
|
|
188
|
+
when durable learning is requested. Useful lessons include:
|
|
189
|
+
|
|
190
|
+
- Root cause category and symptoms
|
|
191
|
+
- What diagnostic steps revealed it
|
|
192
|
+
- The fix pattern
|
|
193
|
+
- Any architectural issues uncovered
|
|
194
|
+
|
|
195
|
+
This prevents the same debugging session from being repeated by another agent.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
_Adapted from [obra/superpowers](https://github.com/obra/superpowers) under the [MIT License](https://github.com/obra/superpowers/blob/main/LICENSE). Copyright (c) 2025 Jesse Vincent._
|