spec-kitty-tracker 0.1.0__tar.gz

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 (44) hide show
  1. spec_kitty_tracker-0.1.0/LICENSE +21 -0
  2. spec_kitty_tracker-0.1.0/PKG-INFO +137 -0
  3. spec_kitty_tracker-0.1.0/README.md +97 -0
  4. spec_kitty_tracker-0.1.0/pyproject.toml +52 -0
  5. spec_kitty_tracker-0.1.0/setup.cfg +4 -0
  6. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/__init__.py +114 -0
  7. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/capabilities.py +31 -0
  8. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/conflicts.py +94 -0
  9. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/__init__.py +29 -0
  10. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/azure_devops.py +414 -0
  11. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/base_http.py +83 -0
  12. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/beads.py +451 -0
  13. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/cli_runner.py +39 -0
  14. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/fp.py +365 -0
  15. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/github.py +248 -0
  16. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/gitlab.py +249 -0
  17. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/in_memory.py +175 -0
  18. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/jira.py +383 -0
  19. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/linear.py +353 -0
  20. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/errors.py +84 -0
  21. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/mapping.py +37 -0
  22. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/mission_sync.py +205 -0
  23. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/models.py +131 -0
  24. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/policy.py +74 -0
  25. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/protocols.py +90 -0
  26. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/py.typed +0 -0
  27. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/registry.py +39 -0
  28. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/store.py +27 -0
  29. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/sync.py +282 -0
  30. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/PKG-INFO +137 -0
  31. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/SOURCES.txt +42 -0
  32. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/dependency_links.txt +1 -0
  33. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/requires.txt +7 -0
  34. spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/top_level.txt +1 -0
  35. spec_kitty_tracker-0.1.0/tests/test_bidirectional_sync_core.py +161 -0
  36. spec_kitty_tracker-0.1.0/tests/test_conflicts.py +50 -0
  37. spec_kitty_tracker-0.1.0/tests/test_error_classification.py +35 -0
  38. spec_kitty_tracker-0.1.0/tests/test_in_memory_connector.py +74 -0
  39. spec_kitty_tracker-0.1.0/tests/test_native_connectors.py +233 -0
  40. spec_kitty_tracker-0.1.0/tests/test_p0_provider_matrix.py +6 -0
  41. spec_kitty_tracker-0.1.0/tests/test_policy.py +28 -0
  42. spec_kitty_tracker-0.1.0/tests/test_registry.py +24 -0
  43. spec_kitty_tracker-0.1.0/tests/test_sync_engine.py +133 -0
  44. spec_kitty_tracker-0.1.0/tests/test_vendor_mapping.py +68 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Spec Kitty Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: spec-kitty-tracker
