Skip to content

fix(workflow): support integration: auto to follow project's initialized AI#2421

Open
Quratulain-bilal wants to merge 7 commits into
github:mainfrom
Quratulain-bilal:fix/workflow-integration-auto-default
Open

fix(workflow): support integration: auto to follow project's initialized AI#2421
Quratulain-bilal wants to merge 7 commits into
github:mainfrom
Quratulain-bilal:fix/workflow-integration-auto-default

Conversation

@Quratulain-bilal
Copy link
Copy Markdown
Contributor

@Quratulain-bilal Quratulain-bilal commented Apr 30, 2026

Summary

Adds support for integration: auto in workflow YAML so workflows dispatch to the AI agent the project was actually
initialized with, instead of a value baked into the workflow file.

Problem

Workflow YAML steps currently bake the integration into the file (integration: claude). When a user initializes a
project with a different agent (e.g., Copilot), they have to manually edit every workflow file. There was no way to say
"use whatever the project was initialized with."

Solution

In workflows/engine.py::_resolve_default, when a step's integration input is set to the sentinel "auto", the
engine now reads the default integration key from .specify/integration.json and substitutes it. If the state file is
missing, malformed, or written by a newer CLI, the resolver returns None and the workflow falls back to whatever
literal value the YAML specifies.

workflows/speckit/workflow.yml is updated to use integration: auto so the bundled SDD workflow works out-of-the-box
with any initialized agent.

Shared low-level reader

To avoid drift between the CLI's loud-fail _read_integration_json and the engine's silent fallback
_load_project_integration, both readers now delegate to a single low-level helper:

  • integration_state.try_read_integration_json(project_root) — pure parse + schema-guard, returns either normalized
    state or a structured IntegrationReadError (no side effects, no console output)
  • CLI (_read_integration_json) — translates each error kind (decode, os, not_object, schema_too_new) into
    its existing console message + typer.Exit(1)
  • Engine (_load_project_integration) — treats any error as a silent fallback to the workflow's literal default

INTEGRATION_JSON, INTEGRATION_STATE_SCHEMA, the new helper, and all related state helpers live together in
src/specify_cli/integration_state.py — the canonical home for integration state.

Tests

  • tests/test_workflows.py — 8 new tests covering integration: auto resolution across:
    • legacy single-integration state
    • modern normalized state (default_integration + installed_integrations)
    • missing integration.json
    • malformed JSON
    • non-UTF-8 bytes
    • schema-too-new
    • non-object root
  • tests/integrations/test_cli.py — CLI-level test for UnicodeDecodeError on malformed integration.json

Full test suite: 2782 passed, 136 skipped, 0 failures.

Files changed

  • src/specify_cli/integration_state.pytry_read_integration_json + IntegrationReadError
  • src/specify_cli/__init__.py_read_integration_json delegates to shared helper
  • src/specify_cli/workflows/engine.py_resolve_default + _load_project_integration honor integration: auto
  • workflows/speckit/workflow.yml — opt into integration: auto
  • tests/test_workflows.py, tests/integrations/test_cli.py — coverage

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adjusts the speckit workflow so its integration input defaults to the project’s initialized AI integration (via .specify/integration.json) instead of being hardcoded to Copilot, aligning workflow execution with project initialization state.

Changes:

  • Updated workflows/speckit/workflow.yml to use default: "auto" for inputs.integration and clarified the prompt text.
  • Enhanced WorkflowEngine input resolution to interpret the "auto" default sentinel for integration by reading .specify/integration.json.
  • Added regression tests covering auto-resolution, explicit override behavior, missing file fallback, and malformed JSON fallback.
Show a summary per file
File Description
workflows/speckit/workflow.yml Switches the workflow’s integration default to auto and documents the behavior in the input prompt.
src/specify_cli/workflows/engine.py Implements "auto" default resolution for the integration input by reading .specify/integration.json.
tests/test_workflows.py Adds tests validating the new default resolution and fallback/override behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment thread src/specify_cli/workflows/engine.py Outdated
Comment thread src/specify_cli/workflows/engine.py Outdated
Comment thread workflows/speckit/workflow.yml
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Quratulain-bilal added a commit to Quratulain-bilal/spec-kit that referenced this pull request May 1, 2026
- 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).
@Quratulain-bilal
Copy link
Copy Markdown
Contributor Author

