feat: implement the wiki field in the issue domain

format.md listed `wiki:` among the domain fields, between depends and
origin, and issue.py had never heard of it. The field fell into extra and
rendered with the foreign keys — sorted in after the sync fields, which
the same document forbids one line below the table. Written without
brackets it parsed as a single string, and nothing but a text editor
could set it.

Implemented rather than de-documented: page_ls.py --titles already
prints these titles, so the field was designed and only unwired.
DOMAIN_KEYS and LIST_KEYS learn it, Issue carries it, and issue_new.py
gets a repeatable --wiki flag.

Titles only, as the format says: no path, no sub_url, no lookup. The
tracker has no field for it, so it is never sent and a pull does not
bring it back — format.md now says so.

Closes #32

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
naudachu
2026-08-10 19:55:10 +05:00
parent bf0936526d
commit 9479babfe9
4 changed files with 164 additions and 7 deletions
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
`wiki:` is a domain field, and the parser now agrees with the format.
python3 -m unittest discover -s tests -v
The bug: `references/format.md` put `wiki:` in the table of domain fields,
between `depends` and `origin`, and `issue.py` had never heard of it. The
field fell into `extra` and rendered with the foreign keys — sorted in beside
`branch`, `gitea`, `synced`, `url`, i.e. AFTER the sync fields, which the same
document forbids one line further down. A list written without brackets parsed
as a single string, and nothing could set the field but a text editor.
These tests pin the resolution: implemented in the domain, rendered among the
domain fields, parsed as a list in both forms, and reachable from the command
line. The layer rule rides along — a title is a name for a document, so the
field carries titles and this layer never resolves one.
"""
import os
import subprocess
import sys
import tempfile
import unittest
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ISSUE_SCRIPTS = os.path.join(REPO, "skills", "issue", "scripts")
sys.path.insert(0, ISSUE_SCRIPTS)
import issue # noqa: E402
TITLES = ["Simple Chains/Ideas/Chain core", "Simple Chains/Ideas/Transport"]
SYNCED = """\
---
id: wire-sqlc-appclick
state: open
labels: [type/task]
assignees: []
milestone: none
depends: [migrate-schema]
wiki: [Simple Chains/Ideas/Chain core]
origin: gitea
branch: feat/wire-sqlc
gitea: claude-skills/tea#42
synced: 2026-08-09T18:40:00Z
url: https://git.noodles.cam/claude-skills/tea/issues/42
---
# Wire sqlc into the appclick repo layer
## Summary
Тело роли не играет.
"""
class TestTheFieldIsInTheDomain(unittest.TestCase):
def test_it_is_a_domain_key_and_a_list_key(self):
self.assertIn("wiki", issue.DOMAIN_KEYS)
self.assertIn("wiki", issue.LIST_KEYS)
def test_it_renders_between_depends_and_origin(self):
"""`format.md` states the order and says domain fields render first.
The old behavior put it after the sync fields."""
order = issue.DOMAIN_KEYS
self.assertEqual(order[order.index("depends") + 1], "wiki")
self.assertEqual(order[order.index("wiki") + 1], "origin")
def test_it_survives_a_round_trip_among_the_domain_fields(self):
iss = issue.Issue.from_text(SYNCED, id="wire-sqlc-appclick")
self.assertEqual(iss.wiki, ["Simple Chains/Ideas/Chain core"])
self.assertNotIn("wiki", iss.extra)
text = iss.to_text()
keys = [line.split(":", 1)[0]
for line in text.splitlines()[1:]
if line != "---" and ":" in line]
keys = keys[:keys.index("origin") + 1]
self.assertEqual(keys[-3:], ["depends", "wiki", "origin"])
self.assertLess(keys.index("wiki"), keys.index("origin"))
again = issue.Issue.from_text(text, id="wire-sqlc-appclick")
self.assertEqual(again.wiki, iss.wiki)
def test_a_bracketless_list_is_still_a_list(self):
"""Without membership in LIST_KEYS this parsed as one string —
`wiki: A, B` became the single title "A, B"."""
text = SYNCED.replace("wiki: [Simple Chains/Ideas/Chain core]",
"wiki: %s" % ", ".join(TITLES))
self.assertEqual(issue.Issue.from_text(text).wiki, TITLES)
def test_the_bracketed_form_parses_the_same_way(self):
text = SYNCED.replace("wiki: [Simple Chains/Ideas/Chain core]",
"wiki: [%s]" % ", ".join(TITLES))
self.assertEqual(issue.Issue.from_text(text).wiki, TITLES)
def test_an_absent_field_is_an_empty_list_and_renders_as_one(self):
text = "\n".join(l for l in SYNCED.splitlines()
if not l.startswith("wiki:"))
iss = issue.Issue.from_text(text)
self.assertEqual(iss.wiki, [])
self.assertIn("wiki: []", iss.to_text())
def test_the_titles_are_carried_verbatim(self):
"""A title with a slash in it is one title — the slash is hierarchy
inside the name, not a path this layer walks."""
iss = issue.Issue(id="x", title="X", wiki=TITLES)
self.assertIn("wiki: [%s]" % ", ".join(TITLES), iss.to_text())
def test_the_domain_still_knows_nothing_about_a_wiki_it_could_reach(self):
"""The layer rule: titles only. No page path, no sub_url, no HTTP."""
with open(os.path.join(ISSUE_SCRIPTS, "issue.py")) as f:
body = f.read()
for banned in ("sub_url", "content_base64", "urllib"):
self.assertNotIn(banned, body)
imports = [l for l in body.splitlines()
if l.startswith("import ") or l.startswith("from ")]
self.assertNotIn("import subprocess", imports)
class TestIssueNewCanSetIt(unittest.TestCase):
"""The script run for real, in a throwaway store — never the developer's."""
def setUp(self):
self._tmp = tempfile.TemporaryDirectory(prefix="tea-wiki-field-")
self.out = os.path.join(os.path.realpath(self._tmp.name), "issues")
self.addCleanup(self._tmp.cleanup)
def new(self, *args):
p = subprocess.run(
[sys.executable, os.path.join(ISSUE_SCRIPTS, "issue_new.py"),
"--type", "task", "--title", "Write the chain core up",
"--out", self.out] + list(args),
capture_output=True, text=True)
self.assertEqual(p.returncode, 0, p.stdout + p.stderr)
with open(os.path.join(self.out, "write-the-chain-core-up.md")) as f:
return f.read()
def test_the_flag_repeats_into_a_list(self):
text = self.new("--wiki", TITLES[0], "--wiki", TITLES[1])
self.assertIn("wiki: [%s]" % ", ".join(TITLES), text)
self.assertEqual(issue.Issue.from_text(text).wiki, TITLES)
def test_without_the_flag_the_field_is_present_and_empty(self):
self.assertIn("wiki: []", self.new())
if __name__ == "__main__":
unittest.main()