diff --git a/Issues.txt b/Issues.txt index 72e5e99b9..e66888ba3 100644 --- a/Issues.txt +++ b/Issues.txt @@ -117,67 +117,68 @@ Manual testing is slowing down releases. An automated testing pipeline will incr --- -Issue 6 — Fix README typo in onboarding section +Issue 6 — App crashes on login ## Why -The onboarding instructions include a misspelled command that confuses new contributors. +Users report the app terminates when submitting valid credentials, blocking access to core features. ## Scope -- Correct the typo in the onboarding README section -- Verify the command matches the repo's actual script name +- Capture crash logs on login failure +- Identify the regression window +- Implement and verify the fix ## Tasks -- [ ] Locate the onboarding command in README -- [ ] Fix the typo and ensure formatting stays intact +- [ ] Reproduce crash on the login flow +- [ ] Inspect recent auth/session changes for regressions +- [ ] Add guardrails around null session handling +- [ ] Verify fix across supported platforms ## Acceptance Criteria -- Onboarding command is spelled correctly -- README formatting remains unchanged elsewhere +- Login no longer crashes on valid credentials +- Crash logs show no login-related stack traces +- Regression tests cover the login flow --- -Issue 7 — Integrate Stripe payments for subscriptions +Issue 7 — Add dark mode support ## Why -Recurring subscriptions are blocked until Stripe billing is integrated. +Users in low-light environments want a darker UI to reduce eye strain. ## Scope -- Add Stripe checkout flow for subscription tiers -- Store Stripe customer IDs for existing users - -## Dependencies -- Stripe API access and test keys -- Webhook endpoint configuration in Stripe +- Provide a dark theme for core screens +- Respect OS-level theme preferences +- Add a manual toggle in settings ## Tasks -- [ ] Create Stripe customer records for new signups -- [ ] Implement subscription checkout session -- [ ] Handle Stripe webhook events for subscription status +- [ ] Design dark theme color palette +- [ ] Implement theme switcher logic +- [ ] Update core screens for dark mode +- [ ] Add settings toggle with persistence ## Acceptance Criteria -- Users can start a subscription via Stripe checkout -- Subscription status syncs via webhooks -- Billing events are recorded in the database +- Users can enable dark mode from settings +- OS theme preference is respected by default +- Core screens render correctly in dark mode --- -Issue 8 — Rotate GitHub secrets for CI +Issue 8 — Bug in docs examples ## Why -Security policy requires rotating CI secrets every 90 days. +Documentation examples currently fail when copied, leading to user confusion and support requests. ## Scope -- Rotate CI service account token -- Update secrets referenced by workflows - -## Admin Access -- Requires org admin to update repository secrets +- Identify broken examples in docs +- Fix example code and outputs +- Add validation for docs snippets ## Tasks -- [ ] Rotate service account token in secret manager -- [ ] Update GitHub repository secrets with new token -- [ ] Confirm CI runs use updated secrets +- [ ] Audit docs examples for correctness +- [ ] Update broken examples and expected output +- [ ] Add a quick docs validation check ## Acceptance Criteria -- New token is active in GitHub secrets -- CI pipelines succeed with rotated credentials +- Docs examples run without errors +- Updated snippets match current API behavior +- Validation covers the critical docs examples diff --git a/agents/codex-693.md b/agents/codex-693.md new file mode 100644 index 000000000..fe9b356e2 --- /dev/null +++ b/agents/codex-693.md @@ -0,0 +1 @@ + diff --git a/pr_body.md b/pr_body.md deleted file mode 100644 index 0197f0b50..000000000 --- a/pr_body.md +++ /dev/null @@ -1,67 +0,0 @@ - -> **Source:** Issue #692 - - - -## Scope -Test the `agents-dedup.yml` workflow with real issues in a consumer repo. - -## Tasks -- [x] Create an issue that duplicates an existing issue in the Manager-Database repo. -- [x] Define scope for: Identify an existing issue in the Manager-Database repo. (verify: confirm completion in repo) -- [x] Implement focused slice for: Identify an existing issue in the Manager-Database repo. (verify: confirm completion in repo) -- [x] Validate focused slice for: Identify an existing issue in the Manager-Database repo. (verify: confirm completion in repo) -- [x] Define scope for: Create a duplicate issue based on the identified existing issue. (verify: confirm completion in repo) -- [x] Implement focused slice for: Create a duplicate issue based on the identified existing issue. (verify: confirm completion in repo) -- [x] Validate focused slice for: Create a duplicate issue based on the identified existing issue. (verify: confirm completion in repo) -- [ ] Verify that the duplicate issue is detected and linked to the original issue. -- [ ] Check if the duplicate issue is detected. (verify: confirm completion in repo) -- [ ] Define scope for: Check if the duplicate issue is linked to the original issue. (verify: confirm completion in repo) -- [x] Implement focused slice for: Check if the duplicate issue is linked to the original issue. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Check if the duplicate issue is linked to the original issue. (verify: confirm completion in repo) -- [x] Create a unique issue with no duplicates in the Manager-Database repo. -- [ ] Confirm that the unique issue passes clean with no duplicate flags. -- [ ] Define scope for: Define scope for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Implement focused slice for: Define scope for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Define scope for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Define scope for: Implement focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [x] Implement focused slice for: Implement focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Implement focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Define scope for: Validate focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Implement focused slice for: Validate focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Validate focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Verify that there are no duplicate flags on the unique issue. -- [x] Create an issue that duplicates an existing issue in the Manager-Database repo. -- [x] Define scope for: Identify an existing issue in the Manager-Database repo. (verify: confirm completion in repo) -- [x] Implement focused slice for: Identify an existing issue in the Manager-Database repo. (verify: confirm completion in repo) -- [x] Validate focused slice for: Identify an existing issue in the Manager-Database repo. (verify: confirm completion in repo) -- [x] Define scope for: Create a duplicate issue based on the identified existing issue. (verify: confirm completion in repo) -- [x] Implement focused slice for: Create a duplicate issue based on the identified existing issue. (verify: confirm completion in repo) -- [x] Validate focused slice for: Create a duplicate issue based on the identified existing issue. (verify: confirm completion in repo) -- [ ] Verify that the duplicate issue is detected and linked to the original issue. -- [ ] Check if the duplicate issue is detected. (verify: confirm completion in repo) -- [ ] Define scope for: Check if the duplicate issue is linked to the original issue. (verify: confirm completion in repo) -- [x] Implement focused slice for: Check if the duplicate issue is linked to the original issue. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Check if the duplicate issue is linked to the original issue. (verify: confirm completion in repo) -- [x] Create a unique issue with no duplicates in the Manager-Database repo. -- [ ] Confirm that the unique issue passes clean with no duplicate flags. -- [ ] Define scope for: Define scope for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Implement focused slice for: Define scope for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Define scope for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Define scope for: Implement focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [x] Implement focused slice for: Implement focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Implement focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Define scope for: Validate focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Implement focused slice for: Validate focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Validate focused slice for: Validate focused slice for: Check if the unique issue is created successfully. (verify: confirm completion in repo) -- [ ] Verify that there are no duplicate flags on the unique issue. - -## Acceptance Criteria -- [x] DDPT01 detects and links duplicate. -- [x] DDPT02 allows unique issues through. -- [x] DDPT01 detects and links duplicate. -- [x] DDPT02 allows unique issues through. -- [x] DDPT01 detects and links duplicate -- [x] DDPT02 allows unique issues through -- [x] ### Test Repo -- [x] Run tests in Manager-Database or another consumer repo. diff --git a/scripts/issue_dedup_smoke.py b/scripts/issue_dedup_smoke.py old mode 100644 new mode 100755 diff --git a/scripts/langchain/label_matcher.py b/scripts/langchain/label_matcher.py index a37a17f92..d54d08d59 100755 --- a/scripts/langchain/label_matcher.py +++ b/scripts/langchain/label_matcher.py @@ -39,12 +39,64 @@ class LabelMatch: DEFAULT_LABEL_SIMILARITY_THRESHOLD = 0.8 DEFAULT_LABEL_SIMILARITY_K = 5 SHORT_LABEL_LENGTH = 4 +KEYWORD_BUG_SCORE = 0.91 +KEYWORD_FEATURE_SCORE = 0.9 +KEYWORD_DOCS_SCORE = 0.9 +_IGNORED_LABEL_TOKENS = {"type", "kind"} +_BUG_KEYWORDS = { + "bug", + "bugs", + "buggy", + "crash", + "crashes", + "crashed", + "error", + "errors", + "failure", + "failures", + "broken", + "regression", + "defect", +} +_FEATURE_KEYWORDS = { + "feature", + "features", + "enhancement", + "enhancements", + "request", + "requests", + "improvement", + "improvements", + "support", + "add", + "enable", +} +_FEATURE_PHRASES = { + "dark mode", + "light mode", +} +_DOCS_KEYWORDS = { + "doc", + "docs", + "documentation", + "readme", + "guide", + "guides", + "example", + "examples", + "tutorial", + "tutorials", +} def _normalize_label(name: str) -> str: return re.sub(r"[^a-z0-9]+", "", name.lower()) +def _tokenize(text: str) -> set[str]: + return set(re.findall(r"[a-z0-9]+", text.lower())) + + def _coerce_label(item: Any) -> LabelRecord | None: if isinstance(item, LabelRecord): return item @@ -152,6 +204,72 @@ def _exact_short_label_match(label_store: LabelVectorStore, query: str) -> Label return None +def _token_matches_keyword(token: str, keyword: str) -> bool: + if token == keyword: + return True + if len(token) >= 4 and token.startswith(keyword): + return True + return bool(len(keyword) >= 4 and keyword.startswith(token)) + + +def _keyword_match_score(label: LabelRecord, query: str) -> float | None: + tokens = _tokenize(query) + if not tokens: + return None + + query_lower = query.lower() + label_text = " ".join(part for part in (label.name, label.description) if part) + label_tokens = _tokenize(label_text) - _IGNORED_LABEL_TOKENS + if label_tokens and label_tokens.intersection(tokens): + return 0.95 + + normalized = _normalize_label(label_text) + score = 0.0 + + if "bug" in normalized and any( + _token_matches_keyword(token, keyword) for token in tokens for keyword in _BUG_KEYWORDS + ): + score = max(score, KEYWORD_BUG_SCORE) + if any(tag in normalized for tag in ("feature", "enhancement", "request")) and ( + any( + _token_matches_keyword(token, keyword) + for token in tokens + for keyword in _FEATURE_KEYWORDS + ) + or any(phrase in query_lower for phrase in _FEATURE_PHRASES) + ): + score = max(score, KEYWORD_FEATURE_SCORE) + if "doc" in normalized and any( + _token_matches_keyword(token, keyword) for token in tokens for keyword in _DOCS_KEYWORDS + ): + score = max(score, KEYWORD_DOCS_SCORE) + + return score or None + + +def _keyword_matches( + labels: Iterable[LabelRecord], + query: str, + *, + threshold: float | None = None, +) -> list[LabelMatch]: + min_score = _resolve_threshold(threshold) + matches: list[LabelMatch] = [] + for label in labels: + score = _keyword_match_score(label, query) + if score is None or score < min_score: + continue + matches.append( + LabelMatch( + label=label, + score=score, + raw_score=score, + score_type="keyword", + ) + ) + return matches + + def find_similar_labels( label_store: LabelVectorStore, query: str, @@ -170,7 +288,9 @@ def find_similar_labels( search_fn = store.similarity_search_with_score score_type = "distance" else: - return [] + matches = _keyword_matches(label_store.labels, query, threshold=threshold) + matches.sort(key=lambda match: match.score, reverse=True) + return matches limit = k or DEFAULT_LABEL_SIMILARITY_K try: @@ -195,6 +315,15 @@ def find_similar_labels( ) ) + keyword_matches = _keyword_matches(label_store.labels, query, threshold=threshold) + if keyword_matches: + seen = {_normalize_label(match.label.name) for match in matches} + for match in keyword_matches: + normalized = _normalize_label(match.label.name) + if normalized and normalized not in seen: + matches.append(match) + seen.add(normalized) + matches.sort(key=lambda match: match.score, reverse=True) return matches diff --git a/tests/scripts/test_label_matcher.py b/tests/scripts/test_label_matcher.py index f225544f5..0292c836c 100644 --- a/tests/scripts/test_label_matcher.py +++ b/tests/scripts/test_label_matcher.py @@ -123,3 +123,163 @@ def test_resolve_label_match_uses_semantic_search(): assert match is not None assert match.label.name == "bug" assert match.score == 0.93 + + +def test_find_similar_labels_keyword_bug_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="type:feature"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels(vector_store, "App crashes on login", threshold=0.8) + + names = [match.label.name for match in matches] + assert "type:bug" in names + assert "type:feature" not in names + bug_match = next(match for match in matches if match.label.name == "type:bug") + assert bug_match.score >= label_matcher.KEYWORD_BUG_SCORE + + +def test_find_similar_labels_keyword_feature_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="type:feature"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels( + vector_store, "Add dark mode support", threshold=0.8 + ) + + names = [match.label.name for match in matches] + assert "type:feature" in names + assert "type:bug" not in names + + +def test_find_similar_labels_keyword_feature_phrase_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="type:feature"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels(vector_store, "Dark mode", threshold=0.8) + + names = [match.label.name for match in matches] + assert "type:feature" in names + assert "type:bug" not in names + + +def test_find_similar_labels_keyword_multicategory_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="documentation"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels(vector_store, "Bug in docs examples", threshold=0.8) + + names = {match.label.name for match in matches} + assert "type:bug" in names + assert "documentation" in names + doc_match = next(match for match in matches if match.label.name == "documentation") + assert doc_match.score >= label_matcher.KEYWORD_DOCS_SCORE + + +def test_find_similar_labels_keyword_docs_description_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="quality", description="Documentation updates"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels(vector_store, "Bug in docs examples", threshold=0.8) + + names = {match.label.name for match in matches} + assert "type:bug" in names + assert "quality" in names + doc_match = next(match for match in matches if match.label.name == "quality") + assert doc_match.score >= label_matcher.KEYWORD_DOCS_SCORE + + +def test_find_similar_labels_appends_keyword_matches_after_semantic(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="documentation"), + ] + store = types.SimpleNamespace( + similarity_search_with_relevance_scores=lambda query, k=5: [ + (DummyDoc("type:bug", {"name": "type:bug"}), 0.92), + ] + ) + vector_store = label_matcher.LabelVectorStore( + store=store, provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels(vector_store, "Bug in docs examples", threshold=0.8) + + names = {match.label.name for match in matches} + assert "type:bug" in names + assert "documentation" in names + doc_match = next(match for match in matches if match.label.name == "documentation") + assert doc_match.score >= label_matcher.KEYWORD_DOCS_SCORE + + +def test_find_similar_labels_dedupes_normalized_keyword_matches(): + labels = [label_matcher.LabelRecord(name="documentation")] + store = types.SimpleNamespace( + similarity_search_with_relevance_scores=lambda query, k=5: [ + (DummyDoc("Documentation", {"name": "Documentation"}), 0.92), + ] + ) + vector_store = label_matcher.LabelVectorStore( + store=store, provider="unit-test", model="unit-test-model", labels=labels + ) + + matches = label_matcher.find_similar_labels(vector_store, "Bug in docs examples", threshold=0.8) + + assert len(matches) == 1 + assert matches[0].label.name == "Documentation" + + +def test_resolve_label_match_keyword_bug_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="type:feature"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + match = label_matcher.resolve_label_match(vector_store, "App crashes on login", threshold=0.8) + + assert match is not None + assert match.label.name == "type:bug" + assert match.score_type == "keyword" + + +def test_resolve_label_match_keyword_feature_match(): + labels = [ + label_matcher.LabelRecord(name="type:bug"), + label_matcher.LabelRecord(name="type:feature"), + ] + vector_store = label_matcher.LabelVectorStore( + store=object(), provider="unit-test", model="unit-test-model", labels=labels + ) + + match = label_matcher.resolve_label_match(vector_store, "Add dark mode support", threshold=0.8) + + assert match is not None + assert match.label.name == "type:feature" + assert match.score_type == "keyword"