Thanks for the review @mnriem and @copilot — addressed all three points in 74dcaf3:

1. Centralized path constant — added a module-level INTEGRATION_JSON = ".specify/integration.json" in workflows/engine.py. I kept it local to the package rather than importing specify_cli.INTEGRATION_JSON directly, because the CLI module imports the workflows package, so reusing the existing constant would create a circular import. A short comment in the code points to the canonical definition so future-you knows to keep them in sync.

2. UnicodeDecodeError fallback — extended the except clause to catch UnicodeDecodeError alongside JSONDecodeError/OSError, so a corrupt or non-UTF8 .specify/integration.json falls back to the literal default instead of crashing the engine. Added a regression test that writes \xff\xfe\x00\x00 (invalid UTF-8 leading byte) and confirms the workflow input resolves to "auto" without exception.

3. requires.integrations accuracy — dropped the narrow any: ["copilot", "claude", "gemini"] allowlist instead of broadening it. The four commands the workflow runs (speckit.specify, speckit.plan, speckit.tasks, speckit.implement) are core spec-kit commands provided by every integration in src/specify_cli/integrations/ (opencode, codex, qwen, cursor_agent, etc. — 30+ today), so the previous list was always inaccurate, not just after this PR. Replaced it with a comment noting the workflow runs against whichever integration the project was initialized with.

All 131 workflow tests pass locally.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment thread workflows/speckit/workflow.yml Outdated
Comment on lines +9 to +13
requires:
speckit_version: ">=0.7.2"
integrations:
any: ["copilot", "claude", "gemini"]
# The four commands used below (specify, plan, tasks, implement) are core
# spec-kit commands provided by every integration, so this workflow runs
# against whichever integration the project was initialized with.
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requires.integrations constraint was removed here, but the PR description only mentions changing the integration input default/prompt. If this removal is intentional, please update the requires metadata to explicitly reflect the intended compatibility (e.g., broaden the allowed integrations) rather than dropping the field entirely, since other tooling/docs may rely on it as a declarative compatibility signal.

Copilot uses AI. Check for mistakes.
Comment thread src/specify_cli/workflows/engine.py Outdated
Comment thread src/specify_cli/workflows/engine.py Outdated
)
elif "default" in input_def:
resolved[name] = input_def["default"]
resolved[name] = self._resolve_default(name, input_def["default"])
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_resolve_inputs() coerces/validates provided values via _coerce_input(), but defaults (including the newly project-resolved integration default) bypass type coercion and enum validation. This can yield invalid inputs values even when an input definition declares an enum/type. Consider running the resolved default through _coerce_input() as well so defaults are validated consistently with user-provided inputs.

Suggested change
resolved[name] = self._resolve_default(name, input_def["default"])
default_value = self._resolve_default(name, input_def["default"])
resolved[name] = self._coerce_input(
name, default_value, input_def
)

Copilot uses AI. Check for mistakes.
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 1, 2026

Please address Copilot feedback. If not applicable, please explain why

Quratulain-bilal added a commit to Quratulain-bilal/spec-kit that referenced this pull request May 1, 2026
- Move INTEGRATION_JSON to a dedicated specify_cli._paths module so the
  CLI entrypoint and the workflows engine share a single source of
  truth instead of each carrying its own constant. specify_cli still
  re-exports INTEGRATION_JSON for backward compatibility.
- Run the resolved default value through _coerce_input() so workflow
  defaults (including the project-resolved integration default) are
  validated against declared type/enum constraints, just like
  user-provided inputs. Adds two regression tests covering an enum
  violation and numeric coercion of a string default.
- Restore requires.integrations as an explicit declarative compatibility
  signal, using the wildcard form ``any: "*"`` to accurately reflect
  that the workflow runs against any registered integration.
@Quratulain-bilal
Copy link
Copy Markdown
Contributor Author

Round 2 — addressed in e9d946e:

