@rulemetric/local 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/meta.json +2 -2
- package/dist/server.mjs +682 -496
- package/dist/supabase/migrations/00087_cleanup_test_data_fix.sql +1 -1
- package/dist/supabase/migrations/00184_auto_accept_budget_settings.sql +113 -0
- package/dist/supabase/migrations/00185_trial_throughput_settings.sql +43 -0
- package/dist/supabase/migrations/00186_canonical_checkout_prefers_live.sql +256 -0
- package/dist/supabase/migrations/00187_recommendation_applications.sql +32 -0
- package/dist/supabase/migrations/00188_training_producer_canonical_path.sql +117 -0
- package/dist/supabase/migrations/00189_limit_obs_window_index.sql +37 -0
- package/dist/supabase/migrations/00190_eval_targets_artifact_link.sql +176 -0
- package/dist/supabase/migrations/00191_recommendation_applications_effect_ledger.sql +106 -0
- package/dist/supabase/migrations/00192_cleanup_reaps_memberless_orgs.sql +142 -0
- package/dist/supabase/migrations/00193_judge_segment_pin_and_anchors.sql +367 -0
- package/dist/supabase/migrations/00194_refine_cases_producer.sql +91 -0
- package/dist/supabase/migrations/00195_skill_usage_views.sql +109 -0
- package/dist/supabase/migrations/00196_retire_unused_skills_producer.sql +102 -0
- package/dist/supabase/migrations/00197_auto_accept_producer_admits_skills.sql +115 -0
- package/dist/supabase/migrations/00198_unstale_non_catalog_suggestions.sql +51 -0
- package/dist/supabase/migrations/00199_auto_accept_runs_every_two_hours.sql +54 -0
- package/dist/supabase/migrations/00200_auto_accept_runs_hourly.sql +42 -0
- package/dist/web/assets/{docs-D_fJ3Svo.js → docs-KIeJWTb-.js} +35 -48
- package/dist/web/assets/index-CFJatBZ3.css +1 -0
- package/dist/web/assets/{index-D8PcENaI.js → index-MYof2fnF.js} +59 -59
- package/dist/web/index.html +3 -3
- package/package.json +2 -2
- package/dist/web/assets/index-B2QaicI_.css +0 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
-- 00197 — the auto-accept producer admits skills, and the loop can promote again.
|
|
2
|
+
--
|
|
3
|
+
-- #372 ("Skills become adoptable") opened two gates and said two was all there
|
|
4
|
+
-- were: `isAutoAdoptable()` and the operator-facing eligibility count in
|
|
5
|
+
-- `promotion-paths.ts`. There were FOUR. The two it did not touch are the two
|
|
6
|
+
-- that actually decide anything:
|
|
7
|
+
--
|
|
8
|
+
-- 3. GET /api/instruction-suggestions/auto-acceptable — the endpoint the
|
|
9
|
+
-- worker asks for candidates. Opened in the same change as this file.
|
|
10
|
+
-- 4. THIS producer, which decides whether a job is created at all.
|
|
11
|
+
--
|
|
12
|
+
-- Measured 2026-08-19, on the owner's own account:
|
|
13
|
+
--
|
|
14
|
+
-- * Last applied instruction promotion: 2026-08-01. Last auto-accept
|
|
15
|
+
-- adoption: 2026-08-15. Nothing since.
|
|
16
|
+
-- * Every `add` proposal generated from 08-16 onward was type `skill` or
|
|
17
|
+
-- `feature`. Not one `instruction`. On 08-19 alone: 20 skills, 13 of them
|
|
18
|
+
-- at or above the 0.8 score gate, top score 0.95.
|
|
19
|
+
-- * So this function's `eligible` CTE matched zero rows on 08-17, 08-18 and
|
|
20
|
+
-- 08-19. The cron fired and succeeded all three mornings and enqueued
|
|
21
|
+
-- NOTHING — no job, no log line, no refusal anyone could read. The loop
|
|
22
|
+
-- did not report that it was blocked, because from its own point of view
|
|
23
|
+
-- there was nothing to do.
|
|
24
|
+
--
|
|
25
|
+
-- The gate's original comment said "a skill pasted into CLAUDE.md is a category
|
|
26
|
+
-- error". It was right, and it no longer describes the actuator: `acceptSkill`
|
|
27
|
+
-- writes a file under `.claude/skills/` and `.agents/skills/` and never touches
|
|
28
|
+
-- CLAUDE.md. The rule this enforced outlived the behaviour it was protecting
|
|
29
|
+
-- against by exactly as long as it took someone to check.
|
|
30
|
+
--
|
|
31
|
+
-- ── Why no skill budget check here ──────────────────────────────────────────
|
|
32
|
+
--
|
|
33
|
+
-- 00184's comment warns that "queueing a job that can only refuse is how the
|
|
34
|
+
-- loop reported nothing eligible for three days", so it is fair to ask why the
|
|
35
|
+
-- skill window budget is not also checked here. Because the two failure modes
|
|
36
|
+
-- are not the same kind:
|
|
37
|
+
--
|
|
38
|
+
-- * A TYPE ban can never pass. A project whose only proposals are skills is
|
|
39
|
+
-- permanently unreachable, and that is what this migration fixes.
|
|
40
|
+
-- * A BUDGET is transient and self-resetting. A run that refuses on budget
|
|
41
|
+
-- still writes its refusals to the job log with the numbers in them, which
|
|
42
|
+
-- is a stage saying why it did nothing — the thing that was missing above.
|
|
43
|
+
--
|
|
44
|
+
-- Same cheap-gates-only contract as 00170 and 00184: this decides whether to
|
|
45
|
+
-- ASK. The authoritative check, including both window budgets, stays in
|
|
46
|
+
-- GET /api/instruction-suggestions/auto-acceptable.
|
|
47
|
+
|
|
48
|
+
create or replace function public.enqueue_auto_accept_suggestions()
|
|
49
|
+
returns integer
|
|
50
|
+
language plpgsql
|
|
51
|
+
security definer
|
|
52
|
+
set search_path = public
|
|
53
|
+
as $$
|
|
54
|
+
declare
|
|
55
|
+
queued integer;
|
|
56
|
+
begin
|
|
57
|
+
with eligible as (
|
|
58
|
+
select s.user_id, s.project_path
|
|
59
|
+
from instruction_suggestions s
|
|
60
|
+
join profiles p on p.id = s.user_id
|
|
61
|
+
join instructions i on i.id = s.instruction_id
|
|
62
|
+
left join projects proj on proj.id = s.project_id
|
|
63
|
+
where
|
|
64
|
+
s.kind = 'add'
|
|
65
|
+
-- Score OR provenance. Insights nominations are filed at 0.5 by
|
|
66
|
+
-- POST /api/insights/recommendations/nominate and exempted from the
|
|
67
|
+
-- score gate by the authoritative endpoint; requiring 0.8 here made
|
|
68
|
+
-- that exemption unreachable in any project without a high-scoring
|
|
69
|
+
-- catalog proposal. The per-window budget still bounds what a run may
|
|
70
|
+
-- take, so a permissive trigger cannot become a permissive adoption.
|
|
71
|
+
and (s.score >= 0.8 or i.frontmatter->>'source' = 'insights')
|
|
72
|
+
-- The two auto-adoptable types, mirroring `isAutoAdoptable()` and the
|
|
73
|
+
-- endpoint. Executable artifacts (hook, mcp, subagent) and advisory ones
|
|
74
|
+
-- (feature) are deliberately absent: installing those runs code or edits
|
|
75
|
+
-- tool configuration, which always requires a human, and the actuator
|
|
76
|
+
-- refuses them anyway. Widening this must never widen that.
|
|
77
|
+
and i.type in ('instruction', 'skill')
|
|
78
|
+
and i.archived = false
|
|
79
|
+
and s.accepted_at is null
|
|
80
|
+
and s.dismissed_at is null
|
|
81
|
+
and s.declined_at is null
|
|
82
|
+
and s.is_stale = false
|
|
83
|
+
-- Consent: per-project override wins, else the user default (true).
|
|
84
|
+
and coalesce(
|
|
85
|
+
proj.metadata->>'autoAcceptSuggestions',
|
|
86
|
+
p.auto_accept_suggestions::text
|
|
87
|
+
) = 'true'
|
|
88
|
+
-- One in flight per (user, project), per the 00083/00086 pattern.
|
|
89
|
+
and not exists (
|
|
90
|
+
select 1 from agent_jobs j
|
|
91
|
+
where j.task_kind = 'cron_auto_accept_suggestions'
|
|
92
|
+
and j.status in ('pending', 'claimed', 'running')
|
|
93
|
+
and j.payload->>'projectPath' = s.project_path
|
|
94
|
+
and j.user_id = s.user_id
|
|
95
|
+
)
|
|
96
|
+
group by s.user_id, s.project_path
|
|
97
|
+
),
|
|
98
|
+
ins as (
|
|
99
|
+
insert into agent_jobs (user_id, task_kind, status, payload, dedupe_key)
|
|
100
|
+
select e.user_id,
|
|
101
|
+
'cron_auto_accept_suggestions',
|
|
102
|
+
'pending',
|
|
103
|
+
jsonb_build_object('projectPath', e.project_path),
|
|
104
|
+
'auto-accept:' || e.user_id::text || ':' || e.project_path
|
|
105
|
+
from eligible e
|
|
106
|
+
returning 1
|
|
107
|
+
)
|
|
108
|
+
select count(*)::int into queued from ins;
|
|
109
|
+
|
|
110
|
+
return queued;
|
|
111
|
+
end;
|
|
112
|
+
$$;
|
|
113
|
+
|
|
114
|
+
comment on function public.enqueue_auto_accept_suggestions() is
|
|
115
|
+
'Nightly producer for cron_auto_accept_suggestions. One row per (user, project) holding at least one consented ADD proposal that is either high-confidence (>= 0.8) or insights-authored, and whose instruction is a non-archived instruction body OR skill (00197 — skills joined the auto-adoptable set in #372; this producer and the auto-acceptable endpoint were the two gates that change missed, and the loop adopted nothing from 2026-08-16 to 2026-08-19 as a result). Cheap gates only — the authoritative eligibility check is GET /api/instruction-suggestions/auto-acceptable, which applies BOTH window budgets: maxPerWindow for rules (CLAUDE.md bloat) and maxSkillsPerWindow for skills (directory sprawl). Budget overrides: profiles.auto_accept_max_per_window (account) and projects.metadata.autoAcceptLimits (project). Turn it off for one repo: update projects set metadata = coalesce(metadata,''{}''::jsonb) || ''{"autoAcceptSuggestions":false}''::jsonb where id = ...; or globally: update profiles set auto_accept_suggestions = false where id = ...';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- Clear the is_stale flags the blanket mark-stale sweep should never have set.
|
|
2
|
+
--
|
|
3
|
+
-- `POST /api/instruction-suggestions/mark-stale` fires when the LOCAL skills
|
|
4
|
+
-- registry (`~/.config/rulemetric/skills-registry.json`) is older than 48h, or
|
|
5
|
+
-- simply unreadable — `checkSkillsRegistryStale()` catches to `true`. Until
|
|
6
|
+
-- today it ran as `UPDATE instruction_suggestions SET is_stale = true WHERE
|
|
7
|
+
-- user_id = ?`, with no second predicate: the age of one JSON file on the
|
|
8
|
+
-- operator's laptop invalidated every proposal the system had ever made for
|
|
9
|
+
-- that user, of every type and from every producer.
|
|
10
|
+
--
|
|
11
|
+
-- That would be recoverable if anything cleared the flag. Nothing does. The
|
|
12
|
+
-- only writers of `is_stale = false` are the two nomination upserts, and they
|
|
13
|
+
-- clear it for the ONE instruction being re-proposed. Insights nomination
|
|
14
|
+
-- identifies items by content hash, so a re-worded finding files a NEW row
|
|
15
|
+
-- rather than reviving the flagged one. So the sweep was a one-way drain, and
|
|
16
|
+
-- `is_stale = false` is a hard clause in the auto-accept eligibility gate.
|
|
17
|
+
--
|
|
18
|
+
-- Measured 2026-08-20 on the production database, trailing 30 days:
|
|
19
|
+
--
|
|
20
|
+
-- type stale / total
|
|
21
|
+
-- instruction 49 / 49 (100%)
|
|
22
|
+
-- skill 198 / 214 (93%)
|
|
23
|
+
-- feature 43 / 55 (78%)
|
|
24
|
+
-- context_file 26 / 35 (74%)
|
|
25
|
+
--
|
|
26
|
+
-- and the 05:00 auto-accept run reported `{"skipped": "nothing-eligible"}` —
|
|
27
|
+
-- the same shape it reports on a genuinely quiet night. Nothing anywhere said
|
|
28
|
+
-- the pool had been emptied by a file-age check.
|
|
29
|
+
--
|
|
30
|
+
-- The route is now scoped to catalog-backed skill proposals (the only thing the
|
|
31
|
+
-- registry is evidence about); this clears the backlog it left behind.
|
|
32
|
+
--
|
|
33
|
+
-- Deliberately NOT cleared:
|
|
34
|
+
-- * catalog skills — the registry really may be stale, and the scoped sweep
|
|
35
|
+
-- will re-flag them on the next run if it is.
|
|
36
|
+
-- * anything with `dismissed_at` set — the other two `is_stale` writers
|
|
37
|
+
-- (`retire-placeholder-nominations`, the instruction-delete path) always
|
|
38
|
+
-- stamp `dismissed_at` alongside it, so that column separates a deliberate
|
|
39
|
+
-- retirement from collateral damage.
|
|
40
|
+
-- * anything already accepted or declined — those rows have an answer, and
|
|
41
|
+
-- `is_stale` is not what is holding them.
|
|
42
|
+
|
|
43
|
+
UPDATE instruction_suggestions s
|
|
44
|
+
SET is_stale = false
|
|
45
|
+
FROM instructions i
|
|
46
|
+
WHERE i.id = s.instruction_id
|
|
47
|
+
AND s.is_stale = true
|
|
48
|
+
AND s.dismissed_at IS NULL
|
|
49
|
+
AND s.accepted_at IS NULL
|
|
50
|
+
AND s.declined_at IS NULL
|
|
51
|
+
AND NOT (i.type = 'skill' AND coalesce(i.frontmatter->>'source', '') <> 'insights');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
-- 00199 — auto-accept stops running once a day and starts running every two hours.
|
|
2
|
+
--
|
|
3
|
+
-- This is a LATENCY fix, not a throughput one — for RULES. Rule adoption stays
|
|
4
|
+
-- bounded by `maxPerWindow` (15 per project per 7 days) in
|
|
5
|
+
-- GET /api/instruction-suggestions/auto-acceptable, so twelve runs a day cannot
|
|
6
|
+
-- adopt more rules than one run a day could over a week; they can only adopt
|
|
7
|
+
-- SOONER.
|
|
8
|
+
--
|
|
9
|
+
-- CORRECTION (2026-08-20, same PR): this comment originally also cited
|
|
10
|
+
-- `maxSkillsPerWindow` (5), which that PR REMOVES — skills now return
|
|
11
|
+
-- `skillsMax: null` and are bounded only by `maxPerRun` (3), counted per type.
|
|
12
|
+
-- So for skills this cadence change IS a throughput change: 3 x 12 = up to 36 a
|
|
13
|
+
-- day where the ceiling was 5 a week. The deliberate argument for uncapping is
|
|
14
|
+
-- that skills load on demand and so do not carry the measured -22.1% cost of an
|
|
15
|
+
-- over-large CLAUDE.md, which is what `maxPerWindow` exists to bound. What holds
|
|
16
|
+
-- the line instead is the duplicate gate in `readInstalledSkills` /
|
|
17
|
+
-- `assessSkillDuplication` — which means that gate must see the WHOLE disk, both
|
|
18
|
+
-- `.agents/skills` and `.claude/skills`. It did not when this migration was
|
|
19
|
+
-- written; that is fixed in the same PR.
|
|
20
|
+
--
|
|
21
|
+
-- Sooner is the entire problem. A proposal is permanently declined once it has
|
|
22
|
+
-- been surfaced in 5 distinct session-hours (`SURFACE_BUDGET`, the
|
|
23
|
+
-- `declined_by_silence` sweep in instruction-suggestions/shared.ts), and
|
|
24
|
+
-- `declined_at` is deliberately never cleared by re-nomination. The owner opens
|
|
25
|
+
-- ~12 substantial sessions a day, so a proposal burns its budget well inside one
|
|
26
|
+
-- working day — while the only pass that could adopt it ran at 05:00 and took at
|
|
27
|
+
-- most 3.
|
|
28
|
+
--
|
|
29
|
+
-- The two clocks were incompatible, and the scoreboard says so: 59 proposals
|
|
30
|
+
-- declined by silence against 34 ever accepted. Measured 2026-08-20, this repo
|
|
31
|
+
-- had FIVE skills at or above the score gate and adopted none — four already
|
|
32
|
+
-- carried `declined_at` from the sweep, stamped before 05:00 ever came round.
|
|
33
|
+
--
|
|
34
|
+
-- Safe to run this often for two independent reasons:
|
|
35
|
+
-- * The producer's `not exists (... status in ('pending','claimed','running'))`
|
|
36
|
+
-- clause admits one job per (user, project) at a time, so runs cannot pile
|
|
37
|
+
-- up. `idx_agent_jobs_dedupe` is a plain lookup index, not a unique one, so
|
|
38
|
+
-- a COMPLETED job correctly does not block the next run.
|
|
39
|
+
-- * A run with nothing to do costs one producer query and enqueues nothing.
|
|
40
|
+
--
|
|
41
|
+
-- Cadence chosen over hourly deliberately: two hours is comfortably inside the
|
|
42
|
+
-- window in which a proposal burns 5 session-hours, and leaves the nightly
|
|
43
|
+
-- ordering (generate 04:30 → adopt) intact rather than interleaving with it
|
|
44
|
+
-- twenty-four times.
|
|
45
|
+
--
|
|
46
|
+
-- SUPERSEDED the same day by 00200, which goes hourly: "comfortably inside the
|
|
47
|
+
-- window" held for the average day and not for a morning burst, which is where
|
|
48
|
+
-- proposals were being lost. Read 00200 for the reasoning that replaced this
|
|
49
|
+
-- paragraph — this file is left as it ran.
|
|
50
|
+
|
|
51
|
+
select cron.alter_job(
|
|
52
|
+
(select jobid from cron.job where jobname = 'enqueue_auto_accept_suggestions'),
|
|
53
|
+
schedule => '0 */2 * * *'
|
|
54
|
+
);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- 00200 — auto-accept goes from every two hours to hourly.
|
|
2
|
+
--
|
|
3
|
+
-- This REVERSES the cadence choice 00199 made eight hours earlier, deliberately
|
|
4
|
+
-- and with the owner's decision on 2026-08-20. 00199's reasoning was:
|
|
5
|
+
--
|
|
6
|
+
-- "Cadence chosen over hourly deliberately: two hours is comfortably inside
|
|
7
|
+
-- the window in which a proposal burns 5 session-hours, and leaves the
|
|
8
|
+
-- nightly ordering (generate 04:30 → adopt) intact rather than interleaving
|
|
9
|
+
-- with it twenty-four times."
|
|
10
|
+
--
|
|
11
|
+
-- Two hours is *inside* that window on an average day, which is the weakest
|
|
12
|
+
-- form of the claim: it depends on the owner opening ~12 substantial sessions a
|
|
13
|
+
-- day spread evenly, and they are not spread evenly. A morning burst of five
|
|
14
|
+
-- sessions exhausts `SURFACE_BUDGET` before a two-hourly pass ever looks, and
|
|
15
|
+
-- the proposal is `declined_by_silence` without a human having declined
|
|
16
|
+
-- anything. Hourly halves the worst case rather than the average one, which is
|
|
17
|
+
-- the case that was actually losing proposals.
|
|
18
|
+
--
|
|
19
|
+
-- The interleaving objection stands and is accepted rather than answered: the
|
|
20
|
+
-- 04:30 generate → adopt ordering now has an adoption pass inside it. That is
|
|
21
|
+
-- safe for the same reason 00199 gave for running at all — a pass with nothing
|
|
22
|
+
-- eligible costs one producer query and enqueues nothing — and the producer's
|
|
23
|
+
-- `not exists (... status in ('pending','claimed','running'))` clause still
|
|
24
|
+
-- admits one job per (user, project) at a time, so a slow run cannot pile up
|
|
25
|
+
-- behind a fast schedule.
|
|
26
|
+
--
|
|
27
|
+
-- THROUGHPUT, stated plainly because 00199's original comment got this wrong:
|
|
28
|
+
-- rules stay bounded by `maxPerWindow` (15 per project per 7 days), so hourly
|
|
29
|
+
-- changes only WHEN a rule is adopted, never how many. Skills have no window
|
|
30
|
+
-- cap since `maxSkillsPerWindow` was removed, so for skills this is a real
|
|
31
|
+
-- throughput increase: `maxPerRun` (3) × 24 = up to 72 a day, from 36. What
|
|
32
|
+
-- bounds skills instead is the duplicate gate (`readInstalledSkills` +
|
|
33
|
+
-- `assessSkillDuplication`), which as of the same PR reads BOTH `.agents/skills`
|
|
34
|
+
-- and `.claude/skills` — on the owner's checkout that is 19 installed skills
|
|
35
|
+
-- visible where the gate previously saw 2. A ceiling that depends on a gate is
|
|
36
|
+
-- only as good as the gate's coverage, so if that regresses, this cadence is
|
|
37
|
+
-- the first thing to walk back.
|
|
38
|
+
|
|
39
|
+
select cron.alter_job(
|
|
40
|
+
(select jobid from cron.job where jobname = 'enqueue_auto_accept_suggestions'),
|
|
41
|
+
schedule => '0 * * * *'
|
|
42
|
+
);
|