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.
- spec_kitty_tracker-0.1.0/LICENSE +21 -0
- spec_kitty_tracker-0.1.0/PKG-INFO +137 -0
- spec_kitty_tracker-0.1.0/README.md +97 -0
- spec_kitty_tracker-0.1.0/pyproject.toml +52 -0
- spec_kitty_tracker-0.1.0/setup.cfg +4 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/__init__.py +114 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/capabilities.py +31 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/conflicts.py +94 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/__init__.py +29 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/azure_devops.py +414 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/base_http.py +83 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/beads.py +451 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/cli_runner.py +39 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/fp.py +365 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/github.py +248 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/gitlab.py +249 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/in_memory.py +175 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/jira.py +383 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/connectors/linear.py +353 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/errors.py +84 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/mapping.py +37 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/mission_sync.py +205 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/models.py +131 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/policy.py +74 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/protocols.py +90 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/py.typed +0 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/registry.py +39 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/store.py +27 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker/sync.py +282 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/PKG-INFO +137 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/SOURCES.txt +42 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/dependency_links.txt +1 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/requires.txt +7 -0
- spec_kitty_tracker-0.1.0/src/spec_kitty_tracker.egg-info/top_level.txt +1 -0
- spec_kitty_tracker-0.1.0/tests/test_bidirectional_sync_core.py +161 -0
- spec_kitty_tracker-0.1.0/tests/test_conflicts.py +50 -0
- spec_kitty_tracker-0.1.0/tests/test_error_classification.py +35 -0
- spec_kitty_tracker-0.1.0/tests/test_in_memory_connector.py +74 -0
- spec_kitty_tracker-0.1.0/tests/test_native_connectors.py +233 -0
- spec_kitty_tracker-0.1.0/tests/test_p0_provider_matrix.py +6 -0
- spec_kitty_tracker-0.1.0/tests/test_policy.py +28 -0
- spec_kitty_tracker-0.1.0/tests/test_registry.py +24 -0
- spec_kitty_tracker-0.1.0/tests/test_sync_engine.py +133 -0
- 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,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
|
+
]
|