1. Shared path constant (no duplication) — moved INTEGRATION_JSON to a new src/specify_cli/_paths.py module. Both specify_cli/__init__.py and specify_cli/workflows/engine.py now import from there, so there is exactly one source of truth. specify_cli.INTEGRATION_JSON is preserved as a re-export so external callers don't break. I went with a real shared module instead of the lazy __import__ pattern from the suggestion, since it avoids the import-time fallback path entirely and reads more straightforwardly.

2. Defaults now run through _coerce_input — applied the suggested change to _resolve_inputs, so resolved defaults (including the project-resolved integration default) are validated against declared type and enum constraints. Added two regression tests: one asserts a default that violates the enum raises ValueError, the other asserts a numeric default given as a string ("3") is coerced to int.

3. requires.integrations restored as a declarative signal — added it back as any: "*", the wildcard form, with a comment explaining that the four core commands (specify/plan/tasks/implement) are provided by every integration. This keeps the field in place as a compatibility hint for tooling/docs while accurately representing the workflow's actual reach (all 30+ registered integrations, not just copilot/claude/gemini).

Test results: 133 passed in tests/test_workflows.py; full suite 1732 passed, 125 skipped (no regressions from the __init__.py re-export change).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 5

Comment thread src/specify_cli/workflows/engine.py
Comment thread src/specify_cli/workflows/engine.py Outdated
Comment thread tests/test_workflows.py
Comment thread workflows/speckit/workflow.yml Outdated
Comment thread tests/test_workflows.py Outdated
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 1, 2026

Please address Copilot feedback. If not applicable, please explain why

Quratulain-bilal added a commit to Quratulain-bilal/spec-kit that referenced this pull request May 1, 2026
- workflows/speckit/workflow.yml: replace the wildcard ``any: "*"`` with
  an explicit list ``[claude, copilot, gemini, opencode]`` matching the
  documented schema for ``requires.integrations.any``. Comment notes the
  list is a non-exhaustive compatibility hint.
- engine.validate_workflow: validate input defaults at authoring time so
  enum/type mismatches in workflow YAML surface during install/validation
  instead of at execution. The ``integration: auto`` sentinel is exempt
  because it is resolved against project state at runtime.
- Extract a shared low-level helper ``try_read_integration_json`` into
  ``specify_cli._paths`` and use it from the workflow engine. Keeps
  parsing rules consistent between the CLI's loud loader and the engine's
  silent loader; CLI ``_read_integration_json`` retains its diagnostic
  semantics layered on top of the same parsing surface.
- Drop the now-unused INTEGRATION_JSON import in engine.py.
- Update test docstring to reflect actual engine behavior on missing
  integration state (no special-cased dispatcher error message exists).
- Add 2 regression tests for validate_workflow's new default checks.
@Quratulain-bilal
Copy link
Copy Markdown
Contributor Author

Round 3 — addressed in 67e4d1f. Walking through each Copilot point:

1. requires.integrations.any: "*" not in documented schema — fair catch; replaced with an explicit list [claude, copilot, gemini, opencode] matching the documented schema. A short comment notes the list is a non-exhaustive compatibility hint and the workflow's four core commands run against any registered integration.

2. _resolve_inputs validates at runtime, not install time — added default validation to validate_workflow(). Authoring mistakes (e.g. a default not in the declared enum, or a non-numeric default for a type: number input) now surface during specify workflow validate/install instead of waiting for execution. The integration: auto sentinel is explicitly exempt because it is resolved against project state at runtime, not against the input's enum. Two new regression tests cover both the rejection path and the auto exemption.

3. _load_project_integration duplicates _read_integration_json parsing — extracted a shared low-level try_read_integration_json(project_root) -> dict | None into specify_cli._paths. The workflow engine now delegates to it; the CLI's loud _read_integration_json (which exits the process with diagnostics) keeps its existing per-exception messaging and is documented as the layered counterpart. This way both paths share the same parsing rules without forcing the CLI to drop its specific error reporting.

4. Test docstring overstates dispatcher behavior — applied the suggested wording change. The docstring now accurately states that the engine must not invent an integration; downstream resolution handles an unresolved "auto".

