raise-cli 2.2.1__py3-none-any.whl
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.
- raise_cli/__init__.py +38 -0
- raise_cli/__main__.py +30 -0
- raise_cli/adapters/__init__.py +91 -0
- raise_cli/adapters/declarative/__init__.py +26 -0
- raise_cli/adapters/declarative/adapter.py +267 -0
- raise_cli/adapters/declarative/discovery.py +94 -0
- raise_cli/adapters/declarative/expressions.py +150 -0
- raise_cli/adapters/declarative/reference/__init__.py +1 -0
- raise_cli/adapters/declarative/reference/github.yaml +143 -0
- raise_cli/adapters/declarative/schema.py +98 -0
- raise_cli/adapters/filesystem.py +299 -0
- raise_cli/adapters/mcp_bridge.py +10 -0
- raise_cli/adapters/mcp_confluence.py +246 -0
- raise_cli/adapters/mcp_jira.py +405 -0
- raise_cli/adapters/models.py +205 -0
- raise_cli/adapters/protocols.py +180 -0
- raise_cli/adapters/registry.py +90 -0
- raise_cli/adapters/sync.py +149 -0
- raise_cli/agents/__init__.py +14 -0
- raise_cli/agents/antigravity.yaml +8 -0
- raise_cli/agents/claude.yaml +8 -0
- raise_cli/agents/copilot.yaml +8 -0
- raise_cli/agents/copilot_plugin.py +124 -0
- raise_cli/agents/cursor.yaml +7 -0
- raise_cli/agents/roo.yaml +8 -0
- raise_cli/agents/windsurf.yaml +8 -0
- raise_cli/artifacts/__init__.py +30 -0
- raise_cli/artifacts/models.py +43 -0
- raise_cli/artifacts/reader.py +55 -0
- raise_cli/artifacts/renderer.py +104 -0
- raise_cli/artifacts/story_design.py +69 -0
- raise_cli/artifacts/writer.py +45 -0
- raise_cli/backlog/__init__.py +1 -0
- raise_cli/backlog/sync.py +115 -0
- raise_cli/cli/__init__.py +3 -0
- raise_cli/cli/commands/__init__.py +3 -0
- raise_cli/cli/commands/_resolve.py +153 -0
- raise_cli/cli/commands/adapters.py +362 -0
- raise_cli/cli/commands/artifact.py +137 -0
- raise_cli/cli/commands/backlog.py +333 -0
- raise_cli/cli/commands/base.py +31 -0
- raise_cli/cli/commands/discover.py +551 -0
- raise_cli/cli/commands/docs.py +130 -0
- raise_cli/cli/commands/doctor.py +177 -0
- raise_cli/cli/commands/gate.py +223 -0
- raise_cli/cli/commands/graph.py +1086 -0
- raise_cli/cli/commands/info.py +81 -0
- raise_cli/cli/commands/init.py +746 -0
- raise_cli/cli/commands/journal.py +167 -0
- raise_cli/cli/commands/mcp.py +524 -0
- raise_cli/cli/commands/memory.py +467 -0
- raise_cli/cli/commands/pattern.py +348 -0
- raise_cli/cli/commands/profile.py +59 -0
- raise_cli/cli/commands/publish.py +80 -0
- raise_cli/cli/commands/release.py +338 -0
- raise_cli/cli/commands/session.py +528 -0
- raise_cli/cli/commands/signal.py +410 -0
- raise_cli/cli/commands/skill.py +350 -0
- raise_cli/cli/commands/skill_set.py +145 -0
- raise_cli/cli/error_handler.py +158 -0
- raise_cli/cli/main.py +163 -0
- raise_cli/compat.py +66 -0
- raise_cli/config/__init__.py +41 -0
- raise_cli/config/agent_plugin.py +105 -0
- raise_cli/config/agent_registry.py +233 -0
- raise_cli/config/agents.py +120 -0
- raise_cli/config/ide.py +32 -0
- raise_cli/config/paths.py +379 -0
- raise_cli/config/settings.py +180 -0
- raise_cli/context/__init__.py +42 -0
- raise_cli/context/analyzers/__init__.py +16 -0
- raise_cli/context/analyzers/models.py +36 -0
- raise_cli/context/analyzers/protocol.py +43 -0
- raise_cli/context/analyzers/python.py +292 -0
- raise_cli/context/builder.py +1569 -0
- raise_cli/context/diff.py +213 -0
- raise_cli/context/extractors/__init__.py +13 -0
- raise_cli/context/extractors/skills.py +121 -0
- raise_cli/core/__init__.py +37 -0
- raise_cli/core/files.py +66 -0
- raise_cli/core/text.py +174 -0
- raise_cli/core/tools.py +441 -0
- raise_cli/discovery/__init__.py +50 -0
- raise_cli/discovery/analyzer.py +691 -0
- raise_cli/discovery/drift.py +355 -0
- raise_cli/discovery/scanner.py +1687 -0
- raise_cli/doctor/__init__.py +4 -0
- raise_cli/doctor/checks/__init__.py +1 -0
- raise_cli/doctor/checks/environment.py +110 -0
- raise_cli/doctor/checks/project.py +238 -0
- raise_cli/doctor/fix.py +80 -0
- raise_cli/doctor/models.py +56 -0
- raise_cli/doctor/protocol.py +43 -0
- raise_cli/doctor/registry.py +100 -0
- raise_cli/doctor/report.py +141 -0
- raise_cli/doctor/runner.py +95 -0
- raise_cli/engines/__init__.py +3 -0
- raise_cli/exceptions.py +215 -0
- raise_cli/gates/__init__.py +19 -0
- raise_cli/gates/builtin/__init__.py +1 -0
- raise_cli/gates/builtin/coverage.py +52 -0
- raise_cli/gates/builtin/lint.py +48 -0
- raise_cli/gates/builtin/tests.py +48 -0
- raise_cli/gates/builtin/types.py +48 -0
- raise_cli/gates/models.py +40 -0
- raise_cli/gates/protocol.py +41 -0
- raise_cli/gates/registry.py +141 -0
- raise_cli/governance/__init__.py +11 -0
- raise_cli/governance/extractor.py +412 -0
- raise_cli/governance/models.py +134 -0
- raise_cli/governance/parsers/__init__.py +35 -0
- raise_cli/governance/parsers/_convert.py +38 -0
- raise_cli/governance/parsers/adr.py +274 -0
- raise_cli/governance/parsers/backlog.py +356 -0
- raise_cli/governance/parsers/constitution.py +119 -0
- raise_cli/governance/parsers/epic.py +323 -0
- raise_cli/governance/parsers/glossary.py +316 -0
- raise_cli/governance/parsers/guardrails.py +345 -0
- raise_cli/governance/parsers/prd.py +112 -0
- raise_cli/governance/parsers/roadmap.py +118 -0
- raise_cli/governance/parsers/vision.py +116 -0
- raise_cli/graph/__init__.py +1 -0
- raise_cli/graph/backends/__init__.py +57 -0
- raise_cli/graph/backends/api.py +137 -0
- raise_cli/graph/backends/dual.py +139 -0
- raise_cli/graph/backends/pending.py +84 -0
- raise_cli/handlers/__init__.py +3 -0
- raise_cli/hooks/__init__.py +54 -0
- raise_cli/hooks/builtin/__init__.py +1 -0
- raise_cli/hooks/builtin/backlog.py +216 -0
- raise_cli/hooks/builtin/gate_bridge.py +83 -0
- raise_cli/hooks/builtin/jira_sync.py +127 -0
- raise_cli/hooks/builtin/memory.py +117 -0
- raise_cli/hooks/builtin/telemetry.py +72 -0
- raise_cli/hooks/emitter.py +184 -0
- raise_cli/hooks/events.py +262 -0
- raise_cli/hooks/protocol.py +38 -0
- raise_cli/hooks/registry.py +117 -0
- raise_cli/mcp/__init__.py +33 -0
- raise_cli/mcp/bridge.py +218 -0
- raise_cli/mcp/models.py +43 -0
- raise_cli/mcp/registry.py +77 -0
- raise_cli/mcp/schema.py +41 -0
- raise_cli/memory/__init__.py +58 -0
- raise_cli/memory/loader.py +247 -0
- raise_cli/memory/migration.py +241 -0
- raise_cli/memory/models.py +169 -0
- raise_cli/memory/writer.py +598 -0
- raise_cli/onboarding/__init__.py +103 -0
- raise_cli/onboarding/bootstrap.py +324 -0
- raise_cli/onboarding/claudemd.py +17 -0
- raise_cli/onboarding/conventions.py +742 -0
- raise_cli/onboarding/detection.py +374 -0
- raise_cli/onboarding/governance.py +443 -0
- raise_cli/onboarding/instructions.py +672 -0
- raise_cli/onboarding/manifest.py +201 -0
- raise_cli/onboarding/memory_md.py +399 -0
- raise_cli/onboarding/migration.py +207 -0
- raise_cli/onboarding/profile.py +624 -0
- raise_cli/onboarding/skill_conflict.py +100 -0
- raise_cli/onboarding/skill_manifest.py +176 -0
- raise_cli/onboarding/skills.py +437 -0
- raise_cli/onboarding/workflows.py +101 -0
- raise_cli/output/__init__.py +28 -0
- raise_cli/output/console.py +394 -0
- raise_cli/output/formatters/__init__.py +9 -0
- raise_cli/output/formatters/adapters.py +135 -0
- raise_cli/output/formatters/discover.py +439 -0
- raise_cli/output/formatters/skill.py +298 -0
- raise_cli/publish/__init__.py +3 -0
- raise_cli/publish/changelog.py +80 -0
- raise_cli/publish/check.py +179 -0
- raise_cli/publish/version.py +172 -0
- raise_cli/rai_base/__init__.py +22 -0
- raise_cli/rai_base/framework/__init__.py +7 -0
- raise_cli/rai_base/framework/methodology.yaml +233 -0
- raise_cli/rai_base/governance/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
- raise_cli/rai_base/governance/architecture/system-context.md +34 -0
- raise_cli/rai_base/governance/architecture/system-design.md +24 -0
- raise_cli/rai_base/governance/backlog.md +8 -0
- raise_cli/rai_base/governance/guardrails.md +17 -0
- raise_cli/rai_base/governance/prd.md +25 -0
- raise_cli/rai_base/governance/vision.md +16 -0
- raise_cli/rai_base/identity/__init__.py +8 -0
- raise_cli/rai_base/identity/core.md +119 -0
- raise_cli/rai_base/identity/perspective.md +119 -0
- raise_cli/rai_base/memory/__init__.py +7 -0
- raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
- raise_cli/schemas/__init__.py +3 -0
- raise_cli/schemas/journal.py +49 -0
- raise_cli/schemas/session_state.py +117 -0
- raise_cli/session/__init__.py +5 -0
- raise_cli/session/bundle.py +820 -0
- raise_cli/session/close.py +268 -0
- raise_cli/session/journal.py +119 -0
- raise_cli/session/resolver.py +126 -0
- raise_cli/session/state.py +187 -0
- raise_cli/skills/__init__.py +44 -0
- raise_cli/skills/locator.py +141 -0
- raise_cli/skills/name_checker.py +199 -0
- raise_cli/skills/parser.py +145 -0
- raise_cli/skills/scaffold.py +212 -0
- raise_cli/skills/schema.py +132 -0
- raise_cli/skills/skillsets.py +195 -0
- raise_cli/skills/validator.py +197 -0
- raise_cli/skills_base/__init__.py +80 -0
- raise_cli/skills_base/contract-template.md +60 -0
- raise_cli/skills_base/preamble.md +37 -0
- raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
- raise_cli/skills_base/rai-debug/SKILL.md +171 -0
- raise_cli/skills_base/rai-discover/SKILL.md +167 -0
- raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
- raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
- raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
- raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
- raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
- raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
- raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
- raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
- raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
- raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
- raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
- raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
- raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
- raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
- raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
- raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
- raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
- raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
- raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
- raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
- raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
- raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
- raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
- raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
- raise_cli/skills_base/rai-research/SKILL.md +143 -0
- raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
- raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
- raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
- raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
- raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
- raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
- raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
- raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
- raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
- raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
- raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
- raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
- raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
- raise_cli/telemetry/__init__.py +42 -0
- raise_cli/telemetry/schemas.py +285 -0
- raise_cli/telemetry/writer.py +217 -0
- raise_cli/tier/__init__.py +0 -0
- raise_cli/tier/context.py +134 -0
- raise_cli/viz/__init__.py +7 -0
- raise_cli/viz/generator.py +406 -0
- raise_cli-2.2.1.dist-info/METADATA +433 -0
- raise_cli-2.2.1.dist-info/RECORD +264 -0
- raise_cli-2.2.1.dist-info/WHEEL +4 -0
- raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
- raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
- raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Interactive conflict resolution for skill file updates.
|
|
2
|
+
|
|
3
|
+
When both upstream and user have modified a skill file, this module
|
|
4
|
+
provides an interactive prompt (TTY) or safe default (non-TTY) to
|
|
5
|
+
resolve the conflict.
|
|
6
|
+
|
|
7
|
+
Inspired by Rails Thor [Ynaqdhm] pattern, adapted for our use case.
|
|
8
|
+
Default action is KEEP (protect user work).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import difflib
|
|
14
|
+
import sys
|
|
15
|
+
from enum import StrEnum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConflictAction(StrEnum):
|
|
19
|
+
"""User's chosen action for a conflicting skill file."""
|
|
20
|
+
|
|
21
|
+
KEEP = "keep"
|
|
22
|
+
OVERWRITE = "overwrite"
|
|
23
|
+
DIFF = "diff"
|
|
24
|
+
BACKUP_OVERWRITE = "backup_overwrite"
|
|
25
|
+
KEEP_ALL = "keep_all"
|
|
26
|
+
OVERWRITE_ALL = "overwrite_all"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_INPUT_MAP: dict[str, ConflictAction] = {
|
|
30
|
+
"k": ConflictAction.KEEP,
|
|
31
|
+
"o": ConflictAction.OVERWRITE,
|
|
32
|
+
"d": ConflictAction.DIFF,
|
|
33
|
+
"b": ConflictAction.BACKUP_OVERWRITE,
|
|
34
|
+
"K": ConflictAction.KEEP_ALL,
|
|
35
|
+
"O": ConflictAction.OVERWRITE_ALL,
|
|
36
|
+
"": ConflictAction.KEEP, # Enter = keep (safe default)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def format_skill_diff(skill_name: str, old_content: str, new_content: str) -> str:
|
|
41
|
+
"""Format a unified diff between old and new skill content.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
skill_name: Name of the skill for display.
|
|
45
|
+
old_content: Current on-disk content.
|
|
46
|
+
new_content: New upstream content.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Formatted unified diff string.
|
|
50
|
+
"""
|
|
51
|
+
old_lines = old_content.splitlines(keepends=True)
|
|
52
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
53
|
+
diff = difflib.unified_diff(
|
|
54
|
+
old_lines,
|
|
55
|
+
new_lines,
|
|
56
|
+
fromfile=f"{skill_name}/SKILL.md (yours)",
|
|
57
|
+
tofile=f"{skill_name}/SKILL.md (upstream)",
|
|
58
|
+
)
|
|
59
|
+
return "".join(diff)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def prompt_skill_conflict(
|
|
63
|
+
skill_name: str,
|
|
64
|
+
old_content: str,
|
|
65
|
+
new_content: str,
|
|
66
|
+
) -> ConflictAction:
|
|
67
|
+
"""Prompt user to resolve a skill file conflict.
|
|
68
|
+
|
|
69
|
+
In non-TTY environments, returns KEEP without prompting.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
skill_name: Name of the conflicting skill.
|
|
73
|
+
old_content: Current on-disk content (user's version).
|
|
74
|
+
new_content: New upstream content.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The user's chosen ConflictAction.
|
|
78
|
+
"""
|
|
79
|
+
if not sys.stdin.isatty():
|
|
80
|
+
return ConflictAction.KEEP
|
|
81
|
+
|
|
82
|
+
print(f"\n {skill_name}/SKILL.md — both upstream and local changes")
|
|
83
|
+
|
|
84
|
+
while True:
|
|
85
|
+
choice = input(
|
|
86
|
+
" [d]iff [o]verwrite [k]eep (default) "
|
|
87
|
+
"[b]ackup+overwrite [O]verwrite-all [K]eep-all: "
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
action = _INPUT_MAP.get(choice)
|
|
91
|
+
if action is None:
|
|
92
|
+
print(f" Invalid choice: '{choice}'. Try again.")
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if action == ConflictAction.DIFF:
|
|
96
|
+
diff = format_skill_diff(skill_name, old_content, new_content)
|
|
97
|
+
print(diff if diff else " (no differences)")
|
|
98
|
+
continue # Re-prompt after showing diff
|
|
99
|
+
|
|
100
|
+
return action
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Skill manifest for tracking distributed skills and detecting upgrades.
|
|
2
|
+
|
|
3
|
+
The manifest (.raise/manifests/skills.json) stores per-file SHA256 hashes
|
|
4
|
+
of distributed skill files, enabling the dpkg three-hash algorithm for
|
|
5
|
+
safe upgrades: auto-update untouched files, keep customized, prompt on conflict.
|
|
6
|
+
|
|
7
|
+
The manifest also serves as the authoritative registry of RaiSE-managed skills
|
|
8
|
+
for the memory graph — skills not in the manifest are unmanaged.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
from datetime import UTC, datetime
|
|
17
|
+
from enum import StrEnum
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
21
|
+
|
|
22
|
+
from raise_cli.config.paths import MANIFESTS_SUBDIR, SKILLS_MANIFEST_FILE, get_raise_dir
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SkillSyncAction(StrEnum):
|
|
28
|
+
"""Classification of a skill's sync state using dpkg three-hash model."""
|
|
29
|
+
|
|
30
|
+
CURRENT = "current"
|
|
31
|
+
AUTO_UPDATE = "auto_update"
|
|
32
|
+
KEEP_USER = "keep_user"
|
|
33
|
+
CONFLICT = "conflict"
|
|
34
|
+
NEW = "new"
|
|
35
|
+
LEGACY = "legacy"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SkillEntry(BaseModel):
|
|
39
|
+
"""Metadata for a single distributed skill file.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
sha256: SHA256 hex digest of the SKILL.md content as written to disk.
|
|
43
|
+
version: raise-cli version that distributed this skill.
|
|
44
|
+
origin: Who distributed this skill ('framework' or 'org').
|
|
45
|
+
distributed_at: When this skill was last written.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
sha256: str
|
|
49
|
+
version: str
|
|
50
|
+
origin: str = "framework"
|
|
51
|
+
distributed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SkillManifest(BaseModel):
|
|
55
|
+
"""Manifest tracking all distributed skills for upgrade detection.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
schema_version: Manifest format version for forward compat.
|
|
59
|
+
raise_cli_version: Which CLI version last wrote this manifest.
|
|
60
|
+
distributed_at: When the manifest was last written.
|
|
61
|
+
skills: Per-skill tracking entries keyed by skill name.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
schema_version: str = "1.0"
|
|
65
|
+
raise_cli_version: str = Field(default_factory=lambda: _get_cli_version())
|
|
66
|
+
distributed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
67
|
+
skill_set: str | None = Field(
|
|
68
|
+
default=None, description="Skill set name last deployed (None = builtins only)"
|
|
69
|
+
)
|
|
70
|
+
skills: dict[str, SkillEntry] = Field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get_cli_version() -> str:
|
|
74
|
+
"""Get the current raise-cli version."""
|
|
75
|
+
try:
|
|
76
|
+
from raise_cli.skills_base import __version__
|
|
77
|
+
|
|
78
|
+
return __version__
|
|
79
|
+
except ImportError:
|
|
80
|
+
return "unknown"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def compute_content_hash(content: str) -> str:
|
|
84
|
+
"""Compute SHA256 hex digest of a string.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
content: The text content to hash.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
64-character hex digest string.
|
|
91
|
+
"""
|
|
92
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def classify_skill(
|
|
96
|
+
hash_distributed: str | None,
|
|
97
|
+
hash_on_disk: str,
|
|
98
|
+
hash_new: str,
|
|
99
|
+
) -> SkillSyncAction:
|
|
100
|
+
"""Classify a skill's sync state using the dpkg three-hash algorithm.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
hash_distributed: Hash of what we shipped last time (None if no manifest).
|
|
104
|
+
hash_on_disk: Hash of the file currently on disk.
|
|
105
|
+
hash_new: Hash of the new bundled version.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
SkillSyncAction indicating what to do with this skill.
|
|
109
|
+
"""
|
|
110
|
+
if hash_distributed is None:
|
|
111
|
+
return SkillSyncAction.LEGACY
|
|
112
|
+
|
|
113
|
+
user_changed = hash_distributed != hash_on_disk
|
|
114
|
+
upstream_changed = hash_distributed != hash_new
|
|
115
|
+
|
|
116
|
+
if not user_changed and not upstream_changed:
|
|
117
|
+
return SkillSyncAction.CURRENT
|
|
118
|
+
|
|
119
|
+
if not user_changed and upstream_changed:
|
|
120
|
+
return SkillSyncAction.AUTO_UPDATE
|
|
121
|
+
|
|
122
|
+
if user_changed and not upstream_changed:
|
|
123
|
+
return SkillSyncAction.KEEP_USER
|
|
124
|
+
|
|
125
|
+
# Both changed — but if user's version matches new, they converged
|
|
126
|
+
if hash_on_disk == hash_new:
|
|
127
|
+
return SkillSyncAction.CURRENT
|
|
128
|
+
|
|
129
|
+
return SkillSyncAction.CONFLICT
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def save_skill_manifest(manifest: SkillManifest, project_root: Path) -> None:
|
|
133
|
+
"""Save skill manifest to .raise/manifests/skills.json.
|
|
134
|
+
|
|
135
|
+
Creates directories if they don't exist.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
manifest: The manifest to save.
|
|
139
|
+
project_root: Root directory of the project.
|
|
140
|
+
"""
|
|
141
|
+
manifest_dir = get_raise_dir(project_root) / MANIFESTS_SUBDIR
|
|
142
|
+
manifest_dir.mkdir(parents=True, exist_ok=True)
|
|
143
|
+
|
|
144
|
+
manifest_path = manifest_dir / SKILLS_MANIFEST_FILE
|
|
145
|
+
data = manifest.model_dump(mode="json")
|
|
146
|
+
manifest_path.write_text(
|
|
147
|
+
json.dumps(data, indent=2, default=str),
|
|
148
|
+
encoding="utf-8",
|
|
149
|
+
)
|
|
150
|
+
logger.debug("Saved skill manifest: %s", manifest_path)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def load_skill_manifest(project_root: Path) -> SkillManifest | None:
|
|
154
|
+
"""Load skill manifest from .raise/manifests/skills.json.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
project_root: Root directory of the project.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
SkillManifest if file exists and is valid, None otherwise.
|
|
161
|
+
"""
|
|
162
|
+
manifest_path = (
|
|
163
|
+
get_raise_dir(project_root) / MANIFESTS_SUBDIR / SKILLS_MANIFEST_FILE
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if not manifest_path.exists():
|
|
167
|
+
logger.debug("Skill manifest not found: %s", manifest_path)
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
content = manifest_path.read_text(encoding="utf-8")
|
|
172
|
+
data = json.loads(content)
|
|
173
|
+
return SkillManifest.model_validate(data)
|
|
174
|
+
except (json.JSONDecodeError, ValidationError) as e:
|
|
175
|
+
logger.warning("Invalid skill manifest: %s", e)
|
|
176
|
+
return None
|