Skip to content

Commit 74dcaf3

Browse files
fix(workflow): address Copilot review feedback on #2421
- Centralize the integration.json path as a module-level INTEGRATION_JSON constant in workflows/engine.py (mirrors specify_cli.INTEGRATION_JSON; cannot be imported directly without a circular dependency). - Catch UnicodeDecodeError alongside JSONDecodeError/OSError so a non-UTF8 integration.json falls back to the literal default instead of crashing the workflow engine. Adds a regression test. - Drop the narrow requires.integrations.any allowlist in the speckit workflow YAML; the four core commands (specify/plan/tasks/implement) are provided by every integration, so the previous list was always inaccurate (e.g. opencode, codex, etc. were excluded).
1 parent 56187f3 commit 74dcaf3

3 files changed

Lines changed: 37 additions & 4 deletions

File tree

src/specify_cli/workflows/engine.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
from .base import RunStatus, StepContext, StepResult, StepStatus
2323

2424

25+
# Path to the file recording the project's initialized integration. Kept in
26+
# sync with ``specify_cli.INTEGRATION_JSON``; the workflows package cannot
27+
# import that constant directly without creating a circular import on the
28+
# CLI module.
29+
INTEGRATION_JSON = ".specify/integration.json"
30+
31+
2532
# -- Workflow Definition --------------------------------------------------
2633

2734

@@ -741,12 +748,12 @@ def _load_project_integration(self) -> str | None:
741748
Returns None when the file is missing or malformed; callers are
742749
expected to fall back to a literal default.
743750
"""
744-
path = self.project_root / ".specify" / "integration.json"
751+
path = self.project_root / INTEGRATION_JSON
745752
if not path.is_file():
746753
return None
747754
try:
748755
data = json.loads(path.read_text(encoding="utf-8"))
749-
except (json.JSONDecodeError, OSError):
756+
except (json.JSONDecodeError, OSError, UnicodeDecodeError):
750757
return None
751758
if not isinstance(data, dict):
752759
return None

tests/test_workflows.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,31 @@ def test_integration_auto_ignores_malformed_integration_json(self, project_dir):
15931593
resolved = engine._resolve_inputs(definition, {})
15941594
assert resolved["integration"] == "auto"
15951595

1596+
def test_integration_auto_ignores_non_utf8_integration_json(self, project_dir):
1597+
"""A non-UTF8 integration.json must not crash — fall back to the literal default."""
1598+
from specify_cli.workflows.engine import WorkflowEngine, WorkflowDefinition
1599+
1600+
specify_dir = project_dir / ".specify"
1601+
specify_dir.mkdir(parents=True, exist_ok=True)
1602+
# 0xFF is invalid as the leading byte of a UTF-8 sequence, so
1603+
# ``Path.read_text(encoding="utf-8")`` raises UnicodeDecodeError.
1604+
(specify_dir / "integration.json").write_bytes(b"\xff\xfe\x00\x00")
1605+
1606+
definition = WorkflowDefinition.from_string("""
1607+
schema_version: "1.0"
1608+
workflow:
1609+
id: "auto-non-utf8"
1610+
name: "Auto Non UTF-8"
1611+
version: "1.0.0"
1612+
inputs:
1613+
integration:
1614+
type: string
1615+
default: "auto"
1616+
""")
1617+
engine = WorkflowEngine(project_dir)
1618+
resolved = engine._resolve_inputs(definition, {})
1619+
assert resolved["integration"] == "auto"
1620+
15961621

15971622
# ===== State Persistence Tests =====
15981623

workflows/speckit/workflow.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ workflow:
88

99
requires:
1010
speckit_version: ">=0.7.2"
11-
integrations:
12-
any: ["copilot", "claude", "gemini"]
11+
# The four commands used below (specify, plan, tasks, implement) are core
12+
# spec-kit commands provided by every integration, so this workflow runs
13+
# against whichever integration the project was initialized with.
1314

1415
inputs:
1516
spec:

0 commit comments

Comments
 (0)