5. PR description test count drifted — updated the PR body to match the actual coverage (8 new regression tests, full breakdown in the "Fix" section).

Test results: 135 passed in tests/test_workflows.py; full suite still 1732 passed / 125 skipped.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

src/specify_cli/workflows/engine.py:741

  • _resolve_inputs() always runs resolved defaults through _coerce_input(). For integration: auto with an enum that does not include "auto", if .specify/integration.json is missing/malformed then _resolve_default() returns the literal "auto" and _coerce_input() will raise a ValueError during input resolution. This is inconsistent with the validation-time exemption for integration: auto and with the intended "fall back to literal default" behavior. Consider exempting enum validation for the "auto" sentinel at runtime (or skipping coercion until after auto has resolved to a concrete integration).
            elif "default" in input_def:
                default_value = self._resolve_default(name, input_def["default"])
                resolved[name] = self._coerce_input(name, default_value, input_def)
            elif input_def.get("required", False):
  • Files reviewed: 5/5 changed files
  • Comments generated: 1

Comment thread src/specify_cli/workflows/engine.py Outdated
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 1, 2026

Please address Copilot feedback. If not applicable, please explain wh

Quratulain-bilal added a commit to Quratulain-bilal/spec-kit that referenced this pull request May 1, 2026
…#2421

Previously the ``integration: auto`` exemption skipped default validation
entirely, which meant a workflow could declare an incompatible type
(e.g. ``type: number`` with ``default: "auto"``) and still pass
validation, only to fail later at runtime.

Both the install-time check in ``validate_workflow()`` and the runtime
coercion in ``_resolve_inputs()`` now exempt only the enum-membership
check for the auto sentinel; the declared type is still enforced. Adds
a regression test that asserts a ``type: number`` + ``default: "auto"``
workflow is rejected at validation time.
@Quratulain-bilal
Copy link
Copy Markdown
Contributor Author

Round 4 — addressed in 0dd3426. Good catch.

Tightened the auto sentinel exemption to enum-only. Previously both the install-time validate_workflow() check and the runtime _resolve_inputs() coercion skipped all default validation when integration: auto — which meant a workflow could declare type: number with default: "auto" and slip through validation, only to fail later at runtime. Now both call sites build a derived input_def that drops just the enum key for the auto case (when an enum is present), and the declared type is still enforced.

Added a regression test that asserts a workflow declaring integration as type: number with default: "auto" is rejected at validation time with an "invalid default" error.

Test results: 136 passed in tests/test_workflows.py.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

src/specify_cli/init.py:1901

  • _read_integration_json() still re-implements JSON parsing directly. Since this PR introduces try_read_integration_json() as the shared low-level loader (including handling UnicodeDecodeError for non-UTF8 files), consider using that helper here (and layering the CLI’s user-facing diagnostics on top) to avoid drift and to ensure non-UTF8 integration.json doesn’t crash the CLI with an uncaught decode error.
from ._paths import INTEGRATION_JSON  # noqa: E402  (re-export for backward compat)


def _read_integration_json(project_root: Path) -> dict[str, Any]:
    """Load ``.specify/integration.json``.  Returns ``{}`` when missing."""
  • Files reviewed: 5/5 changed files
  • Comments generated: 1

Comment thread workflows/speckit/workflow.yml Outdated
Quratulain-bilal added a commit to Quratulain-bilal/spec-kit that referenced this pull request May 1, 2026
…thub#2421

- workflows/speckit/workflow.yml: bump ``requires.speckit_version`` from
  ``>=0.7.2`` to ``>=0.8.3`` so older spec-kit versions, which lack the
  engine-side resolution of the ``integration: "auto"`` sentinel, do not
  pull this workflow from the catalog and then fail by treating "auto"
  as a literal integration key. Adds an inline comment explaining the
  reason for the floor.
- src/specify_cli/__init__.py: ``_read_integration_json`` now also
  catches ``UnicodeDecodeError`` (non-UTF8 file) so the CLI fails with
  a targeted, actionable diagnostic instead of an uncaught traceback,
  and notes in the docstring that the function shares its low-level
  parsing surface with ``_paths.try_read_integration_json`` while
  keeping loud per-cause messaging on top.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread src/specify_cli/__init__.py Outdated
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 4, 2026