3
+ Version: 0.1.0
4
+ Summary: Universal tracker interface, connector SDK, and sync engine for Spec Kitty
5
+ Author: Spec Kitty Contributors
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Spec Kitty Contributors
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Classifier: License :: OSI Approved :: MIT License
29
+ Classifier: Programming Language :: Python :: 3
30
+ Requires-Python: >=3.11
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: httpx<1.0.0,>=0.27.0
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
37
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # spec-kitty-tracker
42
+
43
+ Shared task-tracker abstraction layer for Spec Kitty CLI and SaaS.
44
+
45
+ ## Scope
46
+
47
+ 1. Canonical issue model (`CanonicalIssue`, `ExternalRef`, `CanonicalLink`)
48
+ 2. Tracker connector protocol with capability negotiation
49
+ 3. Doctrine-style source-of-truth ownership policies
50
+ 4. Conflict resolution and deterministic sync engine
51
+ 5. Bidirectional mission/issue sync bridge with decision reference traceability
52
+ 6. Connector registry and vendor connector implementations
53
+
54
+ ## Included Connectors
55
+
56
+ 1. `InMemoryConnector` (fully functional reference connector)
57
+ 2. `BeadsConnector` (explicit `bd` CLI adapter)
58
+ 3. `FPConnector` (explicit `fp` CLI adapter)
59
+ 4. `JiraConnector`
60
+ 5. `LinearConnector`
61
+ 6. `AzureDevOpsConnector`
62
+ 7. `GitHubConnector`
63
+ 8. `GitLabConnector`
64
+
65
+ The vendor connectors are designed for production integration and may require tenant-specific field mappings.
66
+ `BeadsConnector` and `FPConnector` are first-class adapters for local-first native tracker workflows.
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ pip install -e .
72
+ ```
73
+
74
+ For development tools:
75
+
76
+ ```bash
77
+ pip install -e ".[dev]"
78
+ ```
79
+
80
+ ## Quick Example
81
+
82
+ ```python
83
+ import asyncio
84
+ from spec_kitty_tracker import (
85
+ CanonicalIssue,
86
+ CanonicalIssueType,
87
+ CanonicalStatus,
88
+ ExternalRef,
89
+ InMemoryConnector,
90
+ InMemoryIssueStore,
91
+ OwnershipPolicy,
92
+ OwnershipMode,
93
+ SyncEngine,
94
+ )
95
+
96
+
97
+ async def main() -> None:
98
+ connector = InMemoryConnector(name="jira", workspace="example")
99
+ store = InMemoryIssueStore()
100
+
101
+ issue = CanonicalIssue(
102
+ ref=ExternalRef(system="jira", workspace="example", id="DEMO-1", key="DEMO-1"),
103
+ title="First issue",
104
+ body="Seed issue",
105
+ status=CanonicalStatus.TODO,
106
+ issue_type=CanonicalIssueType.TASK,
107
+ )
108
+
109
+ await connector.create_issue(issue)
110
+
111
+ policy = OwnershipPolicy(mode=OwnershipMode.EXTERNAL_AUTHORITATIVE)
112
+ engine = SyncEngine(connector=connector, store=store, policy=policy)
113
+ await engine.pull()
114
+
115
+ pulled = await store.get_issue(issue.ref)
116
+ assert pulled is not None
117
+
118
+
119
+ asyncio.run(main())
120
+ ```
121
+
122
+ ## Design Notes
123
+
124
+ 1. The core contract is intentionally tracker-agnostic.
125
+ 2. `OwnershipPolicy` makes source-of-truth behavior explicit and auditable.
126
+ 3. `SyncEngine` supports pull, push, and bidirectional sync with conflict records.
127
+ 4. Capability flags gate optional features (e.g., hierarchy, dependencies, webhooks).
128
+ 5. Mission updates are idempotent and persist decision references back to source issues.
129
+
130
+ ## Docs
131
+
132
+ 1. [Architecture](docs/ARCHITECTURE.md)
133
+ 2. [Connector Contract](docs/CONNECTOR_CONTRACT.md)
134
+ 3. [Doctrine Policy Modes](docs/DOCTRINE_POLICY.md)
135
+ 4. [P0 Provider Matrix](docs/provider-matrix.md)
136
+ 5. [WP04 Contract Alignment Notes](docs/wp04-contract-alignment.md)
137
+ 6. [WP11 Sync Core Notes](docs/wp11-sync-core-notes.md)
@@ -0,0 +1,97 @@
1
+ # spec-kitty-tracker
2
+
3
+ Shared task-tracker abstraction layer for Spec Kitty CLI and SaaS.
4
+
5
+ ## Scope
6
+
7
+ 1. Canonical issue model (`CanonicalIssue`, `ExternalRef`, `CanonicalLink`)
8
+ 2. Tracker connector protocol with capability negotiation
9
+ 3. Doctrine-style source-of-truth ownership policies
10
+ 4. Conflict resolution and deterministic sync engine
11
+ 5. Bidirectional mission/issue sync bridge with decision reference traceability
12
+ 6. Connector registry and vendor connector implementations
13
+
14
+ ## Included Connectors
15
+
16
+ 1. `InMemoryConnector` (fully functional reference connector)
17
+ 2. `BeadsConnector` (explicit `bd` CLI adapter)
18
+ 3. `FPConnector` (explicit `fp` CLI adapter)
19
+ 4. `JiraConnector`
20
+ 5. `LinearConnector`
21
+ 6. `AzureDevOpsConnector`
22
+ 7. `GitHubConnector`
23
+ 8. `GitLabConnector`
24
+
25
+ The vendor connectors are designed for production integration and may require tenant-specific field mappings.
26
+ `BeadsConnector` and `FPConnector` are first-class adapters for local-first native tracker workflows.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ For development tools:
35
+
36
+ ```bash
37
+ pip install -e ".[dev]"
38
+ ```
39
+
40
+ ## Quick Example
41
+
42
+ ```python
43
+ import asyncio
44
+ from spec_kitty_tracker import (
45
+ CanonicalIssue,
46
+ CanonicalIssueType,
47
+ CanonicalStatus,
48
+ ExternalRef,
49
+ InMemoryConnector,
50
+ InMemoryIssueStore,
51
+ OwnershipPolicy,
52
+ OwnershipMode,
53
+ SyncEngine,
54
+ )
55
+
56
+
57
+ async def main() -> None:
58
+ connector = InMemoryConnector(name="jira", workspace="example")
59
+ store = InMemoryIssueStore()
60
+
61
+ issue = CanonicalIssue(
62
+ ref=ExternalRef(system="jira", workspace="example", id="DEMO-1", key="DEMO-1"),
63
+ title="First issue",
64
+ body="Seed issue",
65
+ status=CanonicalStatus.TODO,
66
+ issue_type=CanonicalIssueType.TASK,
67
+ )
68
+
69
+ await connector.create_issue(issue)
70
+
71
+ policy = OwnershipPolicy(mode=OwnershipMode.EXTERNAL_AUTHORITATIVE)
72
+ engine = SyncEngine(connector=connector, store=store, policy=policy)
73
+ await engine.pull()
74
+
75
+ pulled = await store.get_issue(issue.ref)
76
+ assert pulled is not None
77
+
78
+
79
+ asyncio.run(main())
80
+ ```
81
+
82
+ ## Design Notes
83
+
84
+ 1. The core contract is intentionally tracker-agnostic.
85
+ 2. `OwnershipPolicy` makes source-of-truth behavior explicit and auditable.
86
+ 3. `SyncEngine` supports pull, push, and bidirectional sync with conflict records.
87
+ 4. Capability flags gate optional features (e.g., hierarchy, dependencies, webhooks).
88
+ 5. Mission updates are idempotent and persist decision references back to source issues.
89
+
90
+ ## Docs
91
+
92
+ 1. [Architecture](docs/ARCHITECTURE.md)
93
+ 2. [Connector Contract](docs/CONNECTOR_CONTRACT.md)
94
+ 3. [Doctrine Policy Modes](docs/DOCTRINE_POLICY.md)
95
+ 4. [P0 Provider Matrix](docs/provider-matrix.md)
96
+ 5. [WP04 Contract Alignment Notes](docs/wp04-contract-alignment.md)
97
+ 6. [WP11 Sync Core Notes](docs/wp11-sync-core-notes.md)
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "spec-kitty-tracker"
3
+ version = "0.1.0"
4
+ description = "Universal tracker interface, connector SDK, and sync engine for Spec Kitty"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ authors = [
8
+ { name = "Spec Kitty Contributors" }
9
+ ]
10
+ license = {file = "LICENSE"}
11
+ classifiers = [
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ ]
15
+ dependencies = [
16
+ "httpx>=0.27.0,<1.0.0"
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.0.0",
22
+ "pytest-asyncio>=0.23.0",
23
+ "mypy>=1.10.0",
24
+ "ruff>=0.6.0"
25
+ ]
26
+
27
+ [build-system]
28
+ requires = ["setuptools>=61.0", "wheel"]
29
+ build-backend = "setuptools.build_meta"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools]
35
+ license-files = ["LICENSE"]
36
+
37
+ [tool.setuptools.package-data]
38
+ spec_kitty_tracker = ["py.typed"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+ python_files = "test_*.py"
43
+ python_classes = "Test*"
44
+ python_functions = "test_*"
45
+ addopts = "-q"
46
+ asyncio_mode = "auto"
47
+
48
+ [tool.mypy]
49
+ python_version = "3.11"
50
+ strict = true
51
+ warn_return_any = true
52
+ warn_unused_configs = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,114 @@
1
+ """spec-kitty-tracker: universal task tracker interface and sync engine."""
2
+
3
+ from spec_kitty_tracker.capabilities import TrackerCapabilities
4
+ from spec_kitty_tracker.conflicts import ConflictRecord, ConflictStrategy
5
+ from spec_kitty_tracker.connectors import (
6
+ AzureDevOpsConnector,
7
+ AzureDevOpsConnectorConfig,
8
+ BeadsConnector,
9
+ BeadsConnectorConfig,
10
+ FPConnector,
11
+ FPConnectorConfig,
12
+ GitHubConnector,
13
+ GitHubConnectorConfig,
14
+ GitLabConnector,
15
+ GitLabConnectorConfig,
16
+ InMemoryConnector,
17
+ JiraConnector,
18
+ JiraConnectorConfig,
19
+ LinearConnector,
20
+ LinearConnectorConfig,
21
+ )
22
+ from spec_kitty_tracker.errors import (
23
+ CapabilityNotSupportedError,
24
+ FailureClass,
25
+ ConnectorConfigError,
26
+ ConnectorRequestError,
27
+ IssueNotFoundError,
28
+ SpecKittyTrackerError,
29
+ SyncConflictError,
30
+ classify_http_status,
31
+ )
32
+ from spec_kitty_tracker.models import (
33
+ CanonicalIssue,
34
+ CanonicalIssueType,
35
+ CanonicalLink,
36
+ CanonicalStatus,
37
+ ExternalRef,
38
+ LinkType,
39
+ Page,
40
+ SyncCheckpoint,
41
+ TrackerEvent,
42
+ TrackerEventType,
43
+ utcnow,
44
+ )
45
+ from spec_kitty_tracker.mission_sync import (
46
+ BidirectionalIssueSync,
47
+ DecisionReference,
48
+ MissionSeed,
49
+ MissionUpdate,
50
+ mission_seed_from_issue,
51
+ )
52
+ from spec_kitty_tracker.policy import CORE_ISSUE_FIELDS, FieldOwner, OwnershipMode, OwnershipPolicy
53
+ from spec_kitty_tracker.protocols import LocalIssueStore, TaskTrackerConnector
54
+ from spec_kitty_tracker.registry import ConnectorRegistry
55
+ from spec_kitty_tracker.store import InMemoryIssueStore
56
+ from spec_kitty_tracker.sync import SyncEngine, SyncResult, SyncStats
57
+
58
+ __version__ = "0.1.0"
59
+
60
+ __all__ = [
61
+ "AzureDevOpsConnector",
62
+ "AzureDevOpsConnectorConfig",
63
+ "BeadsConnector",
64
+ "BeadsConnectorConfig",
65
+ "BidirectionalIssueSync",
66
+ "CapabilityNotSupportedError",
67
+ "CanonicalIssue",
68
+ "CanonicalIssueType",
69
+ "CanonicalLink",
70
+ "CanonicalStatus",
71
+ "ConflictRecord",
72
+ "ConflictStrategy",
73
+ "ConnectorConfigError",
74
+ "ConnectorRegistry",
75
+ "ConnectorRequestError",
76
+ "CORE_ISSUE_FIELDS",
77
+ "DecisionReference",
78
+ "ExternalRef",
79
+ "FailureClass",
80
+ "FieldOwner",
81
+ "FPConnector",
82
+ "FPConnectorConfig",
83
+ "GitHubConnector",
84
+ "GitHubConnectorConfig",
85
+ "GitLabConnector",
86
+ "GitLabConnectorConfig",
87
+ "InMemoryConnector",
88
+ "InMemoryIssueStore",
89
+ "IssueNotFoundError",
90
+ "JiraConnector",
91
+ "JiraConnectorConfig",
92
+ "LinearConnector",
93
+ "LinearConnectorConfig",
94
+ "LinkType",
95
+ "LocalIssueStore",
96
+ "MissionSeed",
97
+ "MissionUpdate",
98
+ "OwnershipMode",
99
+ "OwnershipPolicy",
100
+ "Page",
101
+ "SpecKittyTrackerError",
102
+ "SyncCheckpoint",
103
+ "SyncConflictError",
104
+ "SyncEngine",
105
+ "SyncResult",
106
+ "SyncStats",
107
+ "classify_http_status",
108
+ "TaskTrackerConnector",
109
+ "TrackerCapabilities",
110
+ "TrackerEvent",
111
+ "TrackerEventType",
112
+ "mission_seed_from_issue",
113
+ "utcnow",
114
+ ]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True, slots=True)
7
+ class TrackerCapabilities:
8
+ supports_webhooks: bool = False
9
+ supports_comments: bool = True
10
+ supports_hierarchy: bool = True
11
+ supports_dependencies: bool = True
12
+ supports_custom_fields: bool = True
13
+ supports_multi_assignee: bool = True
14
+ supports_sprints_or_cycles: bool = False
15
+ supports_bulk_read: bool = False
16
+ supports_bulk_write: bool = False
17
+ supports_delete: bool = False
18
+
19
+ def as_dict(self) -> dict[str, bool]:
20
+ return {
21
+ "supports_webhooks": self.supports_webhooks,
22
+ "supports_comments": self.supports_comments,
23
+ "supports_hierarchy": self.supports_hierarchy,
24
+ "supports_dependencies": self.supports_dependencies,
25
+ "supports_custom_fields": self.supports_custom_fields,
26
+ "supports_multi_assignee": self.supports_multi_assignee,
27
+ "supports_sprints_or_cycles": self.supports_sprints_or_cycles,
28
+ "supports_bulk_read": self.supports_bulk_read,
29
+ "supports_bulk_write": self.supports_bulk_write,
30
+ "supports_delete": self.supports_delete,
31
+ }
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from enum import StrEnum
6
+ from typing import Any
7
+
8
+ from spec_kitty_tracker.policy import FieldOwner
9
+
10
+
11
+ class ConflictStrategy(StrEnum):
12
+ EXTERNAL_WINS = "external_wins"
13
+ LOCAL_WINS = "local_wins"
14
+ NEWER_TIMESTAMP = "newer_timestamp"
15
+ MANUAL_REVIEW = "manual_review"
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class ConflictRecord:
20
+ field_name: str
21
+ local_value: Any
22
+ external_value: Any
23
+ resolved_value: Any
24
+ strategy: ConflictStrategy
25
+ manual_review_required: bool = False
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class FieldResolution:
30
+ value: Any
31
+ conflict: ConflictRecord | None
32
+
33
+
34
+ def _prefer_newer(
35
+ local_value: Any,
36
+ external_value: Any,
37
+ local_updated_at: datetime | None,
38
+ external_updated_at: datetime | None,
39
+ ) -> Any:
40
+ if local_updated_at is None and external_updated_at is None:
41
+ return external_value
42
+ if local_updated_at is None:
43
+ return external_value
44
+ if external_updated_at is None:
45
+ return local_value
46
+ if external_updated_at >= local_updated_at:
47
+ return external_value
48
+ return local_value
49
+
50
+
51
+ def resolve_field(
52
+ *,
53
+ field_name: str,
54
+ owner: FieldOwner,
55
+ local_value: Any,
56
+ external_value: Any,
57
+ local_updated_at: datetime | None,
58
+ external_updated_at: datetime | None,
59
+ strategy: ConflictStrategy,
60
+ ) -> FieldResolution:
61
+ if local_value == external_value:
62
+ return FieldResolution(value=local_value, conflict=None)
63
+
64
+ if owner is FieldOwner.LOCAL:
65
+ return FieldResolution(value=local_value, conflict=None)
66
+
67
+ if owner is FieldOwner.EXTERNAL:
68
+ return FieldResolution(value=external_value, conflict=None)
69
+
70
+ manual_review_required = False
71
+ if strategy is ConflictStrategy.EXTERNAL_WINS:
72
+ resolved = external_value
73
+ elif strategy is ConflictStrategy.LOCAL_WINS:
74
+ resolved = local_value
75
+ elif strategy is ConflictStrategy.NEWER_TIMESTAMP:
76
+ resolved = _prefer_newer(
77
+ local_value,
78
+ external_value,
79
+ local_updated_at,
80
+ external_updated_at,
81
+ )
82
+ else:
83
+ resolved = local_value
84
+ manual_review_required = True
85
+
86
+ conflict = ConflictRecord(
87
+ field_name=field_name,
88
+ local_value=local_value,
89
+ external_value=external_value,
90
+ resolved_value=resolved,
91
+ strategy=strategy,
92
+ manual_review_required=manual_review_required,
93
+ )
94
+ return FieldResolution(value=resolved, conflict=conflict)
@@ -0,0 +1,29 @@
1
+ from spec_kitty_tracker.connectors.azure_devops import (
2
+ AzureDevOpsConnector,
3
+ AzureDevOpsConnectorConfig,
4
+ )
5
+ from spec_kitty_tracker.connectors.beads import BeadsConnector, BeadsConnectorConfig
6
+ from spec_kitty_tracker.connectors.fp import FPConnector, FPConnectorConfig
7
+ from spec_kitty_tracker.connectors.github import GitHubConnector, GitHubConnectorConfig
8
+ from spec_kitty_tracker.connectors.gitlab import GitLabConnector, GitLabConnectorConfig
9
+ from spec_kitty_tracker.connectors.in_memory import InMemoryConnector
10
+ from spec_kitty_tracker.connectors.jira import JiraConnector, JiraConnectorConfig
11
+ from spec_kitty_tracker.connectors.linear import LinearConnector, LinearConnectorConfig
12
+
13
+ __all__ = [
14
+ "AzureDevOpsConnector",
15
+ "AzureDevOpsConnectorConfig",
16
+ "BeadsConnector",
17
+ "BeadsConnectorConfig",
18
+ "FPConnector",
19
+ "FPConnectorConfig",
20
+ "GitHubConnector",
21
+ "GitHubConnectorConfig",
22
+ "GitLabConnector",
23
+ "GitLabConnectorConfig",
24
+ "InMemoryConnector",
25
+ "JiraConnector",
26
+ "JiraConnectorConfig",
27
+ "LinearConnector",
28
+ "LinearConnectorConfig",
29
+ ]