@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.
Files changed (26) hide show
  1. package/dist/meta.json +2 -2
  2. package/dist/server.mjs +682 -496
  3. package/dist/supabase/migrations/00087_cleanup_test_data_fix.sql +1 -1
  4. package/dist/supabase/migrations/00184_auto_accept_budget_settings.sql +113 -0
  5. package/dist/supabase/migrations/00185_trial_throughput_settings.sql +43 -0
  6. package/dist/supabase/migrations/00186_canonical_checkout_prefers_live.sql +256 -0
  7. package/dist/supabase/migrations/00187_recommendation_applications.sql +32 -0
  8. package/dist/supabase/migrations/00188_training_producer_canonical_path.sql +117 -0
  9. package/dist/supabase/migrations/00189_limit_obs_window_index.sql +37 -0
  10. package/dist/supabase/migrations/00190_eval_targets_artifact_link.sql +176 -0
  11. package/dist/supabase/migrations/00191_recommendation_applications_effect_ledger.sql +106 -0
  12. package/dist/supabase/migrations/00192_cleanup_reaps_memberless_orgs.sql +142 -0
  13. package/dist/supabase/migrations/00193_judge_segment_pin_and_anchors.sql +367 -0
  14. package/dist/supabase/migrations/00194_refine_cases_producer.sql +91 -0
  15. package/dist/supabase/migrations/00195_skill_usage_views.sql +109 -0
  16. package/dist/supabase/migrations/00196_retire_unused_skills_producer.sql +102 -0
  17. package/dist/supabase/migrations/00197_auto_accept_producer_admits_skills.sql +115 -0
  18. package/dist/supabase/migrations/00198_unstale_non_catalog_suggestions.sql +51 -0
  19. package/dist/supabase/migrations/00199_auto_accept_runs_every_two_hours.sql +54 -0
  20. package/dist/supabase/migrations/00200_auto_accept_runs_hourly.sql +42 -0
  21. package/dist/web/assets/{docs-D_fJ3Svo.js → docs-KIeJWTb-.js} +35 -48
  22. package/dist/web/assets/index-CFJatBZ3.css +1 -0
  23. package/dist/web/assets/{index-D8PcENaI.js → index-MYof2fnF.js} +59 -59
  24. package/dist/web/index.html +3 -3
  25. package/package.json +2 -2
  26. 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
+ );