Please addres Copilot feedback. If not applicable, please explain why

Address Copilot feedback: UnicodeDecodeError can be raised by both
read_text() and json.loads(), so combining the handlers ensures both
cases produce a consistent, clear error message.
@Quratulain-bilal
Copy link
Copy Markdown
Contributor Author

@mnriem! Addressed Copilot feedback in 8c4488a combined the JSONDecodeError and
UnicodeDecodeError handlers since path.read_text(encoding="utf-8") can raise UnicodeDecodeError before
json.loads() runs, making the separate Unicode block unreachable. Single combined handler now covers

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment thread src/specify_cli/workflows/engine.py Outdated
Comment thread tests/test_workflows.py
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 5, 2026

Please address Copilot feedback

… in 'integration: auto'

Three Copilot follow-ups on PR github#2421:

1. engine.py:799 — `_load_project_integration` was bypassing the same
   schema guard `_read_integration_json` enforces. It now reads the
   schema field directly, returns None on a future schema (so the
   workflow falls back to the literal 'auto' default rather than
   guessing), and routes through `normalize_integration_state` /
   `default_integration_key` so modern installs that record
   `default_integration` / `installed_integrations` (without the
   legacy top-level `integration` field) resolve correctly.

2. test_workflows.py — added two regression cases:
   - `integration: auto` resolves a modern normalized state file
   - `integration: auto` falls back when the state file declares a
     newer `integration_state_schema` than this CLI supports

3. test_cli.py — added a CLI-level regression for the `UnicodeDecodeError`
   branch in `_read_integration_json` to match the existing
   malformed-JSON coverage.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 2

Comment thread workflows/speckit/workflow.yml
Comment thread src/specify_cli/workflows/engine.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 4

Comment on lines +10 to +12
# 0.8.3 is the first release with engine-side resolution of the
# ``integration: "auto"`` default. Older versions would treat "auto"
# as a literal integration key and fail at dispatch.
Comment on lines +15 to +20
# The four commands below (specify, plan, tasks, implement) are core
# spec-kit commands provided by every integration. The list here is an
# explicit non-exhaustive compatibility hint following the documented
# ``any: [...]`` schema; the workflow is intended to run against any
# integration the project was initialized with, including ones not
# listed below.
Comment on lines +152 to +175
# Validate the default eagerly so authoring mistakes (e.g. a
# default not in the declared enum, or a non-numeric default for
# a number input) surface at install/validation time instead of
# at workflow-execution time. ``"auto"`` for the integration
# input is a runtime-resolved sentinel, so only the
# enum-membership check is exempted for that exact case — the
# declared type is still enforced (e.g. ``type: number`` paired
# with ``default: "auto"`` is still rejected).
if "default" in input_def:
default_value = input_def["default"]
is_auto_integration = (
input_name == "integration" and default_value == "auto"
)
validation_input_def: dict[str, Any] = input_def
if is_auto_integration and "enum" in input_def:
validation_input_def = {
key: value
for key, value in input_def.items()
if key != "enum"
}
try:
WorkflowEngine._coerce_input(
input_name, default_value, validation_input_def
)
Comment thread src/specify_cli/workflows/engine.py Outdated
Comment on lines +792 to +809
def _load_project_integration(self) -> str | None:
"""Read the default integration key from ``.specify/integration.json``.

Honors the same schema guard as ``_read_integration_json`` (rejects
files whose ``integration_state_schema`` is newer than this CLI
supports) and reads the canonical normalized state, so modern
installs that record ``default_integration`` / ``installed_integrations``
resolve correctly under ``integration: auto``. Returns None when the
file is missing, malformed, or written by a newer CLI; callers are
expected to fall back to a literal default.
"""
path = self.project_root / INTEGRATION_JSON
if not path.is_file():
return None
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError, UnicodeDecodeError):
return None
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Address Copilot review on PR github#2421:

