specfact-cli 0.4.2__tar.gz → 0.5.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.
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/.gitignore +3 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/PKG-INFO +5 -96
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/README.md +3 -95
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/pyproject.toml +5 -1
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/__init__.py +1 -1
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/plan.py +170 -4
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/sync.py +245 -110
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/sync/__init__.py +10 -1
- specfact_cli-0.5.0/src/specfact_cli/sync/watcher.py +268 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/validators/repro_checker.py +22 -1
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/LICENSE.md +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.4.2 → specfact_cli-0.5.0}/src/specfact_cli/validators/schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfact-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development with automated quality gates
|
|
5
5
|
Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
|
|
@@ -98,6 +98,7 @@ Requires-Dist: ruamel-yaml>=0.18.16
|
|
|
98
98
|
Requires-Dist: ruff>=0.14.2
|
|
99
99
|
Requires-Dist: typer>=0.20.0
|
|
100
100
|
Requires-Dist: typing-extensions>=4.15.0
|
|
101
|
+
Requires-Dist: watchdog>=6.0.0
|
|
101
102
|
Provides-Extra: dev
|
|
102
103
|
Requires-Dist: basedpyright>=1.32.1; extra == 'dev'
|
|
103
104
|
Requires-Dist: beartype>=0.22.4; extra == 'dev'
|
|
@@ -189,105 +190,13 @@ We ran SpecFact CLI **on itself** to prove it works:
|
|
|
189
190
|
|
|
190
191
|
---
|
|
191
192
|
|
|
192
|
-
## What Can You Do?
|
|
193
|
-
|
|
194
|
-
### 1. 🔄 Import from GitHub Spec-Kit
|
|
195
|
-
|
|
196
|
-
Already using Spec-Kit? **Level up to automated enforcement** in one command:
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
specfact import from-spec-kit --repo ./spec-kit-project --write
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Result**: Your Spec-Kit artifacts become production-ready contracts with automated quality gates.
|
|
203
|
-
|
|
204
|
-
### 2. 🔍 Analyze Your Existing Code
|
|
205
|
-
|
|
206
|
-
Turn brownfield code into a clean spec:
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
specfact import from-code --repo . --name my-project
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Result**: Auto-generated plan showing what your code actually does
|
|
213
|
-
|
|
214
|
-
### 3. 📋 Plan New Features
|
|
215
|
-
|
|
216
|
-
Start with a spec, not with code:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
specfact plan init --interactive
|
|
220
|
-
specfact plan add-feature --key FEATURE-001 --title "User Login"
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
**Result**: Clear acceptance criteria before writing any code
|
|
224
|
-
|
|
225
|
-
### 4. 🛡️ Enforce Quality
|
|
226
|
-
|
|
227
|
-
Set rules that actually block bad code:
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
specfact enforce stage --preset balanced
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
**Modes:**
|
|
234
|
-
|
|
235
|
-
- `minimal` - Just observe, never block
|
|
236
|
-
- `balanced` - Block critical bugs, warn on others
|
|
237
|
-
- `strict` - Block everything suspicious
|
|
238
|
-
|
|
239
|
-
### 5. ✅ Validate Everything
|
|
240
|
-
|
|
241
|
-
One command to check it all:
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
specfact repro
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**Checks:** Contracts, types, async patterns, state machines
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
193
|
## Documentation
|
|
252
194
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
**Quick Links:**
|
|
256
|
-
|
|
257
|
-
- 📖 **[Getting Started](docs/getting-started/README.md)** - Installation and first steps
|
|
258
|
-
- 🎯 **[The Journey: From Spec-Kit to SpecFact](docs/guides/speckit-journey.md)** - Level up from interactive authoring to automated enforcement
|
|
259
|
-
- 📋 **[Command Reference](docs/reference/commands.md)** - All commands with examples
|
|
260
|
-
- 🤖 **[IDE Integration](docs/guides/ide-integration.md)** - Set up slash commands in your IDE
|
|
261
|
-
- 💡 **[Use Cases](docs/guides/use-cases.md)** - Real-world scenarios
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## Installation Options
|
|
266
|
-
|
|
267
|
-
### 1. uvx (Easiest)
|
|
195
|
+
**New to SpecFact?** Start with the [Getting Started Guide](docs/getting-started/README.md)
|
|
268
196
|
|
|
269
|
-
|
|
197
|
+
**Using Spec-Kit?** See [The Journey: From Spec-Kit to SpecFact](docs/guides/speckit-journey.md)
|
|
270
198
|
|
|
271
|
-
|
|
272
|
-
uvx --from specfact-cli specfact plan init
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### 2. pip
|
|
276
|
-
|
|
277
|
-
Install globally:
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
pip install specfact-cli
|
|
281
|
-
specfact --help
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### 3. Docker
|
|
285
|
-
|
|
286
|
-
Run in a container:
|
|
287
|
-
|
|
288
|
-
```bash
|
|
289
|
-
docker run ghcr.io/nold-ai/specfact-cli:latest --help
|
|
290
|
-
```
|
|
199
|
+
**Need help?** Browse the [Documentation Hub](docs/README.md)
|
|
291
200
|
|
|
292
201
|
---
|
|
293
202
|
|
|
@@ -66,105 +66,13 @@ We ran SpecFact CLI **on itself** to prove it works:
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
-
## What Can You Do?
|
|
70
|
-
|
|
71
|
-
### 1. 🔄 Import from GitHub Spec-Kit
|
|
72
|
-
|
|
73
|
-
Already using Spec-Kit? **Level up to automated enforcement** in one command:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
specfact import from-spec-kit --repo ./spec-kit-project --write
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Result**: Your Spec-Kit artifacts become production-ready contracts with automated quality gates.
|
|
80
|
-
|
|
81
|
-
### 2. 🔍 Analyze Your Existing Code
|
|
82
|
-
|
|
83
|
-
Turn brownfield code into a clean spec:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
specfact import from-code --repo . --name my-project
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**Result**: Auto-generated plan showing what your code actually does
|
|
90
|
-
|
|
91
|
-
### 3. 📋 Plan New Features
|
|
92
|
-
|
|
93
|
-
Start with a spec, not with code:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
specfact plan init --interactive
|
|
97
|
-
specfact plan add-feature --key FEATURE-001 --title "User Login"
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Result**: Clear acceptance criteria before writing any code
|
|
101
|
-
|
|
102
|
-
### 4. 🛡️ Enforce Quality
|
|
103
|
-
|
|
104
|
-
Set rules that actually block bad code:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
specfact enforce stage --preset balanced
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
**Modes:**
|
|
111
|
-
|
|
112
|
-
- `minimal` - Just observe, never block
|
|
113
|
-
- `balanced` - Block critical bugs, warn on others
|
|
114
|
-
- `strict` - Block everything suspicious
|
|
115
|
-
|
|
116
|
-
### 5. ✅ Validate Everything
|
|
117
|
-
|
|
118
|
-
One command to check it all:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
specfact repro
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Checks:** Contracts, types, async patterns, state machines
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
69
|
## Documentation
|
|
129
70
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
**Quick Links:**
|
|
133
|
-
|
|
134
|
-
- 📖 **[Getting Started](docs/getting-started/README.md)** - Installation and first steps
|
|
135
|
-
- 🎯 **[The Journey: From Spec-Kit to SpecFact](docs/guides/speckit-journey.md)** - Level up from interactive authoring to automated enforcement
|
|
136
|
-
- 📋 **[Command Reference](docs/reference/commands.md)** - All commands with examples
|
|
137
|
-
- 🤖 **[IDE Integration](docs/guides/ide-integration.md)** - Set up slash commands in your IDE
|
|
138
|
-
- 💡 **[Use Cases](docs/guides/use-cases.md)** - Real-world scenarios
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Installation Options
|
|
143
|
-
|
|
144
|
-
### 1. uvx (Easiest)
|
|
71
|
+
**New to SpecFact?** Start with the [Getting Started Guide](docs/getting-started/README.md)
|
|
145
72
|
|
|
146
|
-
|
|
73
|
+
**Using Spec-Kit?** See [The Journey: From Spec-Kit to SpecFact](docs/guides/speckit-journey.md)
|
|
147
74
|
|
|
148
|
-
|
|
149
|
-
uvx --from specfact-cli specfact plan init
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### 2. pip
|
|
153
|
-
|
|
154
|
-
Install globally:
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
pip install specfact-cli
|
|
158
|
-
specfact --help
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### 3. Docker
|
|
162
|
-
|
|
163
|
-
Run in a container:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
docker run ghcr.io/nold-ai/specfact-cli:latest --help
|
|
167
|
-
```
|
|
75
|
+
**Need help?** Browse the [Documentation Hub](docs/README.md)
|
|
168
76
|
|
|
169
77
|
---
|
|
170
78
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development with automated quality gates"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -59,6 +59,9 @@ dependencies = [
|
|
|
59
59
|
|
|
60
60
|
# Code analysis
|
|
61
61
|
"ruff>=0.14.2",
|
|
62
|
+
|
|
63
|
+
# File system watching
|
|
64
|
+
"watchdog>=6.0.0",
|
|
62
65
|
]
|
|
63
66
|
|
|
64
67
|
[project.optional-dependencies]
|
|
@@ -495,6 +498,7 @@ markers = [
|
|
|
495
498
|
"asyncio: mark test as async",
|
|
496
499
|
"integration: marks tests as integration tests",
|
|
497
500
|
"timeout: mark tests with a timeout",
|
|
501
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
498
502
|
"workflow_coverage: mark test for workflow coverage tracking",
|
|
499
503
|
"component_coverage: mark test for component coverage tracking",
|
|
500
504
|
"contract_coverage: mark test for contract coverage tracking",
|
|
@@ -543,6 +543,13 @@ def add_story(
|
|
|
543
543
|
|
|
544
544
|
@app.command("compare")
|
|
545
545
|
@beartype
|
|
546
|
+
@require(lambda manual: manual is None or isinstance(manual, Path), "Manual must be None or Path")
|
|
547
|
+
@require(lambda auto: auto is None or isinstance(auto, Path), "Auto must be None or Path")
|
|
548
|
+
@require(
|
|
549
|
+
lambda format: isinstance(format, str) and format.lower() in ("markdown", "json", "yaml"),
|
|
550
|
+
"Format must be markdown, json, or yaml",
|
|
551
|
+
)
|
|
552
|
+
@require(lambda out: out is None or isinstance(out, Path), "Out must be None or Path")
|
|
546
553
|
def compare(
|
|
547
554
|
manual: Path | None = typer.Option(
|
|
548
555
|
None,
|
|
@@ -554,6 +561,11 @@ def compare(
|
|
|
554
561
|
"--auto",
|
|
555
562
|
help="Auto-derived plan bundle path (default: latest in .specfact/plans/)",
|
|
556
563
|
),
|
|
564
|
+
code_vs_plan: bool = typer.Option(
|
|
565
|
+
False,
|
|
566
|
+
"--code-vs-plan",
|
|
567
|
+
help="Alias for comparing code-derived plan vs manual plan (auto-detects latest auto plan)",
|
|
568
|
+
),
|
|
557
569
|
format: str = typer.Option(
|
|
558
570
|
"markdown",
|
|
559
571
|
"--format",
|
|
@@ -566,19 +578,53 @@ def compare(
|
|
|
566
578
|
),
|
|
567
579
|
) -> None:
|
|
568
580
|
"""
|
|
569
|
-
Compare manual and auto-derived plans.
|
|
581
|
+
Compare manual and auto-derived plans to detect code vs plan drift.
|
|
570
582
|
|
|
571
|
-
Detects deviations between manually created plans and
|
|
572
|
-
reverse-engineered plans from code.
|
|
583
|
+
Detects deviations between manually created plans (intended design) and
|
|
584
|
+
reverse-engineered plans from code (actual implementation). This comparison
|
|
585
|
+
identifies code vs plan drift automatically.
|
|
586
|
+
|
|
587
|
+
Use --code-vs-plan for convenience: automatically compares the latest
|
|
588
|
+
code-derived plan against the manual plan.
|
|
573
589
|
|
|
574
590
|
Example:
|
|
575
591
|
specfact plan compare --manual .specfact/plans/main.bundle.yaml --auto .specfact/plans/auto-derived-<timestamp>.bundle.yaml
|
|
592
|
+
specfact plan compare --code-vs-plan # Convenience alias
|
|
576
593
|
"""
|
|
577
594
|
from specfact_cli.utils.structure import SpecFactStructure
|
|
578
595
|
|
|
579
596
|
# Ensure .specfact structure exists
|
|
580
597
|
SpecFactStructure.ensure_structure()
|
|
581
598
|
|
|
599
|
+
# Handle --code-vs-plan convenience alias
|
|
600
|
+
if code_vs_plan:
|
|
601
|
+
# Auto-detect manual plan (default)
|
|
602
|
+
if manual is None:
|
|
603
|
+
manual = SpecFactStructure.get_default_plan_path()
|
|
604
|
+
if not manual.exists():
|
|
605
|
+
print_error(
|
|
606
|
+
f"Default manual plan not found: {manual}\nCreate one with: specfact plan init --interactive"
|
|
607
|
+
)
|
|
608
|
+
raise typer.Exit(1)
|
|
609
|
+
print_info(f"Using default manual plan: {manual}")
|
|
610
|
+
|
|
611
|
+
# Auto-detect latest code-derived plan
|
|
612
|
+
if auto is None:
|
|
613
|
+
auto = SpecFactStructure.get_latest_brownfield_report()
|
|
614
|
+
if auto is None:
|
|
615
|
+
plans_dir = Path(SpecFactStructure.PLANS)
|
|
616
|
+
print_error(
|
|
617
|
+
f"No code-derived plans found in {plans_dir}\nGenerate one with: specfact import from-code --repo ."
|
|
618
|
+
)
|
|
619
|
+
raise typer.Exit(1)
|
|
620
|
+
print_info(f"Using latest code-derived plan: {auto}")
|
|
621
|
+
|
|
622
|
+
# Override help text to emphasize code vs plan drift
|
|
623
|
+
print_section("Code vs Plan Drift Detection")
|
|
624
|
+
console.print(
|
|
625
|
+
"[dim]Comparing intended design (manual plan) vs actual implementation (code-derived plan)[/dim]\n"
|
|
626
|
+
)
|
|
627
|
+
|
|
582
628
|
# Use default paths if not specified (smart defaults)
|
|
583
629
|
if manual is None:
|
|
584
630
|
manual = SpecFactStructure.get_default_plan_path()
|
|
@@ -705,7 +751,23 @@ def compare(
|
|
|
705
751
|
# Apply enforcement rules if config exists
|
|
706
752
|
from specfact_cli.utils.structure import SpecFactStructure
|
|
707
753
|
|
|
708
|
-
|
|
754
|
+
# Determine base path from plan paths (use manual plan's parent directory)
|
|
755
|
+
base_path = manual.parent if manual else None
|
|
756
|
+
# If base_path is not a repository root, find the repository root
|
|
757
|
+
if base_path:
|
|
758
|
+
# Walk up to find repository root (where .specfact would be)
|
|
759
|
+
current = base_path.resolve()
|
|
760
|
+
while current != current.parent:
|
|
761
|
+
if (current / SpecFactStructure.ROOT).exists():
|
|
762
|
+
base_path = current
|
|
763
|
+
break
|
|
764
|
+
current = current.parent
|
|
765
|
+
else:
|
|
766
|
+
# If we didn't find .specfact, use the plan's directory
|
|
767
|
+
# But resolve to absolute path first
|
|
768
|
+
base_path = manual.parent.resolve()
|
|
769
|
+
|
|
770
|
+
config_path = SpecFactStructure.get_enforcement_config_path(base_path)
|
|
709
771
|
if config_path.exists():
|
|
710
772
|
try:
|
|
711
773
|
from specfact_cli.utils.yaml_utils import load_yaml
|
|
@@ -895,9 +957,113 @@ def select(
|
|
|
895
957
|
print_info(" - specfact plan promote")
|
|
896
958
|
print_info(" - specfact plan add-feature")
|
|
897
959
|
print_info(" - specfact plan add-story")
|
|
960
|
+
print_info(" - specfact plan sync --shared")
|
|
898
961
|
print_info(" - specfact sync spec-kit")
|
|
899
962
|
|
|
900
963
|
|
|
964
|
+
@app.command("sync")
|
|
965
|
+
@beartype
|
|
966
|
+
@require(lambda repo: repo is None or isinstance(repo, Path), "Repo must be None or Path")
|
|
967
|
+
@require(lambda plan: plan is None or isinstance(plan, Path), "Plan must be None or Path")
|
|
968
|
+
@require(lambda overwrite: isinstance(overwrite, bool), "Overwrite must be bool")
|
|
969
|
+
@require(lambda watch: isinstance(watch, bool), "Watch must be bool")
|
|
970
|
+
@require(lambda interval: isinstance(interval, int) and interval >= 1, "Interval must be int >= 1")
|
|
971
|
+
def sync(
|
|
972
|
+
shared: bool = typer.Option(
|
|
973
|
+
False,
|
|
974
|
+
"--shared",
|
|
975
|
+
help="Enable shared plans sync (bidirectional sync with Spec-Kit)",
|
|
976
|
+
),
|
|
977
|
+
repo: Path | None = typer.Option(
|
|
978
|
+
None,
|
|
979
|
+
"--repo",
|
|
980
|
+
help="Path to repository (default: current directory)",
|
|
981
|
+
),
|
|
982
|
+
plan: Path | None = typer.Option(
|
|
983
|
+
None,
|
|
984
|
+
"--plan",
|
|
985
|
+
help="Path to SpecFact plan bundle for SpecFact → Spec-Kit conversion (default: active plan)",
|
|
986
|
+
),
|
|
987
|
+
overwrite: bool = typer.Option(
|
|
988
|
+
False,
|
|
989
|
+
"--overwrite",
|
|
990
|
+
help="Overwrite existing Spec-Kit artifacts (delete all existing before sync)",
|
|
991
|
+
),
|
|
992
|
+
watch: bool = typer.Option(
|
|
993
|
+
False,
|
|
994
|
+
"--watch",
|
|
995
|
+
help="Watch mode for continuous sync",
|
|
996
|
+
),
|
|
997
|
+
interval: int = typer.Option(
|
|
998
|
+
5,
|
|
999
|
+
"--interval",
|
|
1000
|
+
help="Watch interval in seconds (default: 5)",
|
|
1001
|
+
min=1,
|
|
1002
|
+
),
|
|
1003
|
+
) -> None:
|
|
1004
|
+
"""
|
|
1005
|
+
Sync shared plans between Spec-Kit and SpecFact (bidirectional sync).
|
|
1006
|
+
|
|
1007
|
+
This is a convenience wrapper around `specfact sync spec-kit --bidirectional`
|
|
1008
|
+
that enables team collaboration through shared structured plans. The bidirectional
|
|
1009
|
+
sync keeps Spec-Kit artifacts and SpecFact plans synchronized automatically.
|
|
1010
|
+
|
|
1011
|
+
Shared plans enable:
|
|
1012
|
+
- Team collaboration: Multiple developers can work on the same plan
|
|
1013
|
+
- Automated sync: Changes in Spec-Kit automatically sync to SpecFact
|
|
1014
|
+
- Deviation detection: Compare code vs plan drift automatically
|
|
1015
|
+
- Conflict resolution: Automatic conflict detection and resolution
|
|
1016
|
+
|
|
1017
|
+
Example:
|
|
1018
|
+
specfact plan sync --shared # One-time sync
|
|
1019
|
+
specfact plan sync --shared --watch # Continuous sync
|
|
1020
|
+
specfact plan sync --shared --repo ./project # Sync specific repo
|
|
1021
|
+
"""
|
|
1022
|
+
from specfact_cli.commands.sync import sync_spec_kit
|
|
1023
|
+
from specfact_cli.utils.structure import SpecFactStructure
|
|
1024
|
+
|
|
1025
|
+
if not shared:
|
|
1026
|
+
print_error("This command requires --shared flag")
|
|
1027
|
+
print_info("Use 'specfact plan sync --shared' to enable shared plans sync")
|
|
1028
|
+
print_info("Or use 'specfact sync spec-kit --bidirectional' for direct sync")
|
|
1029
|
+
raise typer.Exit(1)
|
|
1030
|
+
|
|
1031
|
+
# Use default repo if not specified
|
|
1032
|
+
if repo is None:
|
|
1033
|
+
repo = Path(".").resolve()
|
|
1034
|
+
print_info(f"Using current directory: {repo}")
|
|
1035
|
+
|
|
1036
|
+
# Use default plan if not specified
|
|
1037
|
+
if plan is None:
|
|
1038
|
+
plan = SpecFactStructure.get_default_plan_path()
|
|
1039
|
+
if not plan.exists():
|
|
1040
|
+
print_warning(f"Default plan not found: {plan}")
|
|
1041
|
+
print_info("Using default plan path (will be created if needed)")
|
|
1042
|
+
else:
|
|
1043
|
+
print_info(f"Using active plan: {plan}")
|
|
1044
|
+
|
|
1045
|
+
print_section("Shared Plans Sync")
|
|
1046
|
+
console.print("[dim]Bidirectional sync between Spec-Kit and SpecFact for team collaboration[/dim]\n")
|
|
1047
|
+
|
|
1048
|
+
# Call the underlying sync command
|
|
1049
|
+
try:
|
|
1050
|
+
# Call sync_spec_kit with bidirectional=True
|
|
1051
|
+
sync_spec_kit(
|
|
1052
|
+
repo=repo,
|
|
1053
|
+
bidirectional=True, # Always bidirectional for shared plans
|
|
1054
|
+
plan=plan,
|
|
1055
|
+
overwrite=overwrite,
|
|
1056
|
+
watch=watch,
|
|
1057
|
+
interval=interval,
|
|
1058
|
+
)
|
|
1059
|
+
except typer.Exit:
|
|
1060
|
+
# Re-raise typer.Exit to preserve exit codes
|
|
1061
|
+
raise
|
|
1062
|
+
except Exception as e:
|
|
1063
|
+
print_error(f"Shared plans sync failed: {e}")
|
|
1064
|
+
raise typer.Exit(1) from e
|
|
1065
|
+
|
|
1066
|
+
|
|
901
1067
|
@app.command("promote")
|
|
902
1068
|
@beartype
|
|
903
1069
|
@require(lambda plan: plan is None or isinstance(plan, Path), "Plan must be None or Path")
|