This document describes how the tikalk fork (tikalk/agentic-sdlc-spec-kit) maintains customizations while staying synced with upstream github/spec-kit.
The fork isolates all customizations into a single file (cli_customization.py) to minimize merge conflicts. When syncing with upstream:
- Only the import block in
__init__.pymay conflict - All customization logic lives in
cli_customization.py - Upstream changes to other parts of
__init__.pymerge cleanly
The fork uses a suffix-based versioning system to track both upstream and fork-specific changes:
<upstream-version>+adlc<N>
Examples:
0.8.2+adlc1- Fork based on upstream 0.8.2, first fork release0.8.2+adlc2- Same upstream base, second fork release with new features0.9.0+adlc1- After upstream merge to 0.9.0, reset fork counter
| Scenario | Version Change | Example |
|---|---|---|
| Fork adds new feature | Increment adlc suffix | 0.8.2+adlc1 → 0.8.2+adlc2 |
| Merge upstream release | Update base, reset suffix | 0.8.2+adlc5 → 0.9.0+adlc1 |
| Hotfix/patch | Increment adlc suffix | 0.8.2+adlc1 → 0.8.2+adlc2 |
Use agentic-sdlc-v<version> with dashes instead of plus:
- Version:
0.8.2+adlc2 - Tag:
agentic-sdlc-v0.8.2-adlc2
File: src/specify_cli/cli_customization.py
This module provides:
| Feature | Upstream Default | Fork Override |
|---|---|---|
| ACCENT_COLOR | "cyan" | "#f47721" (tikalk orange) |
| BANNER_COLORS | cyan gradient | orange gradient |
| accent() | N/A | Helper function for theming |
| accent_style() | N/A | Helper for Rich style= params |
| PKG_NAMES | ("specify-cli",) | ("agentic-sdlc-specify-cli", "specify-cli") |
| TEAM_DIRECTIVES_DIRNAME | N/A | "team-ai-directives" |
| EXTENSION_NAMESPACES | ["speckit"] | ["speckit", "adlc"] |
| EXTENSION_ALIAS_PATTERN_ENABLED | False | True |
The __init__.py file starts with this import block that you should preserve during merges:
# Tikalk fork customizations - import with fallback to upstream defaults
try:
from .cli_customization import (
ACCENT_COLOR,
BANNER_COLORS,
TAGLINE,
accent,
accent_style,
TEAM_DIRECTIVES_DIRNAME,
PKG_NAMES,
pre_init,
post_init,
skill_app,
compute_skill_output_name,
)
except ImportError:
ACCENT_COLOR = "cyan"
BANNER_COLORS = ["#00ffff", "#00cccc", "cyan", "#009999", "white", "bright_white"]
TEAM_DIRECTIVES_DIRNAME = "team-ai-directives"
PKG_NAMES = ["specify-cli"]
skill_app = None
def accent(
text: str, bold: bool = False, italic: bool = False, dim: bool = False
) -> str:
style = ACCENT_COLOR
if bold:
style = f"bold {style}"
if italic:
style = f"italic {style}"
if dim:
style = f"dim {style}"
return f"[{style}]{text}[/]"
def accent_style() -> str:
return ACCENT_COLOR
def pre_init(project_path, selected_ai, team_ai_directives, tracker=None):
pass
def post_init(project_path, selected_ai, tracker=None, no_git=False):
pass
def compute_skill_output_name():
return NoneCritical: This block must include ALL fork customizations with fallbacks, not just partial imports. The import block is the SINGLE SOURCE OF TRUTH for fork customizations - all theming, hooks (pre_init, post_init), and helper functions must be defined here.
In extensions.py, the fork configures command name patterns:
# Get namespaces from customization module (supports speckit and adlc)
try:
from .cli_customization import EXTENSION_NAMESPACES, EXTENSION_ALIAS_PATTERN_ENABLED
except ImportError:
EXTENSION_NAMESPACES = ["speckit"]
EXTENSION_ALIAS_PATTERN_ENABLED = False
EXTENSION_COMMAND_NAME_PATTERN = re.compile(
rf"^(?:{'|' . join(EXTENSION_NAMESPACES)})\.([a-z0-9-]+)\.([a-z0-9-]+)$"
)
if EXTENSION_ALIAS_PATTERN_ENABLED:
EXTENSION_ALIAS_NAME_PATTERN = re.compile(r"^([a-z0-9-]+)\.([a-z0-9-]+)$")
else:
EXTENSION_ALIAS_NAME_PATTERN = NoneThis section documents the complete workflow for merging upstream changes from github/spec-kit.
# Fetch latest upstream changes
git fetch upstream
# Check what commits are new in upstream (not in origin)
git log origin/main --not upstream/main
git log upstream/main --not origin/maingit checkout main
git branch backup-before-upstream-merge-$(date +%Y%m%d)git fetch upstream
git merge upstream/mainCRITICAL: Never use git checkout --theirs for init.py or pyproject.toml
Correct Strategy: Use upstream as clean base, then ADD fork customizations on top.
-
For
__init__.py: Merge upstream cleanly (no --theirs). Then manually add fork functions AFTER merge:TEAM_DIRECTIVES_DIRNAME = "team-ai-directives"_run_git_command()sync_team_ai_directives()compute_skill_output_name()(delegates to cli_customization)TAGLINE(fork version)
-
For
pyproject.toml: NEVER use--theirs. The file will be in conflict - manually edit to preserve fork values:name = "agentic-sdlc-specify-cli"version = "0.5.X"(fork version)description(fork description)- Wheel paths: update to root directories (see below)
- Add
httpx>=0.27.0dependency
-
For test files: Accept upstream versions to avoid massive diffs:
git checkout --theirs tests/
Why this works:
- No merge conflicts in init.py because we're using upstream as base
- Fork functions are small additions (~50 lines) - easy to add from backup
- pyproject.toml needs manual edit to preserve fork version (never use --theirs)
python3 -m pytest tests/ -vUpdate pyproject.toml:
version = "0.3.X" # Increment from current versionAdd new entry at top of changelog (after ## [Unreleased] or before latest release):
## [0.3.X] - YYYY-MM-DD
### Changed
- **Upstream merge**: Synced with github/spec-kit
- [List upstream commits merged]git add -A
git commit -m "chore: merge upstream and bump to v0.3.X"
git push origin maingit tag -a agentic-sdlc-v0.3.X -m "Release agentic-sdlc-v0.3.X"
git push origin agentic-sdlc-v0.3.XWhen conflicts occur during merge:
- Keep origin changes as base - Our customizations in
cli_customization.pyand fork-specific features must be preserved - Adapt upstream changes - Integrate upstream improvements to work with our customizations
- Test after resolving - Always run tests before committing
| File | Why It Conflicts | Resolution |
|---|---|---|
__init__.py |
Import block at top | Use our try/except import pattern with fallback |
pyproject.toml |
Version number | Increment version after merge |
CHANGELOG.md |
New entries | Add fork-specific entries, preserve upstream section |
These fork customizations should NEVER be modified unless intentionally updating them:
cli_customization.py- All theming and customization constantsextensions.py- Extension namespace configuration- Bundled extensions in
pyproject.toml- levelup, architect, quick, product, tdd - Bundled presets in
pyproject.toml- agentic-sdlc
The following customization categories must stay in cli_customization.py:
- Theming: ACCENT_COLOR, BANNER_COLORS, accent(), accent_style()
- Package Identity: PKG_NAMES for version detection
- Team Directives: TEAM_DIRECTIVES_DIRNAME
- Extension Namespaces: EXTENSION_NAMESPACES, EXTENSION_ALIAS_PATTERN_ENABLED
The following tikalk-specific features are implemented directly in __init__.py because they require access to internal functions:
install_bundled_extensions()- Installs bundled extensions during initinstall_bundled_presets()- Installs bundled presets during initget_preinstalled_extensions()- Returns list of pre-installed extensionssync_team_ai_directives()- Syncs team-ai-directives repository
These functions are called during init but don't conflict with upstream because they use conditional checks (e.g., if skip_bundled:).
Run tests to verify customization module works correctly:
python3 -c "
from src.specify_cli import ACCENT_COLOR, BANNER_COLORS, accent, accent_style
from src.specify_cli.extensions import EXTENSION_COMMAND_NAME_PATTERN
print('ACCENT_COLOR:', ACCENT_COLOR)
print('accent test:', accent('Test', bold=True))
print('Extension pattern:', EXTENSION_COMMAND_NAME_PATTERN.pattern)
"Expected output:
ACCENT_COLOR: #f47721
accent test: [bold #f47721]Test[/]
Extension pattern: ^(?:speckit|adlc)\.([a-z0-9-]+)\.([a-z0-9-]+)$
IMPORTANT: The fork uses the tag pattern agentic-sdlc-v* to distinguish from upstream v* tags.
Upstream uses tags like v0.5.0, v0.5.1, etc. These tags from origin trigger GitHub Actions workflows designed for upstream, which causes confusion and wasted CI runs.
- Fork tags:
agentic-sdlc-v0.3.11(our releases) - Upstream tags:
v0.5.0(from upstream/main, don't push these to origin)
Always check before pushing to avoid triggering upstream workflows:
# Check what tags you're about to push
git push origin --dry-run --tags
# List only fork-pattern tags
git tag -l "agentic-sdlc-*"If upstream tags get pushed to origin by mistake, remove them:
# Local cleanup
git tag -l "v*" | xargs -I {} git tag -d {}
# Remote cleanup (example for v0.5.0)
git push origin --delete v0.5.0Or delete multiple at once:
git push origin --delete v0.0.1 v0.1.0 v0.2.0 v0.5.0This section documents common CI failures and how to debug them.
Symptom: ruff fails with invalid-syntax: Expected ,, found <<
Example error:
invalid-syntax: Expected `,`, found `<<`
--> src/specify_cli/__init__.py:1064:1
|
1064 | <<<<<<< HEAD
| ^^
Cause: Merge conflict not fully resolved - <<<<<<< HEAD markers remain in code
Fix:
# Find remaining conflict markers
grep -rn "<<<<<< HEAD" src/
# Fix in editor (remove the marker line)
# Verify fix
uvx ruff check src/
# Commit and push
git add -A
git commit -m "fix: remove unresolved merge conflict marker"
git push origin mainPrevention: Always run uvx ruff check src/ locally before pushing
Symptom: pytest fails after upstream merge
Fix:
# Run tests locally
uv run pytest
# If tests fail, investigate and fix
# Then commit and pushSymptom: Tests fail on specific Python versions in CI matrix
Fix: Ensure code is compatible with Python 3.11, 3.12, and 3.13
- Check the workflow run: Look at the GitHub Actions logs for the specific error
- Reproduce locally: Run the same commands locally:
uvx ruff check src/ uv run pytest
- Check for conflicts:
grep -rn "<<<<<<" src/ - Review recent changes:
git log --oneline -10
This section documents hard-won lessons from merging upstream changes.
-
NEVER use
git checkout --theirsfor pyproject.toml or init.py- Using
--theirson pyproject.toml discards the fork version entirely - The stash with fork changes gets lost
- Always manually edit to preserve fork values after merge
- Using
-
Use upstream as clean base, then ADD fork customizations
- Merge upstream/main cleanly first
- Then manually add fork-specific functions/values AFTER merge
- This prevents accidental overwriting
-
The import block is the SINGLE SOURCE OF TRUTH
- All fork customizations must be in the try/except import block
- Both the imports AND the fallback functions must be complete
- Missing fallbacks = runtime errors when cli_customization.py is not available
The fork uses specify_cli/core_pack/... paths (NOT root-level directories):
[tool.hatch.build.targets.wheel.force-include]
# Tikalk bundled extensions
"extensions/levelup" = "specify_cli/core_pack/extensions/levelup"
"extensions/evals" = "specify_cli/core_pack/extensions/evals"
"extensions/architect" = "specify_cli/core_pack/extensions/architect"
"extensions/quick" = "specify_cli/core_pack/extensions/quick"
"extensions/product" = "specify_cli/core_pack/extensions/product"
"extensions/tdd" = "specify_cli/core_pack/extensions/tdd" # Don't forget!
# Tikalk bundled presets
"presets/agentic-sdlc" = "specify_cli/bundled_presets/agentic-sdlc"
# Core pack assets
"templates/agent-file-template.md" = "specify_cli/core_pack/templates/agent-file-template.md"
# ... etcCommon mistake: Using root-level paths like "extensions/levelup" = "extensions/levelup" will cause build errors because those paths don't exist inside the wheel.
After merging upstream, ensure these are present in init.py:
- Full import block (see above) - with ALL imports and fallbacks
- TEAM_DIRECTIVES_DIRNAME - for team-ai-directives feature
- _run_git_command() - helper for git operations
- sync_team_ai_directives() - clones/updates team repo
- compute_skill_output_name() - delegates to cli_customization
- TAGLINE - fork's tagline (different from upstream)
Apply theming to all UI elements using accent_style() and accent():
show_banner(): UseBANNER_COLORSandaccent_style()for tagline- StepTracker title:
f"[{accent_style()}]{self.title}" - "Selected AI assistant:" and "Selected script type:": Use
accent() - "Project ready.": Use
accent(bold=True) - All Panel borders: Use
border_style=accent_style() - Next Steps and Enhancement Commands panels
The init flow must include these steps in order:
for key, label in [
("chmod", "Ensure scripts executable"),
("constitution", "Constitution setup"),
("git", "Install git extension"),
("workflow", "Install bundled workflow"),
("team-directives", "Team AI Directives setup"),
("extensions", "Install bundled extensions"),
("presets", "Install bundled presets"),
]:
tracker.add(key, label)
# Final MUST be added LAST
tracker.add("final", "Finalize")The fork uses /spec.* prefix instead of upstream's /speckit.*:
def _display_cmd(name: str) -> str:
# ... agent-specific cases ...
# Fork default: use "spec." prefix instead of "speckit."
return f"/spec.{name}"Always create a backup branch BEFORE merging:
git branch backup-before-upstream-merge-$(date +%Y%m%d-%H%M%S)This allows you to:
- Compare against backup to see exactly what changed
- Restore any accidentally lost customizations
- Debug issues by comparing working vs broken state
The fork bundles extensions, presets, and skills that aren't present in upstream. This causes the test_complete_file_inventory_* tests to fail because they expect upstream's file count.
Solution: Skip these tests on the fork by checking PKG_NAMES:
def test_complete_file_inventory_sh(self, tmp_path):
"""Every file produced by specify init --integration <key> --script sh."""
from specify_cli import PKG_NAMES
if any("agentic-sdlc" in pkg for pkg in PKG_NAMES):
import pytest
pytest.skip("Fork has bundled extensions/presets with different file counts")
# ... rest of testThis check exists in:
tests/integrations/test_integration_base_markdown.pytests/integrations/test_integration_base_skills.py
The tests pass upstream (no skip) and skip on the fork (with skip), ensuring CI passes in both environments.