Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e85d4f
fix: skip corrupted run state files in list_runs (#3814)
Quratulain-bilal Aug 5, 2026
0ecb277
fix: skip corrupted run state files in list_runs (#3817)
Quratulain-bilal Aug 5, 2026
4d5458c
fix: keep long frontmatter values on a single line (#3989)
deborre Aug 5, 2026
6fa8c9a
chore: release 0.16.0, begin 0.16.1.dev0 development (#3992)
mnriem Aug 5, 2026
f01cac6
fix(scripts): stop setup-tasks text mode crashing on a legacy code pa…
jawwad-ali Aug 5, 2026
71125fc
test(integrations): guard multiline/control-char SKILL.md frontmatter…
Quratulain-bilal Aug 5, 2026
e9710ae
fix(archives): wrap the bare EOFError a truncated tar.gz raises (#3938)
Noor-ul-ain001 Aug 5, 2026
f8a448f
fix(skills): apply the line-anchored delimiter scan to hermes and kim…
Quratulain-bilal Aug 5, 2026
f31b2b4
Fix init-force-preset-desync: reapply presets/extensions on init --he…
github-actions[bot] Aug 5, 2026
3d4f71c
fix(extensions): start fresh on a non-UTF-8 extension registry (#3998)
Noor-ul-ain001 Aug 6, 2026
fe3732e
fix(presets): return None for an unreadable layer in resolve_content …
marcelsafin Aug 6, 2026
3dff6f1
fix(scripts): stop check-prerequisites text mode crashing on a legacy…
jawwad-ali Aug 6, 2026
40037b1
feat(init): scaffold managed .specify/.gitignore (#4000)
mnriem Aug 6, 2026
204d94f
fix(workflows): handle an unreadable run state in `workflow status` (…
Noor-ul-ain001 Aug 6, 2026
4a46543
fix: use missing_ok for temp file cleanup to avoid masking errors (#3…
Quratulain-bilal Aug 6, 2026
f71cfaf
fix: bound response read in integration catalog fetch (#3812)
Quratulain-bilal Aug 6, 2026
36a3355
fix(init): escape user-supplied values in `specify init` output (#3787)
jawwad-ali Aug 6, 2026
adb2413
fix: add utf-8 encoding to extension and preset registry file I/O (#3…
Quratulain-bilal Aug 6, 2026
2a7bd28
Merge upstream 0.16.0 into fork (base 0.15.2+adlc4 -> 0.16.0+adlc1)
kanfil Aug 6, 2026
21ccf0f
docs: add 0.16.0+adlc1 FORK.md entry and refresh AGENTS.md team-boot …
kanfil Aug 6, 2026
b8685f7
chore: stop preinstalling levelup extension (adlc-team-skills coexist…
kanfil Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,13 @@ context_markers:
## Team Directives & Constitution

This project is bound by the team-ai-directives knowledge base.
- **Strict Compliance**: You MUST check if a skill applies BEFORE responding to any task or question. If a skill applies, you MUST invoke it immediately.
- **Team Constitution**: Read and strictly adhere to the principles in `../agentic-sdlc-team-ai-directives/context_modules/constitution.md`. When creating or updating the project's constitution, you MUST inherit and align with these principles.
- **Strict Compliance**: You MUST invoke the `team-boot` skill BEFORE responding to any task or question. This is not optional and applies to every interaction. If a skill applies, you MUST invoke it immediately.
- **Anti-Patterns** (do NOT rationalize skipping the skill check):
- "Let me explore the codebase first" -> Skills tell you HOW to explore. Check first.
- "This is a plain message, not a spec command" -> Skill check applies to EVERY interaction.
- "I'll check after understanding the request" -> Check BEFORE understanding -- skills guide your questions.
- "This is too simple" -> Simple things become complex. Check first.
- **Team Constitution**: Read and strictly adhere to the principles in `./agentic-sdlc-team-ai-directives//context_modules/constitution.md`. When creating or updating the project's constitution, you MUST inherit and align with these principles.
- **Rules, Personas & Examples**: Use the `team-discover` skill to find and load relevant rules, personas, or examples before making changes.

For additional context about technologies to be used, project structure,
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

All notable changes to the Specify CLI and templates are documented here.

# [0.16.0+adlc2] - 2026-08-07

### Changed

- **levelup extension no longer preinstalled**: `extensions/catalog.json` now
sets `levelup.preinstall` to `false`. The `levelup` extension's commands
(`/levelup.init`, `/levelup.clarify`, `/levelup.specify`, `/levelup.skill`,
`/levelup.implement`, `/levelup.validate`) overlap with the
`adlc-team-skills` `levelup-*` skills/commands. Removing levelup from
auto-install eliminates the command redundancy when both toolkits are used
together (the recommended coexistence flow: install `adlc-team-skills` first,
run `team-setup`, then `specify init` without `--team-ai-directives`).
The extension remains bundled and installable on demand via
`specify extension install levelup`.

# [0.15.2+adlc4] - 2026-08-05

### Fixed
Expand Down Expand Up @@ -4299,6 +4314,31 @@ This release migrates fork-specific customizations to a preset system to reduce

The following entries are from the upstream spec-kit project and are included for reference.

## [0.16.0] - 2026-08-05

### Changed

- fix: keep long frontmatter values on a single line (#3989)
- fix: skip corrupted run state files in list_runs (#3817)
- fix: skip corrupted run state files in list_runs (#3814)
- Add July 2026 newsletter (#3987)
- fix(presets): start fresh on a non-UTF-8 preset registry (#3955)
- docs: clarify agent PR review prioritization (#3985)
- fix(events): preserve a non-UTF-8 config.toml on hook install/teardown (#3963)
- fix(extensions): treat an unreadable staged backup as a conflict (#3962)
- fix(manifests): reject non-string requires.speckit_version (#3980)
- fix(extensions): reject reinstall when a kept config cannot be read (#3960)
- [extension] Update Charter extension to v0.5.1 (#3983)
- fix(events): return None for an unparseable script command (#3957)
- feat(events): context injection for opencode and JSON-envelope agent hooks (#3934)
- Add TDD Extension to community catalog (#3982)
- Update Archive Extension to v1.1.0 (#3981)
- feat(copilot): default integration to skills (#3976)
- fix(events): ignore non-UTF-8 event overrides (#3897)
- fix: cap stdin read at 1 MiB to prevent DoS (#3857)
- fix(workflows): reject mismatched run state IDs (#3899)
- chore: release 0.15.2, begin 0.15.3.dev0 development (#3953)

## [0.15.2] - 2026-08-03

### Changed
Expand Down
1 change: 1 addition & 0 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ When a fork release changes only bundled extension behavior, keep the CLI versio

| Version | Date | Base Upstream | Changes |
|---------|------|---------------|---------|
| 0.16.0+adlc1 | 2026-08-06 | 0.16.0 (`adb2413a`) | Upstream merge (18 commits, post-0.15.2 → 0.16.0 release `6fa8c9aa`). New upstream feature: `feat(init): scaffold managed .specify/.gitignore` (#4000 — manifest-tracked `.specify/.gitignore` excludes `feature.json` + `extensions/*/local-config.yml`; routed through shared-infra overwrite/skip/preserve policy; 8 integration test inventories + new `test_shared_infra_gitignore.py`). New upstream fixes: escape user-supplied values in `specify init` output (#3787 — `_escape_markup` + `_shell_quote_arg` for the `cd` line, the only CLI module without escaping), reapply presets/extensions on `init --here --force` (#3995 — `_register_presets_for_agent`/`_register_extensions_for_agent` after `manifest.save()`), bound response read in integration catalog fetch (#3812 — `read_response_limited` + `MAX_JSON_METADATA_BYTES`), `missing_ok` temp-file cleanup (#3803), unreadable run state in `workflow status` (#3999), skip corrupted run state in `list_runs` (#3814/#3817), non-UTF-8 extension registry (#3998) + unreadable layer in `resolve_content` (#3959) + EOFError wrap for truncated tar.gz (#3938), line-anchored `---` delimiter scan for hermes+kimi (#3739), keep long frontmatter on single line (#3989 — `yaml.dump(width=float("inf"))`), legacy code-page fix for `check_prerequisites`/`setup_tasks` Python scripts (#3890/#3892). **4 conflicts resolved**: `pyproject.toml` (kept fork name/description, version → `0.16.0+adlc1`), `commands/init.py` (wrapped user-controlled display values with `_escape_markup` *inside* fork's `accent()` theming — project name, paths, echoed `--integration`/`--script`, agent folder in gitignore hint; adopted `_shell_quote_arg` for the Next Steps `cd` line; `f31b2b45` reapply-on-`--force` block auto-merged in un-themed region), `tests/integrations/test_integration_catalog.py` (adopted upstream's restructured file + bounded `FakeResponse`; **fixed mock target**: upstream's `_patch_urlopen` patched `urllib.request.urlopen` but `open_url` uses `opener.open()` which never calls module-level `urlopen` → tests hit real network; switched mock to `open_url` directly, preserving upstream's bounded-read `FakeResponse` for `read_response_limited` contract; applies to both `_patch_urlopen` and the oversized-response regression test), `tests/integrations/test_integration_base_toml.py` (added `.specify/.gitignore` to fork's `stem_pfx` inventory). All other conflict-candidate files auto-merged cleanly, preserving fork customizations: `shared_infra.py` (`.gitignore` block + `missing_ok` + `COMMAND_PREFIX`/`project_path`/theming), `extensions/__init__.py` (non-UTF-8 registry + catalog-URL override + alias logic), `hermes/__init__.py` (line-anchored delimiter + `resolve_command_alias`/`COMMAND_PREFIX`), `agents.py` (`width=float("inf")` + `_skip_primary`/`inject_model_invocation_flag`), `presets/__init__.py` (`resolve_content` guard + `_cleanup_replaced_commands`), `workflows/_commands.py` (unreadable-run-state guard + theming), `update_agent_context.py` (symlink-safe recursive plan discovery + `missing_ok` + fork team-directives block). Fork modules (`_*_fork.py`, `extensions_fork.py`) untouched. No `templates/` changes upstream → no preset porting. Ruff clean (`ruff@0.15.0`). 2629 tests pass across merge-affected files (110 catalog, 122 init/cli, 138 generic/cline/copilot, 2259 infra/workflow/preset/extension/parity). Live smoke: `specify init` scaffolds `.specify/.gitignore` (manifest-tracked, excludes `feature.json` + `extensions/*/local-config.yml`); `specify extension update` (after clearing `.specify/extensions/.cache/`) finds all fork-bundled extensions up-to-date via fork-repo catalog URL. |
| 0.15.2+adlc4 | 2026-08-05 | 0.15.2 (`68daed8f`) | Extension catalog URL override. `ExtensionCatalog.DEFAULT_CATALOG_URL` and `COMMUNITY_CATALOG_URL` now point at the fork's repo (`tikalk/agentic-sdlc-spec-kit`) instead of upstream (`github/spec-kit`). Previously `specify extension update` fetched upstream's catalog → fork-bundled extensions (`levelup`, `team-ai-directives`, `evals`, `edd`, `architect`) showed "Not found in catalog" and `tdd`/`product` showed "Updates not allowed from 'community'" (found in upstream's community catalog instead of the fork's bundled catalog). Fork constants `FORK_DEFAULT_CATALOG_URL`/`FORK_COMMUNITY_CATALOG_URL` added to `_core_fork.py`; override applied in `extensions/__init__.py` ExtensionCatalog class body (try/except with fallback to upstream URLs). 106 catalog tests pass. Live smoke: `specify extension update` now finds all 8 fork-bundled extensions as up-to-date. Note: stale catalog cache (from old upstream URL) may persist for up to 1 hour after upgrade; clear `.specify/extensions/.cache/` to force immediate refresh. |
| 0.15.2+adlc3 | 2026-08-05 | 0.15.2 (`68daed8f`) | assess/bug extension command-reference fix. `_build_preset_command_placeholder_map()` in `integrations/base.py` extended to scan `.specify/extensions/*/extension.yml` `provides.commands[].aliases` (previously only scanned preset manifests) — `__SPECKIT_COMMAND_ASSESS_*__`/`__SPECKIT_COMMAND_BUG_*__` placeholders now resolve to `/assess.*`/`/bug.*` instead of the broken prefix fallback `/spec.assess.*`. Aliases added to `assess` extension (1.0.0→1.0.1, 5 aliases: `assess.intake/research/define/shape/decide`) and `bug` extension (1.0.0→1.0.1, 3 aliases: `bug.assess/fix/test`); catalog.json versions bumped. With `EXTENSION_ALIAS_PATTERN_ENABLED`, files install as `assess.intake.md`/`bug.fix.md` (fork convention, matching git extension). Latent defect since `0.14.4+adlc1` (when assess/bug were bundled). Regression tests in `test_base.py` (placeholder map unit), `test_assess_extension.py` + `test_bug_extension.py` (alias declaration + rendering: no unresolved placeholders, refs match installed files). Existing `test_extension_command_dot/hyphen` tests updated to use clean tmp_path (no extensions) for fallback path. Ruff clean (`ruff@0.15.0`). 1189 tests pass. Live smoke: `specify init` + `extension add assess` → `.opencode/commands/assess.intake.md` with `/assess.research` refs and `/spec.specify` handoff. |
| 0.15.2+adlc2 | 2026-08-05 | 0.15.2 (`03d71b33`) | Upstream merge (13 commits, post-0.15.2, no new release tag). New upstream features: `feat(copilot): default integration to skills` (#3976 — Copilot defaults to skills layout, `--commands` opts back to `.agent.md`+`.prompt.md`; fork adapted `is_skills_mode()` to check both `spec-`/`speckit-` prefixes, re-applied 4 fork customizations onto upstream's rewritten module, fixed `build_command_invocation()` bare-name canonicalization); `feat(events): context injection for opencode and JSON-envelope agent hooks` (#3934, authored by fork maintainer upstream — auto-merged clean). Upstream fixes: non-UTF-8 preset registry (#3955), non-UTF-8 config.toml on hook install/teardown (#3963), unreadable staged backup as conflict (#3962), non-string requires.speckit_version (#3980), reinstall when kept config unreadable (#3960), None for unparseable script command (#3957), migration target-options validation. Community catalog: TDD extension (#3982 — fork has its own bundled tdd), Charter v0.5.1 (#3983), Archive v1.1.0 (#3981). **5 conflicts resolved**: `AGENTS.md` (adopted upstream's "Optional overrides" section + "Opening pull requests" PR-prioritization; preserved fork header/SPECKIT markers), `copilot/__init__.py` (re-applied fork customizations + prefix-aware `is_skills_mode()`), `test_cli.py` (skills-default assertions adapted to fork naming), `test_integration_copilot.py` (alias-aware `build_command_invocation` test assertions), `test_integration_subcommand.py` (copilot switch/upgrade tests adapted). All semantic hotspots (`events.py`, `extensions/__init__.py`, `presets/__init__.py`, `base.py`, `_migrate_commands.py`, `integration_runtime.py`) auto-merged cleanly; fork modules untouched. No `templates/` changes → no preset porting. Ruff clean (`ruff@0.15.0`). 1375+ tests pass across merge-affected files. |
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ specify init my-project --integration copilot --preset compliance

> **Two resolution axes.** `SPECIFY_INIT_DIR` selects the **project** (which directory contains `.specify/`); `SPECIFY_FEATURE_DIRECTORY` / `.specify/feature.json` select the **feature** within that project. They are independent — project first, then feature.

> **Version control.** `specify init` scaffolds a managed `.specify/.gitignore` that excludes machine-local state — `feature.json` (the current-feature pointer, rewritten on every feature switch) and per-machine extension `extensions/*/local-config.yml` overrides — while leaving everything else under `.specify/` (constitution, templates, scripts, extension config) shareable so teams stay aligned. Like the rest of `.specify/`'s shared scripts and templates, the file is tracked in the shared-infrastructure manifest: your edits are preserved on re-init and `specify init --here --force` restores the managed content. It is intentionally left in place by `specify integration uninstall`, which only removes the uninstalled agent's own files.

> **Symlinked project roots.** `SPECIFY_INIT_DIR` relocates *where* the project is, not *how* a command treats symlinks: each command keeps its existing cwd-path stance. Commands that traverse and write project files through broad input paths (`bundle`, `workflow run <file>`) refuse a symlinked `.specify/` to preserve write confinement. Other project-scoped commands keep their existing behavior when `SPECIFY_INIT_DIR` points at a project root, which may include following a symlinked `.specify/`.

## Check Installed Tools
Expand Down
37 changes: 26 additions & 11 deletions extensions/agent-context/scripts/python/update_agent_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

When ``plan_path`` is omitted, the script derives it from
``.specify/feature.json`` (written by /speckit-specify). Falls back to the most
recently modified ``plan.md`` anywhere under ``specs/`` (including nested scoped
layouts such as ``specs/<scope>/<feature>/plan.md``) only when feature.json is
recently modified ``plan.md`` found anywhere under ``specs/`` scoped layouts
nest it as ``specs/<scope>/<feature>/plan.md`` only when feature.json is
absent or its plan does not exist yet.
"""

Expand Down Expand Up @@ -173,16 +173,31 @@ def _resolve_plan_path(project_root: str) -> str:

if not plan_path:
root = Path(project_root).resolve()
plans = sorted(
(root / "specs").rglob("plan.md"),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
if plans:
specs = root / "specs"

def _resolved_rel(p: Path) -> Path | None:
# Resolve symlinks before checking containment: relative_to() is
# lexical and would otherwise accept a plan reached through a specs/
# symlink that points outside the project, emitting an
# in-project-looking path for an out-of-project file (or picking it
# as "most recent").
try:
plan_path = plans[0].relative_to(root).as_posix()
except ValueError:
plan_path = ""
return p.resolve().relative_to(root)
except (OSError, ValueError):
return None

# Recurse (rather than the old one-level specs/*/plan.md glob) so scoped
# layouts created via SPECIFY_FEATURE_DIRECTORY, e.g.
# specs/<scope>/<feature>/plan.md, are still discovered when
# feature.json is absent (#3024). Mirrors the bash and PowerShell twins.
candidates = []
for p in specs.rglob("plan.md"):
rel = _resolved_rel(p)
if rel is not None:
candidates.append((p, rel))
candidates.sort(key=lambda pr: pr[0].stat().st_mtime, reverse=True)
if candidates:
plan_path = candidates[0][1].as_posix()
return plan_path


Expand Down
2 changes: 1 addition & 1 deletion extensions/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"name": "LevelUp - Team AI Directives Contributor",
"version": "1.0.0",
"bundled": true,
"preinstall": true,
"preinstall": false,
"description": "Discover and contribute context modules (rules, personas, examples, skills) to team-ai-directives using Context Directive Records (CDRs)",
"author": "Agentic SDLC Team",
"repository": "https://github.com/tikalk/agentic-sdlc-spec-kit",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "agentic-sdlc-specify-cli"
version = "0.15.2+adlc4"
version = "0.16.0+adlc2"
description = "Specify CLI (tikalk fork). Agentic SDLC toolkit for Spec-Driven Development with pre-installed extensions and AI integrations."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
25 changes: 21 additions & 4 deletions scripts/python/check_prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,31 @@ def _print_paths_only(paths: FeaturePaths, json_mode: bool) -> None:
print(f"TASKS: {paths.tasks}")


def _status_marker(ok: bool) -> str:
"""Return the status glyph, downgraded to ASCII when stdout cannot encode it.

On Windows sys.stdout falls back to the ANSI code page whenever it is not a
console - a pipe or a file redirect, which is how agents and workflow steps
invoke these scripts - and U+2713 is unencodable in cp1252, so printing it
raised UnicodeEncodeError and aborted the report right after
"AVAILABLE_DOCS:". "[OK]"/"[FAIL]" is the ASCII rendering these markers
already have in-tree: see Test-FileExists in scripts/powershell/common.ps1
and normalize_status_text in tests/parity_helpers.py.
"""
glyph = "✓" if ok else "✗"
try:
glyph.encode(getattr(sys.stdout, "encoding", None) or "utf-8")
except (LookupError, UnicodeEncodeError):
return "[OK]" if ok else "[FAIL]"
return glyph


def _check_file(path: Path, description: str) -> None:
marker = "✓" if path.is_file() else "✗"
print(f" {marker} {description}")
print(f" {_status_marker(path.is_file())} {description}")


def _check_dir(path: Path, description: str) -> None:
marker = "✓" if _dir_has_entries(path) else "✗"
print(f" {marker} {description}")
print(f" {_status_marker(_dir_has_entries(path))} {description}")


def _print_text_results(paths: FeaturePaths, include_tasks: bool) -> None:
Expand Down
25 changes: 21 additions & 4 deletions scripts/python/setup_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,31 @@ def _available_docs(paths: FeaturePaths) -> list[str]:
return docs


def _status_marker(ok: bool) -> str:
"""Return the status glyph, downgraded to ASCII when stdout cannot encode it.

On Windows sys.stdout falls back to the ANSI code page whenever it is not a
console - a pipe or a file redirect, which is how agents and workflow steps
invoke these scripts - and U+2713 is unencodable in cp1252, so printing it
raised UnicodeEncodeError and aborted the report mid-listing.
"[OK]"/"[FAIL]" is the ASCII rendering these markers already have in-tree:
see Test-FileExists in scripts/powershell/common.ps1 and
normalize_status_text in tests/parity_helpers.py.
"""
glyph = "✓" if ok else "✗"
try:
glyph.encode(getattr(sys.stdout, "encoding", None) or "utf-8")
except (LookupError, UnicodeEncodeError):
return "[OK]" if ok else "[FAIL]"
return glyph


def _check_file(path: Path, description: str) -> None:
marker = "✓" if path.is_file() else "✗"
print(f" {marker} {description}")
print(f" {_status_marker(path.is_file())} {description}")


def _check_dir(path: Path, description: str) -> None:
marker = "✓" if _dir_has_entries(path) else "✗"
print(f" {marker} {description}")
print(f" {_status_marker(_dir_has_entries(path))} {description}")


def main(argv: list[str] | None = None) -> int:
Expand Down
Loading
Loading