CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 1m10s
CI & Build / TypeScript typecheck (push) Successful in 1m15s
CI & Build / Python tests (push) Failing after 1m22s
CI & Build / Build & push image (push) Skipped
Step 4 of milestone 415 "An existing plan is found before a new one is made". A session that could not see an existing plan made a second one beside it. start_planning and create_milestone now ask first: an ACTIVE milestone in the project with the same title, or one that reads as the same plan (title, design and steps against milestone embeddings), is returned with its progress and a pointer to create_records(milestone_id=...). Nothing is created; force=true bypasses. - dedup.find_matching_plan / plan_gate / plan_match_response; access-checked before either arm (rule 78), fail-open like the other gates. - Done milestones never block; the semantic arm needs 200+ chars of candidate. - kb_plan_match_threshold (default 0.90) is a setting, in the Settings view, and pinned against the Python default by test_settings_defaults_agree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
93 lines
4.1 KiB
Python
93 lines
4.1 KiB
Python
"""The Settings UI shows the default the server actually uses (#3927).
|
|
|
|
WHY THIS EXISTS
|
|
|
|
A retrieval threshold lives in two places by necessity: a Python constant the
|
|
arm reads when no row is stored, and a Vue `ref` the Settings form shows when
|
|
the operator has never touched it. Neither can import the other.
|
|
|
|
So the form's initial value is a CLAIM about the server's behaviour, and it is
|
|
the kind of claim that rots quietly. Retune the Python constant and the input
|
|
keeps rendering the old number — the operator reads it as the bar in force,
|
|
sees no reason to change anything, and the form has misinformed them about the
|
|
one fact it exists to convey. Nothing errors, nothing looks wrong, and the
|
|
value they are shown is simply not the value being applied.
|
|
|
|
WHAT THIS PINS
|
|
|
|
The RELATIONSHIP, never the number: for each threshold, the Vue ref's initial
|
|
string equals the Python default. Retuning either stays free as long as both
|
|
move — which is the point, because these are tuning values and #3853 moved one
|
|
of them the day this was written.
|
|
|
|
Both sides are read from SOURCE rather than imported. The Vue file cannot be
|
|
imported at all, and reading the Python constant through an import would tie
|
|
this to module-load side effects it has no interest in.
|
|
|
|
It cannot check that the form WRITES the right key — that is behaviour, and
|
|
the keys are asserted where they are built. It catches the drift that has no
|
|
other alarm.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import re
|
|
|
|
import pytest
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
_SERVICES = ROOT / "src" / "scribe" / "services"
|
|
_VUE = ROOT / "frontend" / "src" / "views" / "SettingsView.vue"
|
|
|
|
# (services module, python constant, vue ref). Hand-written because the
|
|
# pairing is an editorial fact — the names do not share a convention either
|
|
# side could derive — but every entry is asserted to EXIST on both sides, so a
|
|
# rename fails loudly here rather than silently dropping that threshold from
|
|
# the check.
|
|
_PAIRS = (
|
|
("plugin_context.py", "AUTOINJECT_DEFAULT_THRESHOLD", "kbInjectThreshold"),
|
|
("plugin_context.py", "WRITEPATH_DEFAULT_THRESHOLD", "kbWritePathThreshold"),
|
|
("plugin_context.py", "RULEHINT_DEFAULT_THRESHOLD", "kbRuleHintThreshold"),
|
|
("plugin_context.py", "TOOLRULE_DEFAULT_THRESHOLD", "kbToolRuleThreshold"),
|
|
("plugin_context.py", "PROMPTRULE_DEFAULT_THRESHOLD", "kbPromptRuleThreshold"),
|
|
# The plan gate (milestone 415): it blocks a create, so a form showing a
|
|
# looser bar than the one in force would be the more misleading drift.
|
|
("dedup.py", "PLAN_MATCH_DEFAULT_THRESHOLD", "kbPlanMatchThreshold"),
|
|
)
|
|
|
|
|
|
def _python_default(module: str, name: str) -> float:
|
|
m = re.search(rf"^{re.escape(name)}\s*=\s*([0-9.]+)\s*$",
|
|
(_SERVICES / module).read_text(), re.M)
|
|
assert m, (
|
|
f"{name} is no longer a bare module-level float in "
|
|
f"services/{module}. If it moved or was renamed, update "
|
|
f"_PAIRS; if it was retired, drop its row — leaving it here checks "
|
|
f"nothing while looking like coverage."
|
|
)
|
|
return float(m.group(1))
|
|
|
|
|
|
def _vue_default(ref_name: str) -> float:
|
|
m = re.search(rf"const {re.escape(ref_name)} = ref\(\"([0-9.]+)\"\)",
|
|
_VUE.read_text())
|
|
assert m, (
|
|
f"{ref_name} is no longer a `ref(\"<number>\")` in SettingsView.vue. "
|
|
f"If the control was renamed, update _PAIRS; if it was removed, the "
|
|
f"setting has lost its UI and that is the thing to fix (rule 25)."
|
|
)
|
|
return float(m.group(1))
|
|
|
|
|
|
@pytest.mark.parametrize(("module", "constant", "ref_name"), _PAIRS,
|
|
ids=[p[1] for p in _PAIRS])
|
|
def test_the_form_shows_the_default_the_server_uses(module, constant, ref_name):
|
|
"""An untouched control must render the bar actually in force."""
|
|
server, form = _python_default(module, constant), _vue_default(ref_name)
|
|
assert form == server, (
|
|
f"SettingsView shows {form} for {ref_name} while the server defaults "
|
|
f"to {server} ({constant}). An operator who has never set this reads "
|
|
f"the form as the value in force, so the two must move together — "
|
|
f"retune both, or neither."
|
|
)
|