feat: add deprecation warnings for non-create/non-design CLI modes - #1069
feat: add deprecation warnings for non-create/non-design CLI modes#1069osilkin98 wants to merge 3 commits into
Conversation
Add DEPRECATED_MODES frozenset and warn_deprecated_mode() to emit structlog events and stderr warnings when users invoke deprecated --mode values. Only 9 core factory-shipped modes are deprecated (build, improve, research, meta, discover, review, refine, parallel-improve, interactive). Community modes (qa, deep-qa, swebench, etc.) and internal routing modes (auto, auto-fresh, skill-refine, etc.) are excluded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflict in factory/cli/_main.py --mode help text: keep our deprecation-focused wording while incorporating main's 'creating or updating factory modes' detail for the create mode mention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sentrux Quality ReportAbsoluteDiff (vs base branch) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1069 +/- ##
==========================================
- Coverage 88.36% 88.36% -0.01%
==========================================
Files 127 127
Lines 14864 14898 +34
Branches 2347 2351 +4
==========================================
+ Hits 13134 13164 +30
- Misses 1234 1236 +2
- Partials 496 498 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@ceo-review |
There was a problem hiding this comment.
✅ Factory Review: KEEP
Verdict: KEEP
Reason: QA: CLEAN — 3826 tests pass, lint/type-check clean, composite 0.966, code review 7/7 PASS, adversarial QA 10/10 verified. 2 minor observations (unreachable refine in frozenset, misleading run help text) — no blocking issues.
QA Analysis
Adversarial QA Report — PR #1069: Deprecation Warnings for CLI Modes
Date: 2026-07-28
Project type: CLI (Python)
Verdict: PASS
Smoke Test
No project-level smoke test defined in factory.md. Proceeded directly to feature tests.
Test 1: Run existing deprecation test suite
Criterion: All 20 unit tests in tests/test_deprecation.py pass.
Status: VERIFIED
Command:
uv run pytest tests/test_deprecation.py -v
Output:
tests/test_deprecation.py::test_deprecated_modes_exact_set PASSED [ 5%]
tests/test_deprecation.py::test_deprecated_modes_subset_of_known_modes PASSED [ 10%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_deprecated_mode_emits_structlog PASSED [ 15%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_deprecated_mode_prints_stderr PASSED [ 20%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_interactive_has_alias_note PASSED [ 25%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_create_not_deprecated PASSED [ 30%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_design_not_deprecated PASSED [ 35%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_auto_not_deprecated PASSED [ 40%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_swebench_not_deprecated PASSED [ 45%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_qa_not_deprecated PASSED [ 50%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_deep_qa_not_deprecated PASSED [ 55%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[build] PASSED [ 60%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[discover] PASSED [ 65%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[improve] PASSED [ 70%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[interactive] PASSED [ 75%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[meta] PASSED [ 80%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[parallel-improve] PASSED [ 85%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[refine] PASSED [ 90%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[research] PASSED [ 95%]
tests/test_deprecation.py::TestWarnDeprecatedMode::test_all_deprecated_modes_warn[review] PASSED [100%]
20 passed in 0.10s
Test 2: All deprecated modes produce stderr warnings
Criterion: Each of the 9 deprecated modes emits a WARNING to stderr with the correct mode name, mentions --mode design, and notes it "remains functional."
Status: VERIFIED
Command:
uv run python -c "
import sys, io
from unittest.mock import patch
from factory.cli._helpers import warn_deprecated_mode, DEPRECATED_MODES
for mode in sorted(DEPRECATED_MODES):
old_stderr = sys.stderr
sys.stderr = captured = io.StringIO()
with patch('factory.cli._helpers.log'):
warn_deprecated_mode(mode)
sys.stderr = old_stderr
output = captured.getvalue()
has_warning = f'--mode {mode} is deprecated' in output
has_design = '--mode design instead' in output
has_functional = 'remains functional' in output
print(f' {mode}: WARNING={has_warning}, DESIGN={has_design}, FUNCTIONAL={has_functional}')
"Output:
build: WARNING=True, DESIGN=True, FUNCTIONAL=True
discover: WARNING=True, DESIGN=True, FUNCTIONAL=True
improve: WARNING=True, DESIGN=True, FUNCTIONAL=True
interactive: WARNING=True, DESIGN=True, FUNCTIONAL=True
meta: WARNING=True, DESIGN=True, FUNCTIONAL=True
parallel-improve: WARNING=True, DESIGN=True, FUNCTIONAL=True
refine: WARNING=True, DESIGN=True, FUNCTIONAL=True
research: WARNING=True, DESIGN=True, FUNCTIONAL=True
review: WARNING=True, DESIGN=True, FUNCTIONAL=True
Test 3: Non-deprecated modes produce NO warning
Criterion: Modes create, design, auto, swebench, qa, deep-qa produce no stderr and no structlog warning.
Status: VERIFIED
Command:
uv run python -c "
import sys, io
from unittest.mock import patch
from factory.cli._helpers import warn_deprecated_mode
for mode in ['create', 'design', 'auto', 'swebench', 'qa', 'deep-qa']:
old_stderr = sys.stderr
sys.stderr = captured = io.StringIO()
with patch('factory.cli._helpers.log') as mock_log:
warn_deprecated_mode(mode)
sys.stderr = old_stderr
output = captured.getvalue()
log_called = mock_log.warning.called
print(f' {mode}: stderr_empty={output == \"\"}, log_silent={not log_called}')
"Output:
create: stderr_empty=True, log_silent=True
design: stderr_empty=True, log_silent=True
auto: stderr_empty=True, log_silent=True
swebench: stderr_empty=True, log_silent=True
qa: stderr_empty=True, log_silent=True
deep-qa: stderr_empty=True, log_silent=True
Test 4: Interactive mode alias note
Criterion: The interactive mode warning includes the text "alias for 'design'", while other deprecated modes do not.
Status: VERIFIED
Command:
uv run python -c "
import sys, io
from unittest.mock import patch
from factory.cli._helpers import warn_deprecated_mode
# interactive
old_stderr = sys.stderr
sys.stderr = captured = io.StringIO()
with patch('factory.cli._helpers.log'):
warn_deprecated_mode('interactive')
sys.stderr = old_stderr
print(repr(captured.getvalue()))
# build (should NOT have alias)
old_stderr = sys.stderr
sys.stderr = captured = io.StringIO()
with patch('factory.cli._helpers.log'):
warn_deprecated_mode('build')
sys.stderr = old_stderr
print(repr(captured.getvalue()))
"Output:
"WARNING: --mode interactive is deprecated ('interactive' is an alias for 'design'). Use --mode design instead. This mode remains functional but will be removed in a future release.\n"
'WARNING: --mode build is deprecated. Use --mode design instead. This mode remains functional but will be removed in a future release.\n'
Interactive mode has the alias note; build mode does not. Correct.
Test 5: Warning message includes correct mode name and mentions --mode design
Criterion: Each deprecated mode's warning text contains --mode <MODE_NAME> is deprecated and --mode design instead.
Status: VERIFIED
Command:
uv run python -c "
import sys, io
from unittest.mock import patch
from factory.cli._helpers import warn_deprecated_mode
for mode in ['build', 'improve', 'research', 'meta', 'discover', 'review', 'refine', 'parallel-improve', 'interactive']:
old_stderr = sys.stderr
sys.stderr = captured = io.StringIO()
with patch('factory.cli._helpers.log'):
warn_deprecated_mode(mode)
sys.stderr = old_stderr
output = captured.getvalue()
correct_name = f'--mode {mode} is deprecated' in output
mentions_design = '--mode design instead' in output
print(f' {mode}: correct_name={correct_name}, mentions_design={mentions_design}')
"Output:
build: correct_name=True, mentions_design=True
improve: correct_name=True, mentions_design=True
research: correct_name=True, mentions_design=True
meta: correct_name=True, mentions_design=True
discover: correct_name=True, mentions_design=True
review: correct_name=True, mentions_design=True
refine: correct_name=True, mentions_design=True
parallel-improve: correct_name=True, mentions_design=True
interactive: correct_name=True, mentions_design=True
Test 6: Edge cases (empty string, None, unknown mode)
Criterion: The function handles edge cases gracefully — no crash, no spurious warnings.
Status: VERIFIED
Command:
uv run python -c "
import sys, io
from unittest.mock import patch
from factory.cli._helpers import warn_deprecated_mode
for label, val in [('empty string', ''), ('None', None), ('unknown', 'nonexistent-mode')]:
old_stderr = sys.stderr
sys.stderr = captured = io.StringIO()
with patch('factory.cli._helpers.log') as mock_log:
warn_deprecated_mode(val)
sys.stderr = old_stderr
output = captured.getvalue()
log_called = mock_log.warning.called
print(f' {label}: stderr_empty={output == \"\"}, log_silent={not log_called}')
"Output:
empty string: stderr_empty=True, log_silent=True
None: stderr_empty=True, log_silent=True
unknown: stderr_empty=True, log_silent=True
All edge cases handled cleanly — no crash, no output.
Test 7: DEPRECATED_MODES set integrity
Criterion: The set contains exactly 9 modes, is a frozenset (immutable), and create/design are not in it.
Status: VERIFIED
Command:
uv run python -c "
from factory.cli._helpers import DEPRECATED_MODES
print(f'Count: {len(DEPRECATED_MODES)}')
print(f'Type: {type(DEPRECATED_MODES).__name__}')
print(f'Members: {sorted(DEPRECATED_MODES)}')
print(f'create in set: {\"create\" in DEPRECATED_MODES}')
print(f'design in set: {\"design\" in DEPRECATED_MODES}')
try:
DEPRECATED_MODES.add('test')
print('Mutable: True (BAD)')
except AttributeError:
print('Immutable: True (GOOD)')
"Output:
Count: 9
Type: frozenset
Members: ['build', 'discover', 'improve', 'interactive', 'meta', 'parallel-improve', 'refine', 'research', 'review']
create in set: False
design in set: False
Immutable: True (GOOD)
Test 8: Integration points verified
Criterion: warn_deprecated_mode is called in cmd_ceo and cmd_run before mode dispatch.
Status: VERIFIED
Evidence: grep -n "warn_deprecated_mode" factory/cli/ceo.py shows:
- Line 33: imported from
_helpers - Line 71: called with
getattr(args, "mode", "auto")incmd_ceo - Line 2172: called with
modeincmd_run
Help text updated at lines 706 and 851 of factory/cli/ceo.py to mention deprecation.
Test 9: No regressions in existing test suite
Criterion: Core test files pass without failures.
Status: VERIFIED
Command:
uv run pytest tests/test_deprecation.py tests/test_models.py tests/test_cli.py tests/test_analysis.py tests/test_state.py -v --tb=short
Output (summary):
403 passed, 3 warnings in 164.69s
All 403 tests pass. The 3 warnings are pre-existing RuntimeWarning about unawaited coroutines in mock objects — unrelated to this PR.
Acceptance Criteria Verification
| # | Criterion | Status |
|---|---|---|
| 1 | DEPRECATED_MODES frozenset with 9 modes | VERIFIED |
| 2 | warn_deprecated_mode() prints to stderr | VERIFIED |
| 3 | warn_deprecated_mode() emits structlog event | VERIFIED |
| 4 | Calls in cmd_ceo() and cmd_run() before mode dispatch | VERIFIED |
| 5 | Non-deprecated modes (create, design, auto, swebench, qa, deep-qa) produce no warning | VERIFIED |
| 6 | Interactive mode includes alias note | VERIFIED |
| 7 | Warning includes correct mode name and mentions --mode design | VERIFIED |
| 8 | Edge cases handled (empty string, None, unknown mode) | VERIFIED |
| 9 | Updated help text mentions deprecation | VERIFIED |
| 10 | No regressions in existing tests | VERIFIED |
Adversarial Verdict: PASS
All acceptance criteria verified with evidence. The deprecation warning system works correctly: deprecated modes produce clear stderr warnings with the right mode name and replacement suggestion, non-deprecated modes are silent, edge cases are handled gracefully, and the existing test suite shows no regressions.
Posted by Factory CEO
The welcome wizard is deprecated in favor of direct CLI usage. When invoked, it now prints a stderr warning directing users to 'factory ceo --mode design <path>' or 'factory ceo --mode create <idea>'. The wizard remains fully functional after the warning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the deprecation warning task from the design workflow.
Changes
factory/cli/_helpers.py: AddedDEPRECATED_MODESfrozenset (9 modes: build, improve, research, meta, discover, review, refine, parallel-improve, interactive) andwarn_deprecated_mode()function that emits a structlogdeprecated_cli_modeevent and prints a stderr warning with migration guidancefactory/cli/ceo.py: Callswarn_deprecated_mode()in bothcmd_ceo()(after interactive→design alias mapping) andcmd_run()(after mode resolution from args), before mode-specific dispatchfactory/cli/_main.py: Updated--modehelp text for bothfactory ceoandfactory runto note deprecated modestests/test_deprecation.py: 20 tests covering: exact deprecated set membership, structlog event emission, stderr output, alias note for interactive, and verification that create/design/auto/qa/swebench are NOT deprecatedExcluded from deprecation (per user scope correction):