feat: add web UI prompt library from canonical workflows (#428)

Surface short copy/paste operator prompts derived from canonical workflow
files with workflow path and SHA-256 citations. Adds /prompts, /prompts/{id},
and /api/prompts with one-click copy.

Closes #428
This commit is contained in:
2026-07-07 13:32:05 -04:00
parent e62c9f07ae
commit db248af8f1
4 changed files with 127 additions and 87 deletions
+61 -52
View File
@@ -1,4 +1,5 @@
"""Tests for web UI prompt library (#428)."""
import json
import sys
import unittest
from pathlib import Path
@@ -8,73 +9,81 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from starlette.testclient import TestClient
from webui.app import create_app
from webui.prompt_library import load_prompt_library, prompt_to_dict
from webui.prompt_library import find_prompt, library_to_dict, load_prompt_library, prompt_to_dict
REQUIRED_PROMPT_SLUGS = frozenset({
"review-pr",
"work-issue",
"create-issue",
"comment-issue",
"cleanup",
"audit",
"onboarding",
})
class TestWebuiPromptLibrary(unittest.TestCase):
class TestPromptLibraryLoader(unittest.TestCase):
def test_library_loads_required_prompts(self):
entries = load_prompt_library()
slugs = {entry.slug for entry in entries}
self.assertEqual(slugs, REQUIRED_PROMPT_SLUGS)
def test_workflow_hashes_present(self):
review = find_prompt("review-pr")
self.assertIsNotNone(review)
assert review is not None
self.assertTrue(review.workflow_hash)
self.assertEqual(len(review.workflow_hash), 64)
def test_prompt_text_is_short(self):
for entry in load_prompt_library():
self.assertLess(len(entry.prompt_text), 400)
self.assertNotIn("Do not improvise around the gates", entry.prompt_text)
class TestPromptLibraryRoutes(unittest.TestCase):
def setUp(self):
self.client = TestClient(create_app())
def test_library_has_required_prompts(self):
entries = load_prompt_library()
slugs = {entry.slug for entry in entries}
self.assertEqual(
slugs,
{
"review-pr",
"work-issue",
"create-issue",
"comment-issue",
"cleanup",
"audit",
"onboarding",
},
)
def test_workflow_entries_include_hash_and_task_mode(self):
review = next(e for e in load_prompt_library() if e.slug == "review-pr")
self.assertEqual(
review.workflow_path,
"skills/llm-project-workflow/workflows/review-merge-pr.md",
)
self.assertEqual(review.task_mode, "review-merge-pr")
self.assertIsNotNone(review.workflow_hash)
self.assertGreater(len(review.workflow_hash), 12)
self.assertIn("eligible open PR", review.prompt_text)
def test_comment_issue_defers_to_workflow(self):
comment = next(e for e in load_prompt_library() if e.slug == "comment-issue")
self.assertIn("comment_issue", comment.prompt_text)
self.assertIn("create-issue", comment.workflow_path)
self.assertIn("§16", comment.source_note)
def test_prompts_page_renders_cards_and_copy_buttons(self):
def test_prompts_page_lists_all_entries(self):
response = self.client.get("/prompts")
self.assertEqual(response.status_code, 200)
text = response.text
self.assertIn("Prompt library", text)
self.assertIn("review-merge-pr.md", text)
self.assertIn("Copy prompt", text)
self.assertIn('data-copy-target="prompt-review-pr"', text)
self.assertNotIn("child issue", text.lower())
for label in (
"Review PR",
"Work issue",
"Create issue",
"Comment on issue",
"Post-merge cleanup",
"Reconciliation audit",
"Project onboarding",
):
self.assertIn(label, response.text)
self.assertIn("Copy prompt", response.text)
self.assertIn("sha256:", response.text)
self.assertNotIn("child issue", response.text.lower())
def test_prompt_detail_route(self):
response = self.client.get("/prompts/work-issue")
self.assertEqual(response.status_code, 200)
self.assertIn("work-issue.md", response.text)
self.assertIn("Copy prompt", response.text)
def test_prompt_detail_404(self):
self.assertEqual(self.client.get("/prompts/missing").status_code, 404)
def test_api_prompts_json(self):
response = self.client.get("/api/prompts")
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data["count"], 7)
self.assertEqual(len(data["prompts"]), 7)
first = data["prompts"][0]
self.assertIn("slug", first)
self.assertIn("prompt_text", first)
self.assertIn("workflow_path", first)
self.assertIn("workflow_hash", first)
review = next(item for item in data["prompts"] if item["slug"] == "review-pr")
self.assertIn("workflow_hash", review)
self.assertIn("review-merge-pr.md", review["workflow_path"])
def test_prompt_to_dict_round_trip(self):
def test_prompt_to_dict_roundtrip(self):
entry = load_prompt_library()[0]
payload = prompt_to_dict(entry)
self.assertEqual(payload["slug"], entry.slug)
self.assertEqual(payload["prompt_text"], entry.prompt_text)
encoded = json.dumps(prompt_to_dict(entry))
self.assertIn("workflow_path", encoded)
if __name__ == "__main__":