Both `_read_integration_json` (CLI) and `_load_project_integration`
(workflow engine) were parsing `.specify/integration.json` independently,
duplicating the schema guard and risking drift between the two readers.

Extract the parse + schema validation into a single low-level helper
`try_read_integration_json` in `integration_state.py` that returns either
the normalized state or a structured `IntegrationReadError`. Both callers
now delegate to this helper:

- CLI keeps its loud-fail UX: each error kind ("decode", "os",
  "not_object", "schema_too_new") is translated into the existing console
  message + typer.Exit(1).
- Engine keeps its silent fallback: any error simply returns None so
  `integration: auto` falls back to the workflow's literal default.

This eliminates the divergence Copilot flagged without changing observable
behavior for either caller.
@mnriem mnriem requested a review from Copilot May 12, 2026 20:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 3

Comment on lines +10 to +12
# 0.8.3 is the first release with engine-side resolution of the
# ``integration: "auto"`` default. Older versions would treat "auto"
# as a literal integration key and fail at dispatch.
Comment on lines +150 to +173
# Validate the default eagerly so authoring mistakes (e.g. a
# default not in the declared enum, or a non-numeric default for
# a number input) surface at install/validation time instead of
# at workflow-execution time. ``"auto"`` for the integration
# input is a runtime-resolved sentinel, so only the
# enum-membership check is exempted for that exact case — the
# declared type is still enforced (e.g. ``type: number`` paired
# with ``default: "auto"`` is still rejected).
if "default" in input_def:
default_value = input_def["default"]
is_auto_integration = (
input_name == "integration" and default_value == "auto"
)
validation_input_def: dict[str, Any] = input_def
if is_auto_integration and "enum" in input_def:
validation_input_def = {
key: value
for key, value in input_def.items()
if key != "enum"
}
try:
WorkflowEngine._coerce_input(
input_name, default_value, validation_input_def
)
Comment on lines +28 to +42
def try_read_integration_json(
project_root: Path,
) -> tuple[dict[str, Any] | None, IntegrationReadError | None]:
"""Parse ``.specify/integration.json`` without raising.

Returns ``(normalized_state, None)`` on success, ``(None, None)`` when the
file does not exist, or ``(None, error)`` for any parse / validation
failure. This is the single low-level reader; both the CLI's loud
``_read_integration_json`` and the workflow engine's silent
``_load_project_integration`` consume it so the schema guard and parse
logic cannot drift between them.
"""
path = project_root / INTEGRATION_JSON
if not path.is_file():
return None, None
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right that the description was stale. The single source of truth for INTEGRATION_JSON and
try_read_integration_json is src/specify_cli/integration_state.py (which has been the canonical home for
integration-state helpers since write_integration_json, normalize_integration_state, etc. were extracted earlier).
There is no _paths.py; I've updated the PR description to reflect that. Thanks!

@Quratulain-bilal Quratulain-bilal changed the title fix(workflow): support integration: auto to follow project's initialized AI fix(workflow): support integration: auto to follow project's initialized AI May 12, 2026
@mnriem mnriem requested a review from Copilot May 12, 2026 20:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 1

Comment thread src/specify_cli/integration_state.py Outdated
Comment on lines +41 to +42
if not path.is_file():
return None, None
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed f136b7b which splits the check: a truly missing file still returns (None, None) for the engine's
silent fallback, but a path that exists but isn't a regular file now returns an IntegrationReadError so the CLI surfaces
the misconfiguration loudly. Tests pass (206 passed, 1 skipped). Thanks!

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 12, 2026

Please address Copilot feedback

Address Copilot review on PR github#2421:

`try_read_integration_json` was collapsing two distinct cases into a
single `(None, None)` return:

1. `.specify/integration.json` truly missing — silent fallback is correct.
2. Path exists but is a directory, socket, or other non-regular file —
   this is a misconfiguration the CLI should surface loudly.

Split the check: `exists()` falsey returns `(None, None)`; existing-but-
not-a-regular-file returns `(None, IntegrationReadError(kind="os", ...))`
so the CLI's loud-fail path produces an actionable error while the
engine still treats it as a fallback to the workflow's literal default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants