feat(planning): start_planning hands back the active plan that already covers the work (#4079)
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
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
This commit is contained in:
@@ -104,6 +104,8 @@ const kbPromptRuleThreshold = ref("0.72");
|
||||
const kbDupThresholdSnippet = ref("0.82");
|
||||
const kbDupThresholdNote = ref("0.93");
|
||||
const kbDupThresholdTask = ref("0.93");
|
||||
// PLAN_MATCH_DEFAULT_THRESHOLD in services/dedup.py.
|
||||
const kbPlanMatchThreshold = ref("0.90");
|
||||
const savingKbInject = ref(false);
|
||||
const kbInjectSaved = ref(false);
|
||||
|
||||
@@ -155,6 +157,9 @@ async function saveKbInject() {
|
||||
const dupSnip = Math.min(1, Math.max(0, Number(kbDupThresholdSnippet.value) || 0.82));
|
||||
const dupNote = Math.min(1, Math.max(0, Number(kbDupThresholdNote.value) || 0.93));
|
||||
const dupTask = Math.min(1, Math.max(0, Number(kbDupThresholdTask.value) || 0.93));
|
||||
// Same `|| default` guard: a floor of 0 would hand back an existing plan
|
||||
// for every new one, and no plan could be started without force.
|
||||
const planT = Math.min(1, Math.max(0, Number(kbPlanMatchThreshold.value) || 0.9));
|
||||
// Same `|| default` reasoning: falling back to 0 would surface every
|
||||
// snippet in the corpus on every edit, which is the failure this knob fixes.
|
||||
const wpT = Math.min(1, Math.max(0, Number(kbWritePathThreshold.value) || 0.68));
|
||||
@@ -171,6 +176,7 @@ async function saveKbInject() {
|
||||
kbDupThresholdSnippet.value = String(dupSnip);
|
||||
kbDupThresholdNote.value = String(dupNote);
|
||||
kbDupThresholdTask.value = String(dupTask);
|
||||
kbPlanMatchThreshold.value = String(planT);
|
||||
kbWritePathThreshold.value = String(wpT);
|
||||
kbRuleHintThreshold.value = String(rhT);
|
||||
kbToolRuleThreshold.value = String(trT);
|
||||
@@ -199,6 +205,7 @@ async function saveKbInject() {
|
||||
kb_duplicate_threshold_snippet: String(dupSnip),
|
||||
kb_duplicate_threshold_note: String(dupNote),
|
||||
kb_duplicate_threshold_task: String(dupTask),
|
||||
kb_plan_match_threshold: String(planT),
|
||||
});
|
||||
kbInjectSaved.value = true;
|
||||
setTimeout(() => (kbInjectSaved.value = false), 2000);
|
||||
@@ -662,6 +669,9 @@ onMounted(async () => {
|
||||
if (allSettings.kb_duplicate_threshold_task !== undefined) {
|
||||
kbDupThresholdTask.value = allSettings.kb_duplicate_threshold_task;
|
||||
}
|
||||
if (allSettings.kb_plan_match_threshold !== undefined) {
|
||||
kbPlanMatchThreshold.value = allSettings.kb_plan_match_threshold;
|
||||
}
|
||||
if (allSettings.notify_task_reminders !== undefined) {
|
||||
notifyTaskReminders.value = allSettings.notify_task_reminders !== "false";
|
||||
}
|
||||
@@ -1623,6 +1633,27 @@ async function deleteUser(userId: number) {
|
||||
stays strict to keep the report pointed at work opened twice.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="kb-plan-match-threshold">Existing-plan match threshold</label>
|
||||
<input
|
||||
id="kb-plan-match-threshold"
|
||||
v-model="kbPlanMatchThreshold"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
class="fs-input input"
|
||||
style="max-width: 8rem"
|
||||
/>
|
||||
<p class="field-hint">
|
||||
How alike a new plan must be to an active milestone in the same project
|
||||
before an agent is handed that milestone instead of creating a second
|
||||
one. A plan with the same title always matches. Lower it if sessions
|
||||
still open parallel plans for work that already has one; raise it if
|
||||
they are sent to plans that are only related.
|
||||
</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn-primary" @click="saveKbInject" :disabled="savingKbInject">
|
||||
{{ savingKbInject ? 'Saving…' : 'Save' }}
|
||||
|
||||
Reference in New Issue
Block a user