CalvinCalvin
Guides

Scope Policy Consistency Analysis

Analysis of consistency between diff and deploy commands regarding scope policy handling

Scope Policy Consistency Analysis

Created: 2025-12-19
Status: Complete ✅
Updated: 2025-12-21

This document analyzes the consistency between diff and deploy commands regarding scope policy handling, and proposes fixes.


Executive Summary

The diff (Preview changes) and deploy commands use different logic for determining scope policy, leading to inconsistent behavior:

  1. diff now reads config.deploy.target (fixed), but deploy does not
  2. They use different ScopePolicy variants for the same scenario
  3. Neither command handles file deletion for orphaned Calvin-managed files

Scope System Overview

Asset-Level Scope (models.rs)

ScopeDescription
Scope::Project (default)Install to project directory (e.g., .cursor/rules/)
Scope::UserInstall to home directory (e.g., ~/.cursor/rules/)

Compile/Filter Policy (sync/scope.rs)

ScopePolicyBehavior
Keep (default)Preserve original scope from asset frontmatter
ProjectOnlyOnly keep scope: project assets (filter out user)
UserOnlyOnly keep scope: user assets (filter out project)
ForceUserForce all assets to scope: user
ForceProjectForce all assets to scope: project

Configuration (config.rs)

DeployTargetConfigDescription
Unset (default)Not configured, user needs to choose
ProjectDeploy to project directory
HomeDeploy to user home directory

Current Behavior Comparison

cmd_diff (after fix)

// Reads config.deploy.target
let use_home = home || config.deploy.target == DeployTargetConfig::Home;

let scope_policy = if use_home {
    ScopePolicy::UserOnly    // ← Only keeps scope:user assets
} else {
    ScopePolicy::ProjectOnly // ← Only keeps scope:project assets
};
flowchart TD
    Start([Start Diff]) --> CheckConfig{Check config.deploy.target}
    CheckConfig -- Home --> UseHome[Use Home]
    CheckConfig -- Project --> UseProject[Use Project]
    CheckConfig -- Unset --> CheckFlag{Check --home flag}
    
    CheckFlag -- Yes --> UseHome
    CheckFlag -- No --> UseProject
    
    UseHome --> PolicyUser[Policy: UserOnly]
    UseProject --> PolicyProject[Policy: ProjectOnly]
    
    PolicyUser --> Filter[Filter: Asset Scope == User]
    PolicyProject --> Filter2[Filter: Asset Scope == Project]

cmd_deploy (current, unfixed)

// Does NOT read config.deploy.target
let scope_policy = if home {
    ScopePolicy::ForceUser  // ← Forces ALL assets to user scope
} else {
    ScopePolicy::Keep       // ← Keeps both scopes
};
flowchart TD
    Start([Start Deploy]) --> CheckFlag{Check --home flag}
    
    CheckFlag -- Yes --> PolicyForce[Policy: ForceUser]
    CheckFlag -- No --> PolicyKeep[Policy: Keep]
    
    PolicyForce --> ForceAction[Action: Force all to User Scope]
    PolicyKeep --> KeepAction[Action: Respect Asset Scope]
    
    style Start fill:#f96,stroke:#333,stroke-dasharray: 5 5

Inconsistency Matrix

ScenariodiffdeployConsistent?
CLI --homeUserOnlyForceUser❌ No
Config target = "home"UserOnlyKeep (ignores config)❌ No
CLI defaultProjectOnlyKeep❌ No
Config target = "project"ProjectOnlyKeep (ignores config)❌ No

Semantic Questions

Q1: What should deploy.target = "home" mean?

Option A: ForceUser — Deploy ALL assets to home directory, regardless of their original scope.

  • Pro: Simple mental model ("everything goes to home")
  • Con: No way to have project-only assets when deploying globally

Option B: UserOnly — Deploy ONLY scope: user assets to home directory.

  • Pro: Fine-grained control via asset-level scope
  • Con: Requires user to understand scope distinction

Recommendation: ForceUser for --home CLI flag, UserOnly for deploy.target = "home" config.

Q2: What happens to orphaned files?

Current Behavior: Nothing. Files previously deployed by Calvin remain indefinitely.

Example Scenario:

  1. User initially configures deploy.target = "project"
  2. Runs calvin deploy → files written to .cursor/rules/, .github/instructions/, etc.
  3. User changes to deploy.target = "home"
  4. Runs calvin deploy → files written to ~/.cursor/rules/, etc.
  5. Old files in .cursor/rules/ remain (orphaned)

Desired Behavior:

  • diff should show files that will be deleted
  • deploy should delete orphaned files (with --cleanup flag, or prompt in interactive mode)
  • Only delete files with Calvin signature: <!-- Generated by Calvin. DO NOT EDIT. -->
  • Warn about files without signature that may be orphans

TODO: Implementation Tasks

Phase 1: Consistency Fixes (High Priority)

  • TODO-SC-1: Update cmd_deploy to read config.deploy.target ✅ 2025-12-19

    • File: src/commands/deploy/cmd.rs
    • Added DeployTargetConfig check similar to cmd_diff and cmd_watch
  • TODO-SC-2: Unify ScopePolicy usage between diff and deploy ✅ 2025-12-19

    • Decision: Use ForceUser for home target, Keep for project target
    • Rationale: Matches CLI --home semantics, reduces cognitive load
  • TODO-SC-3: Update interactive.rs menu options ✅ 2025-12-19

    • No code change needed: cmd_diff and cmd_watch already read config internally
    • CLI home parameter is just an override, config is the default

Phase 2: Orphan File Management (Medium Priority)

  • TODO-SC-4: Add orphan file detection in diff ✅ 2025-12-19

    • Integrated lockfile loading and detect_orphans() call
    • Updated JSON output with "orphans" field
    • Updated text output with render_diff_summary_with_orphans()
  • TODO-SC-5: Implement Calvin signature detection ✅ 2025-12-19

    • has_calvin_signature() function in src/sync/orphan.rs
    • Detects three signature patterns (HTML comment, hash comment, etc.)
  • TODO-SC-6: Add --cleanup flag to deploy ✅ 2025-12-19

    • Implemented delete_orphans() function
    • Interactive confirmation, JSON events, lockfile update
    • Safety: only delete files with Calvin signature
  • TODO-SC-7: Update lockfile to track scope ✅ 2025-12-19

    • Added scope field to FileEntry
    • Backward compatible with existing lockfiles

Phase 3: Documentation & Testing

  • TODO-SC-8: Document scope policy semantics ✅ 2025-12-19

    • Created docs/scope-guide.md
    • Updated docs/command-reference.md with --cleanup flag
  • TODO-SC-9: Integration tests ✅ 2025-12-19

    • 349+ tests covering scope consistency
    • Orphan detection and cleanup tests

FileResponsibility
src/commands/debug.rscmd_diff implementation
src/commands/deploy/cmd.rscmd_deploy entry point
src/application/deploy/use_case.rsCore deploy logic
src/commands/interactive/Interactive menu
src/domain/policies/scope_policy.rsScopePolicy enum and apply logic
src/domain/entities/lockfile.rsTracks deployed files
src/config/Configuration definitions

Decision Log

DateDecisionRationale
2025-12-19Identified inconsistencydiff and deploy use different scope policies
2025-12-19Created tracking documentNeed structured approach to fix
2025-12-19Chose ForceUser for homeMatches CLI semantics, simpler mental model, no behavior change for deploy

References

On this page