mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-10-02 01:34:57 +08:00
code-modernization: keep one citation per rule when rendering business rules
The extractor is told to cite a single file range per rule, but on a large Perl codebase it kept returning two or three files and a sentence of explanation in the source field, which the report and the pane cannot resolve to a line. The renderer now takes the first path:line-range as the rule's citation and puts anything after it on an "Also cited" line.
This commit is contained in:
@@ -12,6 +12,7 @@ a heading or a table cell. Standard library only.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
CATEGORIES = ['Calculation', 'Validation', 'Lifecycle', 'Policy']
|
||||
@@ -24,6 +25,21 @@ def one_line(value, limit=300):
|
||||
return text if len(text) <= limit else text[: limit - 1].rstrip() + '…'
|
||||
|
||||
|
||||
CITATION = re.compile(r'^\s*`?([^\s`:,;()]+):(\d+)(?:\s*[-\u2013]\s*(\d+))?')
|
||||
|
||||
|
||||
def split_citation(value):
|
||||
"""One citation per rule: the first path:line[-line] in the text is it, and whatever follows
|
||||
(a second file, a note) comes back separately so later tools can rely on the citation alone."""
|
||||
text = ' '.join(str(value if value is not None else '').split())
|
||||
m = CITATION.match(text)
|
||||
if not m:
|
||||
return one_line(text, 200), ''
|
||||
primary = f'{m.group(1)}:{m.group(2)}' + (f'-{m.group(3)}' if m.group(3) else '')
|
||||
rest = text[m.end():].strip(' \t;,.-\u2013\u2014`')
|
||||
return one_line(primary, 200), one_line(rest, 300)
|
||||
|
||||
|
||||
def block(value):
|
||||
"""Multi-line text: keep the lines, but never let a value open a heading or a fence."""
|
||||
lines = []
|
||||
@@ -37,11 +53,14 @@ def block(value):
|
||||
|
||||
def render_rule(number, rule):
|
||||
rid = f'RULE-{number:03d}'
|
||||
source, also = split_citation(rule.get('source'))
|
||||
out = [f"### {rid}: {one_line(rule.get('name'), 120)}",
|
||||
f"**Category:** {one_line(rule.get('category'), 20)}",
|
||||
f"**Priority:** {one_line(rule.get('priority'), 4)}",
|
||||
f"**Source:** `{one_line(rule.get('source'), 200).replace(chr(39), '')}`",
|
||||
f"**Plain English:** {one_line(rule.get('plainEnglish'), 400)}",
|
||||
f"**Source:** `{source.replace(chr(39), '')}`"]
|
||||
if also:
|
||||
out.append(f'**Also cited:** {also}')
|
||||
out += [f"**Plain English:** {one_line(rule.get('plainEnglish'), 400)}",
|
||||
'**Specification:**',
|
||||
f" Given {block(rule.get('given'))}",
|
||||
f" When {block(rule.get('when'))}",
|
||||
@@ -99,7 +118,7 @@ def main(argv):
|
||||
'| ID | Name | Category | Priority | Source | Confidence |', '|---|---|---|---|---|---|']
|
||||
for n, r in numbered:
|
||||
doc.append(f"| RULE-{n:03d} | {one_line(r.get('name'), 80)} | {one_line(r.get('category'), 20)} | "
|
||||
f"{one_line(r.get('priority'), 4)} | `{one_line(r.get('source'), 80).replace(chr(39), '')}` | {one_line(r.get('confidence'), 10)} |")
|
||||
f"{one_line(r.get('priority'), 4)} | `{split_citation(r.get('source'))[0][:80].replace(chr(39), '')}` | {one_line(r.get('confidence'), 10)} |")
|
||||
doc.append('')
|
||||
for category in CATEGORIES + [None]:
|
||||
group = [n for n, r in numbered if (r.get('category') if r.get('category') in CATEGORIES else None) == category]
|
||||
|
||||
@@ -159,6 +159,21 @@ class RenderTests(unittest.TestCase):
|
||||
dto = open(os.path.join(ws, 'analysis/s/DATA_OBJECTS.md'), encoding='utf-8').read()
|
||||
self.assertIn('## Account', dto)
|
||||
|
||||
def test_a_rule_keeps_one_citation_and_the_rest_moves_to_also_cited(self):
|
||||
sources = ['wwwroot/a.pl:100-120 (aggregation); family table at lib/b.pm:31-40',
|
||||
'lib/x.pm:966,1018,1238 (order lists)', 'no citation here', 'c.cbl:7']
|
||||
result = {'confirmedRules': [
|
||||
{'name': f'R{i}', 'category': 'Policy', 'priority': 'P1', 'source': src, 'plainEnglish': 'x',
|
||||
'given': 'g', 'when': 'w', 'then': 't', 'confidence': 'High'} for i, src in enumerate(sources)]}
|
||||
with tempfile.TemporaryDirectory() as ws:
|
||||
write(ws, 'analysis/s/rules_result.json', json.dumps(result))
|
||||
self.assertEqual(run('render_rules.py', 's', '--workspace', ws).returncode, 0)
|
||||
text = open(os.path.join(ws, 'analysis/s/BUSINESS_RULES.md'), encoding='utf-8').read()
|
||||
cites = re.findall(r'^\*\*Source:\*\* `([^`]*)`', text, re.M)
|
||||
self.assertEqual(sorted(cites), sorted(['wwwroot/a.pl:100-120', 'lib/x.pm:966', 'no citation here', 'c.cbl:7']))
|
||||
self.assertIn('**Also cited:** (aggregation); family table at lib/b.pm:31-40', text)
|
||||
self.assertEqual(text.count('**Also cited:**'), 2)
|
||||
|
||||
def test_unreadable_result_is_an_error(self):
|
||||
with tempfile.TemporaryDirectory() as ws:
|
||||
self.assertEqual(run('render_rules.py', 's', '--workspace', ws).returncode, 1)
|
||||
|
||||
Reference in New Issue
Block a user