Skip to main content

difflore_core/packs/
mod.rs

1//! Shareable starter rule-pack marketplace (roadmap item ①).
2//!
3//! A *rule pack* is a curated, attributed starter set a brand-new team can
4//! install on day-0 to close the cold-start recall gap (`difflore import-reviews`
5//! needs `gh` auth + PR history; packs need neither). The registry is a plain
6//! public GitHub repo exposing an `index.json` catalog plus per-pack `pack.json`
7//! manifests — install is a pure HTTPS GET of public content with a `sha256`
8//! supply-chain pin, so it works logged-out and offline-after-cache.
9//!
10//! ## Honesty / moat guardrails (non-negotiable, see roadmap §1)
11//!
12//! Installed pack rules are **suggestions, not ratified memory**. They:
13//!   - carry `origin = 'pack'` (the authoritative "installed, not mined here"
14//!     marker) and a synthetic `source_repo = "pack:<id>"` that can never match
15//!     a real git remote — so the runtime scope gate confines them to the
16//!     `crossRepoStarter` suggestion-only fallback automatically (no new
17//!     privileged retrieval path);
18//!   - start at `confidence_score = 0.55`, below `manual` (0.7) and
19//!     `conversation` (0.6), so they never start at parity with earned memory;
20//!   - carry **no fabricated metrics** — `cited_count` / `trust_rate` reflect
21//!     *this team's* observed behavior and start at 0.
22//!
23//! ## Rule body format (dependency on item ⑥)
24//!
25//! Pack rule bodies are rendered through item ⑥'s public, DB-free renderer
26//! [`crate::context::rule_render::render_code_spec`] so an installed pack rule is
27//! byte-for-byte indistinguishable *in body* from a mined rule — only its
28//! `origin` / tags / `source_repo` / confidence differ. We do NOT re-implement
29//! rendering here.
30
31mod install;
32mod manifest;
33mod registry;
34
35pub use install::{InstallPackOutcome, InstalledPackRule, install_pack};
36pub use manifest::{
37    PackIndex, PackIndexEntry, PackIndexVersion, PackMaintainer, PackManifest, PackProvenance,
38    PackRule, PackRuleExamples, PackRuleProvenance, PackTarget, manifest_sha256,
39};
40pub use registry::{
41    DEFAULT_PACK_REGISTRY, PackFetchError, fetch_index, fetch_manifest, is_default_registry,
42};
43
44/// The `origin` value stamped on every installed pack rule. The single
45/// strongest provenance marker; downstream consumers key off it to render a
46/// "from a starter pack" badge and to exclude pack rules from any "your team's
47/// earned memory" metric or eval. The local `idx_skills_origin` index makes
48/// `WHERE origin = 'pack'` cheap.
49pub const PACK_ORIGIN: &str = "pack";
50
51/// Base confidence for an installed pack rule. Deliberately below `manual`
52/// (0.7) and `conversation` (0.6): pack rules are suggestions and must not
53/// start at parity with the team's own earned judgment. `confidence_from_tags`
54/// may refine via `severity:` but the install floor stays here.
55pub const PACK_CONFIDENCE: f64 = 0.55;
56
57/// Reserved synthetic-`source_repo` namespace prefix. A `pack:` value can never
58/// match a real `owner/repo` git remote, which is the isolation key (roadmap
59/// §4.2): a pack rule can only ever reach the cross-repo starter fallback.
60pub const PACK_SOURCE_REPO_PREFIX: &str = "pack:";
61
62/// Build the synthetic `source_repo` for a pack id (e.g. `difflore/go-http-safety`
63/// -> `pack:difflore/go-http-safety`).
64#[must_use]
65pub fn pack_source_repo(pack_id: &str) -> String {
66    format!("{PACK_SOURCE_REPO_PREFIX}{}", pack_id.trim())
67}
68
69/// `pack:<id>@<version>` install-identity tag. `packs list --installed` groups
70/// locally-installed rows on this tag; `packs install` treats a row already
71/// carrying it as idempotent.
72#[must_use]
73pub fn pack_version_tag(pack_id: &str, version: &str) -> String {
74    format!("pack:{}@{}", pack_id.trim(), version.trim())
75}
76
77/// `pack-rule:<ruleId>` per-rule identity tag — the lever a version supersede
78/// deletes/replaces on, independent of the `@version` suffix.
79#[must_use]
80pub fn pack_rule_tag(rule_id: &str) -> String {
81    format!("pack-rule:{}", rule_id.trim())
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn synthetic_source_repo_is_never_a_real_owner_repo() {
90        // The `pack:` prefix is what guarantees a pack rule can never match a
91        // git remote — `repo_scope_from_source_repo` would derive a scope that
92        // no real `repo_scopes_for_search_rules` value can equal.
93        assert_eq!(
94            pack_source_repo("difflore/go-http-safety"),
95            "pack:difflore/go-http-safety"
96        );
97    }
98
99    #[test]
100    fn version_and_rule_tags_are_stable() {
101        assert_eq!(
102            pack_version_tag("difflore/go-http-safety", "1.2.0"),
103            "pack:difflore/go-http-safety@1.2.0"
104        );
105        assert_eq!(
106            pack_rule_tag("go-http-safety/413-body-limit"),
107            "pack-rule:go-http-safety/413-body-limit"
108        );
109    }
110}