claude-dev-env 8.4.3 → 8.5.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/commands/fix-followups.md +66 -0
- package/hooks/followup_ledger.py +143 -0
- package/hooks/hooks_constants/followup_ledger_constants.py +27 -0
- package/hooks/test_followup_ledger.py +83 -0
- package/package.json +1 -1
- package/rules/flag-non-breaking-findings.md +72 -0
- package/scripts/dev_env_scripts_constants/followup_constants.py +72 -0
- package/scripts/followup_cli.py +273 -0
- package/scripts/test_followup_cli.py +156 -0
- package/scripts/test_validate_instruction_pairs.py +144 -1
- package/scripts/validate_instruction_pairs.py +277 -47
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Fix every non-breaking finding the follow-up ledger holds and open a draft PR for it
|
|
3
|
+
argument-hint: [repository root, or blank for the current repository]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Fix the non-breaking findings a local gate recorded rather than blocked on,
|
|
7
|
+
and deliver them as one draft pull request.
|
|
8
|
+
|
|
9
|
+
The repository root is `$ARGUMENTS`, or the current repository when that is
|
|
10
|
+
blank.
|
|
11
|
+
|
|
12
|
+
Use `rules/asd-ste100-language.md` for user-facing wording.
|
|
13
|
+
|
|
14
|
+
## 1. Read the ledger
|
|
15
|
+
|
|
16
|
+
Run `cde followup brief --repository-root <root>`.
|
|
17
|
+
|
|
18
|
+
When it prints `no follow-ups recorded`, report that and stop. There is no
|
|
19
|
+
work.
|
|
20
|
+
|
|
21
|
+
Otherwise the brief lists one line per finding, each carrying the rule
|
|
22
|
+
identifier, the file, and the message.
|
|
23
|
+
|
|
24
|
+
## 2. Group the findings
|
|
25
|
+
|
|
26
|
+
Group the lines by rule identifier. A rule with several findings takes one
|
|
27
|
+
pass over every file it names, so the same fix shape lands once.
|
|
28
|
+
|
|
29
|
+
Read each named file before editing it. A recorded finding names the state of
|
|
30
|
+
the file at the moment the gate ran, so confirm the finding still holds.
|
|
31
|
+
Keep a finding the current file still carries. Drop the rest.
|
|
32
|
+
|
|
33
|
+
## 3. Fix each group
|
|
34
|
+
|
|
35
|
+
Each finding is non-breaking, so the change that raised it already shipped.
|
|
36
|
+
Keep every fix mechanical. Touch no behavior, change no signature, and add no
|
|
37
|
+
abstraction.
|
|
38
|
+
|
|
39
|
+
Run the repository's own fast checks over the files you changed. Where a
|
|
40
|
+
finding names a generated file, regenerate it with the repository's tooling.
|
|
41
|
+
|
|
42
|
+
## 4. Open the pull request
|
|
43
|
+
|
|
44
|
+
Branch from the repository's default branch. Commit each rule group on its
|
|
45
|
+
own, with a message naming the rule identifier and the files.
|
|
46
|
+
|
|
47
|
+
Open the pull request as a draft. Write the body as:
|
|
48
|
+
|
|
49
|
+
- One `Before:` paragraph saying which findings stood open.
|
|
50
|
+
- One `After:` paragraph saying what the tree now holds.
|
|
51
|
+
- A short `How` paragraph naming the rule groups and the files each touched.
|
|
52
|
+
|
|
53
|
+
Push, then confirm the required checks report on the branch head.
|
|
54
|
+
|
|
55
|
+
## 5. Empty the ledger
|
|
56
|
+
|
|
57
|
+
Once the pull request is open, run
|
|
58
|
+
`cde followup clear --repository-root <root>`.
|
|
59
|
+
|
|
60
|
+
A finding the pull request left alone goes back in the ledger with
|
|
61
|
+
`cde followup ingest`, so the next pass picks it up.
|
|
62
|
+
|
|
63
|
+
## Report
|
|
64
|
+
|
|
65
|
+
Name the pull request link, the rule groups fixed, the findings dropped as no
|
|
66
|
+
longer present, and anything left in the ledger.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Append-only ledger of non-breaking findings awaiting a follow-up fix.
|
|
2
|
+
|
|
3
|
+
A gate that finds a breaking defect stops the commit, the push, or the tool
|
|
4
|
+
call. A gate that finds a non-breaking smell records the finding here and lets
|
|
5
|
+
the work proceed. ``cde followup`` reads the ledger and briefs the agent that
|
|
6
|
+
fixes the recorded smells in their own pull request.
|
|
7
|
+
|
|
8
|
+
Every write is fail-safe. A ledger that cannot be created or appended to leaves
|
|
9
|
+
the calling gate's decision unchanged, so recording a finding never becomes a
|
|
10
|
+
new way for a gate to fail.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import NamedTuple
|
|
19
|
+
|
|
20
|
+
_hooks_directory = str(Path(__file__).resolve().parent)
|
|
21
|
+
if _hooks_directory not in sys.path:
|
|
22
|
+
sys.path.insert(0, _hooks_directory)
|
|
23
|
+
|
|
24
|
+
from hooks_constants.followup_ledger_constants import (
|
|
25
|
+
FILE_PATH_KEY,
|
|
26
|
+
ALL_FOLLOWUP_LEDGER_PATH_SEGMENTS,
|
|
27
|
+
LEDGER_APPEND_MODE,
|
|
28
|
+
LEDGER_ENCODING,
|
|
29
|
+
MESSAGE_KEY,
|
|
30
|
+
RULE_ID_KEY,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FollowupFinding(NamedTuple):
|
|
35
|
+
"""One non-breaking finding a later pull request resolves.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
rule_id: Stable identifier of the check that raised the finding.
|
|
39
|
+
file_path: Repository-relative path the finding names.
|
|
40
|
+
message: The text a reader acts on.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
rule_id: str
|
|
44
|
+
file_path: str
|
|
45
|
+
message: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def followup_ledger_path(repository_root: Path) -> Path:
|
|
49
|
+
"""Return the ledger path for one repository.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
repository_root: The repository whose ledger to address.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The absolute path of that repository's follow-up ledger.
|
|
56
|
+
"""
|
|
57
|
+
return repository_root.joinpath(*ALL_FOLLOWUP_LEDGER_PATH_SEGMENTS)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def record_followup_finding(repository_root: Path, finding: FollowupFinding) -> None:
|
|
61
|
+
"""Append one finding to the repository's ledger, once.
|
|
62
|
+
|
|
63
|
+
A finding already present in the ledger is left alone, so a gate that runs
|
|
64
|
+
on every commit records a standing smell a single time. Every filesystem
|
|
65
|
+
error is swallowed, so a ledger failure leaves the caller's gate decision
|
|
66
|
+
unchanged.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
repository_root: The repository whose ledger receives the finding.
|
|
70
|
+
finding: The non-breaking finding to record.
|
|
71
|
+
"""
|
|
72
|
+
if finding in all_recorded_findings(repository_root):
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
ledger_path = followup_ledger_path(repository_root)
|
|
76
|
+
record_text = json.dumps(
|
|
77
|
+
{
|
|
78
|
+
RULE_ID_KEY: finding.rule_id,
|
|
79
|
+
FILE_PATH_KEY: finding.file_path,
|
|
80
|
+
MESSAGE_KEY: finding.message,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
try:
|
|
84
|
+
ledger_path.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
with ledger_path.open(LEDGER_APPEND_MODE, encoding=LEDGER_ENCODING) as ledger_file:
|
|
86
|
+
ledger_file.write(record_text + "\n")
|
|
87
|
+
except OSError:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def all_recorded_findings(repository_root: Path) -> tuple[FollowupFinding, ...]:
|
|
92
|
+
"""Return every finding the repository's ledger holds, in record order.
|
|
93
|
+
|
|
94
|
+
An absent or unreadable ledger reads as empty, and a line that does not
|
|
95
|
+
parse into a complete record is skipped, so one damaged line never hides
|
|
96
|
+
the findings around it.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
repository_root: The repository whose ledger to read.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The recorded findings, in the order they were appended.
|
|
103
|
+
"""
|
|
104
|
+
ledger_path = followup_ledger_path(repository_root)
|
|
105
|
+
try:
|
|
106
|
+
ledger_text = ledger_path.read_text(encoding=LEDGER_ENCODING)
|
|
107
|
+
except (OSError, UnicodeError):
|
|
108
|
+
return ()
|
|
109
|
+
|
|
110
|
+
all_findings: list[FollowupFinding] = []
|
|
111
|
+
for each_line in ledger_text.splitlines():
|
|
112
|
+
each_finding = _finding_from_line(each_line)
|
|
113
|
+
if each_finding is not None:
|
|
114
|
+
all_findings.append(each_finding)
|
|
115
|
+
return tuple(all_findings)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _finding_from_line(ledger_line: str) -> FollowupFinding | None:
|
|
119
|
+
"""Parse one ledger line into a finding.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
ledger_line: One line of the ledger file.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The parsed finding, or None when the line carries no complete record.
|
|
126
|
+
"""
|
|
127
|
+
stripped_line = ledger_line.strip()
|
|
128
|
+
if not stripped_line:
|
|
129
|
+
return None
|
|
130
|
+
try:
|
|
131
|
+
parsed_record = json.loads(stripped_line)
|
|
132
|
+
except json.JSONDecodeError:
|
|
133
|
+
return None
|
|
134
|
+
if not isinstance(parsed_record, dict):
|
|
135
|
+
return None
|
|
136
|
+
rule_id = parsed_record.get(RULE_ID_KEY)
|
|
137
|
+
file_path = parsed_record.get(FILE_PATH_KEY)
|
|
138
|
+
message = parsed_record.get(MESSAGE_KEY)
|
|
139
|
+
if not isinstance(rule_id, str) or not isinstance(file_path, str):
|
|
140
|
+
return None
|
|
141
|
+
if not isinstance(message, str):
|
|
142
|
+
return None
|
|
143
|
+
return FollowupFinding(rule_id, file_path, message)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Constants for the non-breaking-finding follow-up ledger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ALL_FOLLOWUP_LEDGER_PATH_SEGMENTS",
|
|
7
|
+
"LEDGER_ENCODING",
|
|
8
|
+
"LEDGER_APPEND_MODE",
|
|
9
|
+
"RULE_ID_KEY",
|
|
10
|
+
"FILE_PATH_KEY",
|
|
11
|
+
"MESSAGE_KEY",
|
|
12
|
+
"SEVERITY_BREAKING",
|
|
13
|
+
"SEVERITY_SMELL",
|
|
14
|
+
"ALL_SEVERITY_NAMES",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
ALL_FOLLOWUP_LEDGER_PATH_SEGMENTS: tuple[str, ...] = (".claude", "followups", "smells.jsonl")
|
|
18
|
+
LEDGER_ENCODING: str = "utf-8"
|
|
19
|
+
LEDGER_APPEND_MODE: str = "a"
|
|
20
|
+
|
|
21
|
+
RULE_ID_KEY: str = "rule_id"
|
|
22
|
+
FILE_PATH_KEY: str = "file_path"
|
|
23
|
+
MESSAGE_KEY: str = "message"
|
|
24
|
+
|
|
25
|
+
SEVERITY_BREAKING: str = "breaking"
|
|
26
|
+
SEVERITY_SMELL: str = "smell"
|
|
27
|
+
ALL_SEVERITY_NAMES: frozenset[str] = frozenset({SEVERITY_BREAKING, SEVERITY_SMELL})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Behavior tests for the non-breaking-finding follow-up ledger."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
_hooks_directory = str(Path(__file__).resolve().parent)
|
|
8
|
+
if _hooks_directory not in sys.path:
|
|
9
|
+
sys.path.insert(0, _hooks_directory)
|
|
10
|
+
|
|
11
|
+
from followup_ledger import (
|
|
12
|
+
FollowupFinding,
|
|
13
|
+
all_recorded_findings,
|
|
14
|
+
followup_ledger_path,
|
|
15
|
+
record_followup_finding,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_record_followup_finding_appends_one_readable_record(tmp_path: Path) -> None:
|
|
20
|
+
finding = FollowupFinding(
|
|
21
|
+
rule_id="instruction-filename",
|
|
22
|
+
file_path="docs/Claude.md",
|
|
23
|
+
message="Use the canonical filename: docs/Claude.md",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
record_followup_finding(tmp_path, finding)
|
|
27
|
+
|
|
28
|
+
assert all_recorded_findings(tmp_path) == (finding,)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_record_followup_finding_keeps_distinct_findings_apart(tmp_path: Path) -> None:
|
|
32
|
+
first = FollowupFinding("instruction-filename", "docs/Claude.md", "wrong name")
|
|
33
|
+
second = FollowupFinding("instruction-mode", "docs/AGENTS.md", "wrong mode")
|
|
34
|
+
|
|
35
|
+
record_followup_finding(tmp_path, first)
|
|
36
|
+
record_followup_finding(tmp_path, second)
|
|
37
|
+
|
|
38
|
+
assert all_recorded_findings(tmp_path) == (first, second)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_record_followup_finding_records_a_repeat_once(tmp_path: Path) -> None:
|
|
42
|
+
finding = FollowupFinding("instruction-mode", "docs/AGENTS.md", "wrong mode")
|
|
43
|
+
|
|
44
|
+
record_followup_finding(tmp_path, finding)
|
|
45
|
+
record_followup_finding(tmp_path, finding)
|
|
46
|
+
|
|
47
|
+
assert all_recorded_findings(tmp_path) == (finding,)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_all_recorded_findings_is_empty_without_a_ledger(tmp_path: Path) -> None:
|
|
51
|
+
assert all_recorded_findings(tmp_path) == ()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_all_recorded_findings_skips_an_unreadable_line(tmp_path: Path) -> None:
|
|
55
|
+
finding = FollowupFinding("instruction-mode", "docs/AGENTS.md", "wrong mode")
|
|
56
|
+
record_followup_finding(tmp_path, finding)
|
|
57
|
+
ledger_path = followup_ledger_path(tmp_path)
|
|
58
|
+
with ledger_path.open("a", encoding="utf-8") as ledger_file:
|
|
59
|
+
ledger_file.write("{ not json\n")
|
|
60
|
+
|
|
61
|
+
assert all_recorded_findings(tmp_path) == (finding,)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_record_followup_finding_writes_one_json_object_per_line(tmp_path: Path) -> None:
|
|
65
|
+
record_followup_finding(
|
|
66
|
+
tmp_path, FollowupFinding("instruction-mode", "docs/AGENTS.md", "wrong mode")
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
all_lines = followup_ledger_path(tmp_path).read_text(encoding="utf-8").splitlines()
|
|
70
|
+
|
|
71
|
+
assert len(all_lines) == 1
|
|
72
|
+
assert json.loads(all_lines[0])["rule_id"] == "instruction-mode"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_record_followup_finding_swallows_a_write_failure(tmp_path: Path) -> None:
|
|
76
|
+
blocking_file = tmp_path / ".claude"
|
|
77
|
+
blocking_file.write_text("not a directory", encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
record_followup_finding(
|
|
80
|
+
tmp_path, FollowupFinding("instruction-mode", "docs/AGENTS.md", "wrong mode")
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
assert all_recorded_findings(tmp_path) == ()
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Flag Non-Breaking Findings
|
|
2
|
+
|
|
3
|
+
**When this applies:** Writing or changing a local gate — a git hook, a
|
|
4
|
+
pre-commit entry, a Claude tool-use hook — and deciding what one of its checks
|
|
5
|
+
does when it finds something.
|
|
6
|
+
|
|
7
|
+
## Rule
|
|
8
|
+
|
|
9
|
+
A gate blocks on a breaking finding and records a non-breaking one.
|
|
10
|
+
|
|
11
|
+
A **breaking** finding means the change is wrong: a bug, a secret in the
|
|
12
|
+
tree, a broken test, a syntax error, an instruction file that fails to
|
|
13
|
+
load. The gate fails and the work stops there.
|
|
14
|
+
|
|
15
|
+
A **smell** finding means the change reads poorly: a length limit, a naming
|
|
16
|
+
convention, a prose term, a comment rule, a structural preference. The gate
|
|
17
|
+
records it and lets the commit, the push, or the tool call proceed. A later
|
|
18
|
+
pull request carries the fix.
|
|
19
|
+
|
|
20
|
+
Every check a gate runs declares which of the two it is. A check that cannot
|
|
21
|
+
say which one it raises is a breaking check until someone decides otherwise.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
A smell that blocks a commit stops delivery for something the reader would
|
|
26
|
+
have fixed in the next pass anyway. The writer then reaches for the bypass
|
|
27
|
+
flag, and the bypass turns off the breaking checks beside it. The gate that
|
|
28
|
+
blocks on everything ends up enforcing nothing.
|
|
29
|
+
|
|
30
|
+
Recording the smell keeps both halves working. The breaking checks stay
|
|
31
|
+
sharp because nobody routes around them, and the smells still get fixed
|
|
32
|
+
because the ledger holds them until a pull request does.
|
|
33
|
+
|
|
34
|
+
## The ledger
|
|
35
|
+
|
|
36
|
+
A recorded finding lands in `.claude/followups/smells.jsonl` at the
|
|
37
|
+
repository root, one JSON object per line carrying the rule identifier, the
|
|
38
|
+
repository-relative path, and the message. `hooks/followup_ledger.py` writes
|
|
39
|
+
and reads it. Every write there is fail-safe, so a ledger failure leaves the
|
|
40
|
+
gate's decision unchanged.
|
|
41
|
+
|
|
42
|
+
The ledger is per-checkout state and stays out of the repository.
|
|
43
|
+
|
|
44
|
+
## Reading and clearing it
|
|
45
|
+
|
|
46
|
+
| Command | What it does |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `cde followup list` | Names every recorded follow-up |
|
|
49
|
+
| `cde followup ingest REPORT` | Records the diagnostics a policy-lint JSON report carries |
|
|
50
|
+
| `cde followup brief` | Writes the task an agent fixes them from |
|
|
51
|
+
| `cde followup clear` | Empties the ledger |
|
|
52
|
+
|
|
53
|
+
The `/fix-followups` command drives the whole pass: it reads the brief, fixes
|
|
54
|
+
each rule group, opens a draft pull request, and clears the ledger.
|
|
55
|
+
|
|
56
|
+
## Worked example
|
|
57
|
+
|
|
58
|
+
`scripts/validate_instruction_pairs.py` raises five findings and splits them
|
|
59
|
+
on this line. A missing governing `AGENTS.md`, an import text that differs,
|
|
60
|
+
and an instruction path that is not a regular file each stop the instructions
|
|
61
|
+
loading, so the gate fails. A non-canonical filename and a Git mode other than
|
|
62
|
+
100644 leave the instructions loading, so the gate records them and passes.
|
|
63
|
+
|
|
64
|
+
`SEVERITY_BY_RULE_ID` in that module is the whole declaration. Add a check,
|
|
65
|
+
add its row.
|
|
66
|
+
|
|
67
|
+
## Sibling rules
|
|
68
|
+
|
|
69
|
+
| Rule | Role |
|
|
70
|
+
|---|---|
|
|
71
|
+
| [`ci-owns-the-gate.md`](ci-owns-the-gate.md) | The full check suite runs once, on CI |
|
|
72
|
+
| [`git-workflow.md`](git-workflow.md) | A red required check blocks the branch |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Constants for the follow-up ledger command and the instruction-pair gate.
|
|
2
|
+
|
|
3
|
+
Script-level scalar constants live in dev_env_scripts_constants alongside
|
|
4
|
+
timing.py and the other per-script constant modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
FILENAME_RULE_ID: str = "instruction-filename"
|
|
10
|
+
REGULAR_FILE_RULE_ID: str = "instruction-regular-file"
|
|
11
|
+
GIT_MODE_RULE_ID: str = "instruction-git-mode"
|
|
12
|
+
MISSING_AGENTS_RULE_ID: str = "instruction-missing-agents"
|
|
13
|
+
IMPORT_TEXT_RULE_ID: str = "instruction-import-text"
|
|
14
|
+
|
|
15
|
+
SEVERITY_BREAKING: str = "breaking"
|
|
16
|
+
SEVERITY_SMELL: str = "smell"
|
|
17
|
+
SEVERITY_BY_RULE_ID: dict[str, str] = {
|
|
18
|
+
FILENAME_RULE_ID: SEVERITY_SMELL,
|
|
19
|
+
REGULAR_FILE_RULE_ID: SEVERITY_BREAKING,
|
|
20
|
+
GIT_MODE_RULE_ID: SEVERITY_SMELL,
|
|
21
|
+
MISSING_AGENTS_RULE_ID: SEVERITY_BREAKING,
|
|
22
|
+
IMPORT_TEXT_RULE_ID: SEVERITY_BREAKING,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
GATE_PASSED_EXIT_CODE: int = 0
|
|
26
|
+
GATE_FAILED_EXIT_CODE: int = 1
|
|
27
|
+
RECORDED_SMELL_TEMPLATE: str = "recorded for follow-up: %s"
|
|
28
|
+
|
|
29
|
+
LIST_COMMAND_NAME: str = "list"
|
|
30
|
+
INGEST_COMMAND_NAME: str = "ingest"
|
|
31
|
+
BRIEF_COMMAND_NAME: str = "brief"
|
|
32
|
+
CLEAR_COMMAND_NAME: str = "clear"
|
|
33
|
+
ALL_COMMAND_NAMES: tuple[str, ...] = (
|
|
34
|
+
LIST_COMMAND_NAME,
|
|
35
|
+
INGEST_COMMAND_NAME,
|
|
36
|
+
BRIEF_COMMAND_NAME,
|
|
37
|
+
CLEAR_COMMAND_NAME,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
SUCCESS_EXIT_CODE: int = 0
|
|
41
|
+
INVALID_INPUT_EXIT_CODE: int = 2
|
|
42
|
+
|
|
43
|
+
DIAGNOSTICS_KEY: str = "diagnostics"
|
|
44
|
+
DIAGNOSTIC_RULE_ID_KEY: str = "rule_id"
|
|
45
|
+
DIAGNOSTIC_MESSAGE_KEY: str = "message"
|
|
46
|
+
DIAGNOSTIC_LOCATION_KEY: str = "location"
|
|
47
|
+
LOCATION_PATH_KEY: str = "path"
|
|
48
|
+
ABSENT_LOCATION_PATH: str = ""
|
|
49
|
+
|
|
50
|
+
LINE_SEPARATOR: str = "\n"
|
|
51
|
+
EMPTY_LEDGER_MESSAGE: str = "no follow-ups recorded"
|
|
52
|
+
UNREADABLE_REPORT_TEMPLATE: str = "cannot read the lint report: {report_path}"
|
|
53
|
+
USAGE_TEXT: str = (
|
|
54
|
+
"Usage: followup_cli.py <list|ingest|brief|clear> [--repository-root PATH]\n"
|
|
55
|
+
" list Name every recorded follow-up\n"
|
|
56
|
+
" ingest REPORT Record every diagnostic in a policy-lint JSON report\n"
|
|
57
|
+
" brief Write the task an agent works the follow-ups from\n"
|
|
58
|
+
" clear Empty the ledger"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
BRIEF_HEADER: str = (
|
|
62
|
+
"Fix every finding below in one change, then open a draft pull request "
|
|
63
|
+
"for it. Each finding is non-breaking, so the change that raised it "
|
|
64
|
+
"already shipped. Keep the fixes mechanical, touch no behavior, and run "
|
|
65
|
+
"the repository's own checks before pushing."
|
|
66
|
+
)
|
|
67
|
+
BRIEF_FINDING_TEMPLATE: str = "- [{rule_id}] {file_path}: {message}"
|
|
68
|
+
BRIEF_FOOTER_TEMPLATE: str = (
|
|
69
|
+
"Once the pull request is open, empty the ledger with "
|
|
70
|
+
"`python {command_path} clear`."
|
|
71
|
+
)
|
|
72
|
+
LIST_FINDING_TEMPLATE: str = "{rule_id}\t{file_path}\t{message}"
|