From 99ae6d3a7ed1cf9773f10d3b567457c2161961d1 Mon Sep 17 00:00:00 2001 From: ainetx Date: Thu, 18 Jun 2026 15:50:17 +0300 Subject: [PATCH 1/2] refactor: replace wildcard gitignore patterns with explicit file paths for generated agent resources Signed-off-by: ainetx --- .gitignore | 367 +++++++++++++++--- .../studio/scripts/studio/commands/agents.py | 129 +++--- skills/studio/scripts/studio/commands/init.py | 58 +-- skills/studio/scripts/studio/commands/kit.py | 7 +- tests/test_agents_coverage.py | 60 +-- tests/test_cli_integration.py | 1 - tests/test_init_update_footprint.py | 7 +- tests/test_kit.py | 12 +- tests/test_update.py | 6 +- 9 files changed, 438 insertions(+), 209 deletions(-) diff --git a/.gitignore b/.gitignore index 52d8f567..ec020468 100644 --- a/.gitignore +++ b/.gitignore @@ -36,54 +36,329 @@ build/ # Files matched here are owned by Constructor Studio and may be overwritten. .bootstrap/.core/ .bootstrap/.gen/ -.agents/skills/cf/ -.agents/skills/cf-*/ -.agents/skills/studio-*/ -.agents/skills/cypilot-*/ -.agents/skills/cf-constructor-*/ -.codex/agents/cf*.toml -.codex/agents/studio-*.toml -.codex/agents/cypilot-*.toml -.codex/agents/cf-constructor-*.toml -.codex/agents/storytelling-*.toml +.agents/skills/cf-analyze/SKILL.md +.agents/skills/cf-auto-config/SKILL.md +.agents/skills/cf-brainstorm/SKILL.md +.agents/skills/cf-brave-new-world/SKILL.md +.agents/skills/cf-coding/SKILL.md +.agents/skills/cf-debug-prompts/SKILL.md +.agents/skills/cf-explain/SKILL.md +.agents/skills/cf-explore/SKILL.md +.agents/skills/cf-generate/SKILL.md +.agents/skills/cf-help/SKILL.md +.agents/skills/cf-kit/SKILL.md +.agents/skills/cf-map/SKILL.md +.agents/skills/cf-plan/SKILL.md +.agents/skills/cf-sdlc-change-impact-analysis/SKILL.md +.agents/skills/cf-sdlc-decompose/SKILL.md +.agents/skills/cf-sdlc-doc-adr/SKILL.md +.agents/skills/cf-sdlc-doc-design/SKILL.md +.agents/skills/cf-sdlc-doc-feature/SKILL.md +.agents/skills/cf-sdlc-doc-prd/SKILL.md +.agents/skills/cf-sdlc-implement/SKILL.md +.agents/skills/cf-sdlc-migrate-openspec/SKILL.md +.agents/skills/cf-sdlc-pr-review/SKILL.md +.agents/skills/cf-sdlc-pr-status/SKILL.md +.agents/skills/cf-sdlc-reverse-engineer/SKILL.md +.agents/skills/cf-studio/SKILL.md +.agents/skills/cf-workspace/SKILL.md +.agents/skills/cf-write-docs/SKILL.md +.agents/skills/cf-write-skills/SKILL.md +.agents/skills/cf/SKILL.md +.claude/agents/cf-analyze-planner.md +.claude/agents/cf-brainstorm-expert.md +.claude/agents/cf-brainstorm-facilitator.md +.claude/agents/cf-brainstorm-panel.md +.claude/agents/cf-code-bug-finder.md +.claude/agents/cf-codegen.md +.claude/agents/cf-deterministic-validator.md +.claude/agents/cf-diff-scope-resolver.md +.claude/agents/cf-explorer.md +.claude/agents/cf-generate-author-junior.md +.claude/agents/cf-generate-author-lead.md +.claude/agents/cf-generate-author-middle.md +.claude/agents/cf-generate-author-senior.md +.claude/agents/cf-generate-author.md +.claude/agents/cf-generate-coder-casual.md +.claude/agents/cf-generate-coder-smart.md +.claude/agents/cf-generate-collector.md +.claude/agents/cf-generate-planner.md +.claude/agents/cf-generate-prompt-engineer-casual.md +.claude/agents/cf-generate-prompt-engineer-smart.md +.claude/agents/cf-migrate-migrator.md +.claude/agents/cf-migrate-planner.md +.claude/agents/cf-migrate-scanner.md +.claude/agents/cf-migrate-verifier.md +.claude/agents/cf-pdsl-author.md +.claude/agents/cf-pdsl-reviewer.md +.claude/agents/cf-pdsl-transformer.md +.claude/agents/cf-phase-compiler-isolated.md +.claude/agents/cf-phase-compiler.md +.claude/agents/cf-phase-runner-isolated.md +.claude/agents/cf-phase-runner.md +.claude/agents/cf-pr-review.md +.claude/agents/cf-prompt-bug-finder.md +.claude/agents/cf-ralphex.md +.claude/agents/cf-semantic-reviewer-artifact.md +.claude/agents/cf-semantic-reviewer-code.md +.claude/agents/cf-semantic-reviewer-consistency.md +.claude/agents/cf-semantic-reviewer-freeform.md +.claude/agents/cf-semantic-reviewer-prompt.md +.claude/agents/cf-storytelling-context-pack.md +.claude/agents/cf-storytelling-export.md +.claude/agents/cf-storytelling-gate.md +.claude/agents/cf-storytelling-preflight.md +.claude/agents/cf-storytelling-wrap.md +.claude/skills/cf-analyze/SKILL.md +.claude/skills/cf-auto-config/SKILL.md +.claude/skills/cf-brainstorm/SKILL.md +.claude/skills/cf-brave-new-world/SKILL.md +.claude/skills/cf-coding/SKILL.md +.claude/skills/cf-debug-prompts/SKILL.md +.claude/skills/cf-explain/SKILL.md +.claude/skills/cf-explore/SKILL.md +.claude/skills/cf-generate/SKILL.md +.claude/skills/cf-help/SKILL.md +.claude/skills/cf-kit/SKILL.md +.claude/skills/cf-map/SKILL.md +.claude/skills/cf-plan/SKILL.md +.claude/skills/cf-sdlc-change-impact-analysis/SKILL.md +.claude/skills/cf-sdlc-decompose/SKILL.md +.claude/skills/cf-sdlc-doc-adr/SKILL.md +.claude/skills/cf-sdlc-doc-design/SKILL.md +.claude/skills/cf-sdlc-doc-feature/SKILL.md +.claude/skills/cf-sdlc-doc-prd/SKILL.md +.claude/skills/cf-sdlc-implement/SKILL.md +.claude/skills/cf-sdlc-migrate-openspec/SKILL.md +.claude/skills/cf-sdlc-pr-review/SKILL.md +.claude/skills/cf-sdlc-pr-status/SKILL.md +.claude/skills/cf-sdlc-reverse-engineer/SKILL.md +.claude/skills/cf-studio/SKILL.md +.claude/skills/cf-workspace/SKILL.md +.claude/skills/cf-write-docs/SKILL.md +.claude/skills/cf-write-skills/SKILL.md +.claude/skills/cf/SKILL.md .codex/.cf-installed -.codex/.constructor-studio-installed -.claude/skills/cf/ -.claude/skills/cf-*/ -.claude/commands/cf*.md -.claude/commands/studio-*.md -.claude/commands/cypilot-*.md -.claude/commands/cf-constructor-*.md -.claude/agents/cf*.md -.claude/agents/studio-*.md -.claude/agents/cypilot-*.md -.claude/agents/cf-constructor-*.md -.claude/agents/storytelling-*.md -.cursor/commands/cf*.md -.cursor/commands/studio-*.md -.cursor/commands/cypilot-*.md -.cursor/commands/cf-constructor-*.md -.cursor/agents/cf*.md -.cursor/agents/studio-*.md -.cursor/agents/cypilot-*.md -.cursor/agents/cf-constructor-*.md -.cursor/agents/storytelling-*.md -.github/prompts/cf*.prompt.md -.github/prompts/studio-*.prompt.md -.github/prompts/cypilot-*.prompt.md -.github/prompts/cf-constructor-*.prompt.md -.github/agents/cf*.md -.github/agents/studio-*.md -.github/agents/cypilot-*.md -.github/agents/cf-constructor-*.md -.github/agents/storytelling-*.md +.codex/agents/cf-analyze-planner.toml +.codex/agents/cf-brainstorm-expert.toml +.codex/agents/cf-brainstorm-facilitator.toml +.codex/agents/cf-brainstorm-panel.toml +.codex/agents/cf-code-bug-finder.toml +.codex/agents/cf-codegen.toml +.codex/agents/cf-deterministic-validator.toml +.codex/agents/cf-diff-scope-resolver.toml +.codex/agents/cf-explorer.toml +.codex/agents/cf-generate-author-junior.toml +.codex/agents/cf-generate-author-lead.toml +.codex/agents/cf-generate-author-middle.toml +.codex/agents/cf-generate-author-senior.toml +.codex/agents/cf-generate-author.toml +.codex/agents/cf-generate-coder-casual.toml +.codex/agents/cf-generate-coder-smart.toml +.codex/agents/cf-generate-collector.toml +.codex/agents/cf-generate-planner.toml +.codex/agents/cf-generate-prompt-engineer-casual.toml +.codex/agents/cf-generate-prompt-engineer-smart.toml +.codex/agents/cf-migrate-migrator.toml +.codex/agents/cf-migrate-planner.toml +.codex/agents/cf-migrate-scanner.toml +.codex/agents/cf-migrate-verifier.toml +.codex/agents/cf-pdsl-author.toml +.codex/agents/cf-pdsl-reviewer.toml +.codex/agents/cf-pdsl-transformer.toml +.codex/agents/cf-phase-compiler-isolated.toml +.codex/agents/cf-phase-compiler.toml +.codex/agents/cf-phase-runner-isolated.toml +.codex/agents/cf-phase-runner.toml +.codex/agents/cf-pr-review.toml +.codex/agents/cf-prompt-bug-finder.toml +.codex/agents/cf-ralphex.toml +.codex/agents/cf-semantic-reviewer-artifact.toml +.codex/agents/cf-semantic-reviewer-code.toml +.codex/agents/cf-semantic-reviewer-consistency.toml +.codex/agents/cf-semantic-reviewer-freeform.toml +.codex/agents/cf-semantic-reviewer-prompt.toml +.codex/agents/cf-storytelling-context-pack.toml +.codex/agents/cf-storytelling-export.toml +.codex/agents/cf-storytelling-gate.toml +.codex/agents/cf-storytelling-preflight.toml +.codex/agents/cf-storytelling-wrap.toml +.cursor/agents/cf-analyze-planner.md +.cursor/agents/cf-brainstorm-expert.md +.cursor/agents/cf-brainstorm-facilitator.md +.cursor/agents/cf-brainstorm-panel.md +.cursor/agents/cf-code-bug-finder.md +.cursor/agents/cf-codegen.md +.cursor/agents/cf-deterministic-validator.md +.cursor/agents/cf-diff-scope-resolver.md +.cursor/agents/cf-explorer.md +.cursor/agents/cf-generate-author-junior.md +.cursor/agents/cf-generate-author-lead.md +.cursor/agents/cf-generate-author-middle.md +.cursor/agents/cf-generate-author-senior.md +.cursor/agents/cf-generate-author.md +.cursor/agents/cf-generate-coder-casual.md +.cursor/agents/cf-generate-coder-smart.md +.cursor/agents/cf-generate-collector.md +.cursor/agents/cf-generate-planner.md +.cursor/agents/cf-generate-prompt-engineer-casual.md +.cursor/agents/cf-generate-prompt-engineer-smart.md +.cursor/agents/cf-migrate-migrator.md +.cursor/agents/cf-migrate-planner.md +.cursor/agents/cf-migrate-scanner.md +.cursor/agents/cf-migrate-verifier.md +.cursor/agents/cf-pdsl-author.md +.cursor/agents/cf-pdsl-reviewer.md +.cursor/agents/cf-pdsl-transformer.md +.cursor/agents/cf-phase-compiler-isolated.md +.cursor/agents/cf-phase-compiler.md +.cursor/agents/cf-phase-runner-isolated.md +.cursor/agents/cf-phase-runner.md +.cursor/agents/cf-pr-review.md +.cursor/agents/cf-prompt-bug-finder.md +.cursor/agents/cf-ralphex.md +.cursor/agents/cf-semantic-reviewer-artifact.md +.cursor/agents/cf-semantic-reviewer-code.md +.cursor/agents/cf-semantic-reviewer-consistency.md +.cursor/agents/cf-semantic-reviewer-freeform.md +.cursor/agents/cf-semantic-reviewer-prompt.md +.cursor/agents/cf-storytelling-context-pack.md +.cursor/agents/cf-storytelling-export.md +.cursor/agents/cf-storytelling-gate.md +.cursor/agents/cf-storytelling-preflight.md +.cursor/agents/cf-storytelling-wrap.md +.cursor/commands/cf-analyze.md +.cursor/commands/cf-auto-config.md +.cursor/commands/cf-brainstorm.md +.cursor/commands/cf-brave-new-world.md +.cursor/commands/cf-coding.md +.cursor/commands/cf-debug-prompts.md +.cursor/commands/cf-explain.md +.cursor/commands/cf-explore.md +.cursor/commands/cf-generate.md +.cursor/commands/cf-help.md +.cursor/commands/cf-kit.md +.cursor/commands/cf-map.md +.cursor/commands/cf-plan.md +.cursor/commands/cf-sdlc-change-impact-analysis.md +.cursor/commands/cf-sdlc-decompose.md +.cursor/commands/cf-sdlc-doc-adr.md +.cursor/commands/cf-sdlc-doc-design.md +.cursor/commands/cf-sdlc-doc-feature.md +.cursor/commands/cf-sdlc-doc-prd.md +.cursor/commands/cf-sdlc-implement.md +.cursor/commands/cf-sdlc-migrate-openspec.md +.cursor/commands/cf-sdlc-pr-review.md +.cursor/commands/cf-sdlc-pr-status.md +.cursor/commands/cf-sdlc-reverse-engineer.md +.cursor/commands/cf-studio.md +.cursor/commands/cf-workspace.md +.cursor/commands/cf-write-docs.md +.cursor/commands/cf-write-skills.md +.cursor/commands/cf.md .github/.cf-installed -.github/.constructor-studio-installed -.github/copilot-instructions.md -.windsurf/workflows/cf*.md -.windsurf/workflows/studio-*.md -.windsurf/workflows/cypilot-*.md -.windsurf/workflows/cf-constructor-*.md +.github/agents/cf-analyze-planner.agent.md +.github/agents/cf-brainstorm-expert.agent.md +.github/agents/cf-brainstorm-facilitator.agent.md +.github/agents/cf-brainstorm-panel.agent.md +.github/agents/cf-code-bug-finder.agent.md +.github/agents/cf-codegen.agent.md +.github/agents/cf-deterministic-validator.agent.md +.github/agents/cf-diff-scope-resolver.agent.md +.github/agents/cf-explorer.agent.md +.github/agents/cf-generate-author-junior.agent.md +.github/agents/cf-generate-author-lead.agent.md +.github/agents/cf-generate-author-middle.agent.md +.github/agents/cf-generate-author-senior.agent.md +.github/agents/cf-generate-author.agent.md +.github/agents/cf-generate-coder-casual.agent.md +.github/agents/cf-generate-coder-smart.agent.md +.github/agents/cf-generate-collector.agent.md +.github/agents/cf-generate-planner.agent.md +.github/agents/cf-generate-prompt-engineer-casual.agent.md +.github/agents/cf-generate-prompt-engineer-smart.agent.md +.github/agents/cf-migrate-migrator.agent.md +.github/agents/cf-migrate-planner.agent.md +.github/agents/cf-migrate-scanner.agent.md +.github/agents/cf-migrate-verifier.agent.md +.github/agents/cf-pdsl-author.agent.md +.github/agents/cf-pdsl-reviewer.agent.md +.github/agents/cf-pdsl-transformer.agent.md +.github/agents/cf-phase-compiler-isolated.agent.md +.github/agents/cf-phase-compiler.agent.md +.github/agents/cf-phase-runner-isolated.agent.md +.github/agents/cf-phase-runner.agent.md +.github/agents/cf-pr-review.agent.md +.github/agents/cf-prompt-bug-finder.agent.md +.github/agents/cf-ralphex.agent.md +.github/agents/cf-semantic-reviewer-artifact.agent.md +.github/agents/cf-semantic-reviewer-code.agent.md +.github/agents/cf-semantic-reviewer-consistency.agent.md +.github/agents/cf-semantic-reviewer-freeform.agent.md +.github/agents/cf-semantic-reviewer-prompt.agent.md +.github/agents/cf-storytelling-context-pack.agent.md +.github/agents/cf-storytelling-export.agent.md +.github/agents/cf-storytelling-gate.agent.md +.github/agents/cf-storytelling-preflight.agent.md +.github/agents/cf-storytelling-wrap.agent.md +.github/prompts/cf-analyze.prompt.md +.github/prompts/cf-auto-config.prompt.md +.github/prompts/cf-brainstorm.prompt.md +.github/prompts/cf-brave-new-world.prompt.md +.github/prompts/cf-coding.prompt.md +.github/prompts/cf-debug-prompts.prompt.md +.github/prompts/cf-explain.prompt.md +.github/prompts/cf-explore.prompt.md +.github/prompts/cf-generate.prompt.md +.github/prompts/cf-help.prompt.md +.github/prompts/cf-kit.prompt.md +.github/prompts/cf-map.prompt.md +.github/prompts/cf-plan.prompt.md +.github/prompts/cf-sdlc-change-impact-analysis.prompt.md +.github/prompts/cf-sdlc-decompose.prompt.md +.github/prompts/cf-sdlc-doc-adr.prompt.md +.github/prompts/cf-sdlc-doc-design.prompt.md +.github/prompts/cf-sdlc-doc-feature.prompt.md +.github/prompts/cf-sdlc-doc-prd.prompt.md +.github/prompts/cf-sdlc-implement.prompt.md +.github/prompts/cf-sdlc-migrate-openspec.prompt.md +.github/prompts/cf-sdlc-pr-review.prompt.md +.github/prompts/cf-sdlc-pr-status.prompt.md +.github/prompts/cf-sdlc-reverse-engineer.prompt.md +.github/prompts/cf-studio.prompt.md +.github/prompts/cf-workspace.prompt.md +.github/prompts/cf-write-docs.prompt.md +.github/prompts/cf-write-skills.prompt.md +.github/prompts/cf.prompt.md +.windsurf/workflows/cf-analyze.md +.windsurf/workflows/cf-auto-config.md +.windsurf/workflows/cf-brainstorm.md +.windsurf/workflows/cf-brave-new-world.md +.windsurf/workflows/cf-coding.md +.windsurf/workflows/cf-debug-prompts.md +.windsurf/workflows/cf-explain.md +.windsurf/workflows/cf-explore.md +.windsurf/workflows/cf-generate.md +.windsurf/workflows/cf-help.md +.windsurf/workflows/cf-kit.md +.windsurf/workflows/cf-map.md +.windsurf/workflows/cf-plan.md +.windsurf/workflows/cf-sdlc-change-impact-analysis.md +.windsurf/workflows/cf-sdlc-decompose.md +.windsurf/workflows/cf-sdlc-doc-adr.md +.windsurf/workflows/cf-sdlc-doc-design.md +.windsurf/workflows/cf-sdlc-doc-feature.md +.windsurf/workflows/cf-sdlc-doc-prd.md +.windsurf/workflows/cf-sdlc-implement.md +.windsurf/workflows/cf-sdlc-migrate-openspec.md +.windsurf/workflows/cf-sdlc-pr-review.md +.windsurf/workflows/cf-sdlc-pr-status.md +.windsurf/workflows/cf-sdlc-reverse-engineer.md +.windsurf/workflows/cf-studio.md +.windsurf/workflows/cf-workspace.md +.windsurf/workflows/cf-write-docs.md +.windsurf/workflows/cf-write-skills.md +.windsurf/workflows/cf.md .bootstrap/config/kits/sdlc/ # END Constructor Studio diff --git a/skills/studio/scripts/studio/commands/agents.py b/skills/studio/scripts/studio/commands/agents.py index 94c4be3f..55fe644f 100644 --- a/skills/studio/scripts/studio/commands/agents.py +++ b/skills/studio/scripts/studio/commands/agents.py @@ -1707,15 +1707,6 @@ def _default_agents_config() -> dict: "skills": { "custom_content": "", "outputs": shared_skills + [ - { - "path": ".github/copilot-instructions.md", - "template": [ - "# Constructor Studio", - _GENERATED_MARKER, - "", - "{custom_content}", - ], - }, { "path": ".github/prompts/cf.prompt.md", "template": [ @@ -2471,15 +2462,6 @@ def _has_non_openai_install_signal(project_root: Path) -> bool: if _file_has_studio_follow_link(project_root / ".cursor" / "rules" / "studio.mdc"): return True - legacy_ci = project_root / ".github" / "copilot-instructions.md" - if legacy_ci.is_file(): - try: - ci_text = legacy_ci.read_text(encoding="utf-8") - if ci_text.startswith("# Constructor Studio") or ci_text.startswith("# Studio"): - return True - except (OSError, UnicodeDecodeError): - pass - return ( (project_root / ".github" / "prompts" / "cf.prompt.md").is_file() or (project_root / ".github" / "prompts" / "studio.prompt.md").is_file() @@ -2597,18 +2579,8 @@ def _is_agent_installed(agent: str, project_root: Path) -> bool: return True # ── Legacy Copilot fallback ─────────────────────────────────────────── - # A Constructor Studio-managed copilot-instructions.md (starts with - # "# Constructor Studio" or legacy "# Studio") is a valid signal from - # pre-marker installs. Also detect via prompts file. + # Detect via prompts files and legacy install markers only. if agent == "copilot": - legacy_ci = project_root / ".github" / "copilot-instructions.md" - if legacy_ci.is_file(): - try: - ci_text = legacy_ci.read_text(encoding="utf-8") - if ci_text.startswith("# Constructor Studio") or ci_text.startswith("# Studio"): - return True - except (OSError, UnicodeDecodeError): - pass for prompt_name in ("cf.prompt.md", "studio.prompt.md", "cypilot.prompt.md"): if (project_root / ".github" / "prompts" / prompt_name).is_file(): return True @@ -3381,21 +3353,6 @@ def _process_skills( continue out_path, rel_path, content = rendered - # Guard: skip overwriting user-authored copilot-instructions.md. - # The file is only Constructor Studio-managed when it starts with "# Constructor Studio" or legacy "# Studio". - if rel_path == ".github/copilot-instructions.md" and out_path.is_file(): - try: - existing = out_path.read_text(encoding="utf-8") - except OSError: - existing = "" - if not existing.startswith("# Constructor Studio") and not existing.startswith("# Studio"): - rel = _safe_relpath(out_path.resolve(), project_root) - skills_result["skipped"].append( - f"{rel} (user-authored, not overwriting)" - ) - skills_result["_copilot_user_authored"] = True - continue - _write_or_skip(out_path, content, skills_result, project_root, dry_run) return skills_result @@ -3798,6 +3755,85 @@ def _process_kit_public_agents_and_rules( return {"agents": public_agents, "rules": public_rules} +def _collect_managed_result_paths( + project_root: Path, + section: Dict[str, Any], +) -> Set[str]: + """Collect normalized project-relative output paths from a generator result.""" + paths: Set[str] = set() + for key in ("created", "updated", "unchanged", "deleted"): + values = section.get(key, []) + if not isinstance(values, list): + continue + for value in values: + if isinstance(value, tuple): + candidates = [value[-1]] + else: + candidates = [value] + for candidate in candidates: + if not isinstance(candidate, str) or not candidate.strip(): + continue + candidate_path = Path(candidate) + if candidate_path.is_absolute(): + paths.add(_safe_relpath(candidate_path, project_root)) + else: + paths.add(candidate.replace("\\", "/").strip("/")) + for output in section.get("outputs", []): + if not isinstance(output, dict): + continue + raw_path = output.get("path") + if not isinstance(raw_path, str) or not raw_path.strip(): + continue + candidate_path = Path(raw_path) + if candidate_path.is_absolute(): + paths.add(_safe_relpath(candidate_path, project_root)) + else: + paths.add(raw_path.replace("\\", "/").strip("/")) + return {path for path in paths if path} + + +def list_managed_agent_output_paths( + project_root: Path, + studio_root: Path, +) -> List[str]: + """Return exact CFS-managed agent integration paths for the current install.""" + cfg = _default_agents_config() + managed_paths: Set[str] = set() + + for marker_path, _marker_content in _INSTALL_MARKERS.values(): + managed_paths.add(marker_path) + + for agent in _ALL_RECOGNIZED_AGENTS: + agent_cfg = cfg.get("agents", {}).get(agent, {}) + skills_cfg = agent_cfg.get("skills", {}) if isinstance(agent_cfg, dict) else {} + outputs = skills_cfg.get("outputs") if isinstance(skills_cfg, dict) else None + if isinstance(outputs, list): + for output in outputs: + if not isinstance(output, dict): + continue + rel_path = output.get("path") + if isinstance(rel_path, str) and rel_path.strip(): + managed_paths.add(rel_path.replace("\\", "/").strip("/")) + sections: List[Dict[str, Any]] = [] + sections.append(_process_workflows(agent, project_root, studio_root, cfg, None, dry_run=True)) + sections.append(_process_skills(agent, project_root, studio_root, cfg, None, dry_run=True)) + sections.append(_process_kit_workflow_skills(agent, project_root, studio_root, cfg, None, dry_run=True)) + sections.append(_process_subagents(agent, project_root, studio_root, cfg, None, dry_run=True)) + public_sections = _process_kit_public_agents_and_rules( + agent, + project_root, + studio_root, + dry_run=True, + ) + sections.append(public_sections.get("agents", {})) + sections.append(public_sections.get("rules", {})) + for section in sections: + if isinstance(section, dict): + managed_paths.update(_collect_managed_result_paths(project_root, section)) + + return sorted(path for path in managed_paths if path) + + def _process_legacy_cleanup( agent: str, project_root: Path, @@ -3924,9 +3960,8 @@ def _process_legacy_cleanup( # @cpt-begin:cpt-studio-algo-agent-integration-generate-shims:p1:inst-write-install-marker # Tools that share generic directories need a unique Constructor Studio-specific # marker so detection/regeneration can distinguish Constructor Studio installs from - # unrelated user files. The marker is always created when the agent is - # processed — even if copilot-instructions.md was preserved as user- - # authored, the other generated outputs (prompts, shared skills, agents) + # unrelated user files. The marker is always created when the agent is + # processed because the other generated outputs (prompts, shared skills, agents) # still need to be managed by future `cfs update` runs. marker_info = _INSTALL_MARKERS.get(agent) if marker_info and not dry_run: diff --git a/skills/studio/scripts/studio/commands/init.py b/skills/studio/scripts/studio/commands/init.py index 33be0e4a..a60f6eba 100644 --- a/skills/studio/scripts/studio/commands/init.py +++ b/skills/studio/scripts/studio/commands/init.py @@ -14,6 +14,7 @@ from ..utils.artifacts_meta import create_backup, generate_default_registry, generate_slug from ..utils import toml_utils from ..utils.ui import ui +from .agents import list_managed_agent_output_paths # Cache-managed install content: core directories go into .core/, root files go into the install root. # Full directories (copied entirely) @@ -360,6 +361,7 @@ def _ignored_kit_paths(core_toml_path: Path, default: str = "tracked") -> List[s def _gitignore_patterns( + project_root: Path, install_dir: str, ignored_kit_paths: List[str], runtime_tracking: str = "ignored", @@ -373,56 +375,9 @@ def _gitignore_patterns( f"{install_rel}/{GEN_SUBDIR}/", ]) if agent_tracking == "ignored": - patterns.extend([ - ".agents/skills/cf/", - ".agents/skills/cf-*/", - ".agents/skills/studio-*/", - ".agents/skills/cypilot-*/", - ".agents/skills/cf-constructor-*/", - ".codex/agents/cf*.toml", - ".codex/agents/studio-*.toml", - ".codex/agents/cypilot-*.toml", - ".codex/agents/cf-constructor-*.toml", - ".codex/agents/storytelling-*.toml", - ".codex/.cf-installed", - ".codex/.constructor-studio-installed", - ".claude/skills/cf/", - ".claude/skills/cf-*/", - ".claude/commands/cf*.md", - ".claude/commands/studio-*.md", - ".claude/commands/cypilot-*.md", - ".claude/commands/cf-constructor-*.md", - ".claude/agents/cf*.md", - ".claude/agents/studio-*.md", - ".claude/agents/cypilot-*.md", - ".claude/agents/cf-constructor-*.md", - ".claude/agents/storytelling-*.md", - ".cursor/commands/cf*.md", - ".cursor/commands/studio-*.md", - ".cursor/commands/cypilot-*.md", - ".cursor/commands/cf-constructor-*.md", - ".cursor/agents/cf*.md", - ".cursor/agents/studio-*.md", - ".cursor/agents/cypilot-*.md", - ".cursor/agents/cf-constructor-*.md", - ".cursor/agents/storytelling-*.md", - ".github/prompts/cf*.prompt.md", - ".github/prompts/studio-*.prompt.md", - ".github/prompts/cypilot-*.prompt.md", - ".github/prompts/cf-constructor-*.prompt.md", - ".github/agents/cf*.md", - ".github/agents/studio-*.md", - ".github/agents/cypilot-*.md", - ".github/agents/cf-constructor-*.md", - ".github/agents/storytelling-*.md", - ".github/.cf-installed", - ".github/.constructor-studio-installed", - ".github/copilot-instructions.md", - ".windsurf/workflows/cf*.md", - ".windsurf/workflows/studio-*.md", - ".windsurf/workflows/cypilot-*.md", - ".windsurf/workflows/cf-constructor-*.md", - ]) + patterns.extend( + list_managed_agent_output_paths(project_root, project_root / install_rel) + ) for kit_path in ignored_kit_paths: kit_rel = kit_path.strip().replace("\\", "/").strip("/") if kit_rel: @@ -431,6 +386,7 @@ def _gitignore_patterns( def _compute_gitignore_block( + project_root: Path, install_dir: str, ignored_kit_paths: List[str], runtime_tracking: str = "ignored", @@ -442,6 +398,7 @@ def _compute_gitignore_block( "# Generated Constructor Studio runtime and agent integration files.", "# Files matched here are owned by Constructor Studio and may be overwritten.", *_gitignore_patterns( + project_root, install_dir, ignored_kit_paths, runtime_tracking=runtime_tracking, @@ -462,6 +419,7 @@ def _write_gitignore_block( ) -> str: gitignore_path = project_root / ".gitignore" expected_block = _compute_gitignore_block( + project_root, install_dir, _ignored_kit_paths(core_toml_path, default=default_kit_tracking), runtime_tracking=_read_install_tracking(core_toml_path, "runtime_tracking", default="ignored"), diff --git a/skills/studio/scripts/studio/commands/kit.py b/skills/studio/scripts/studio/commands/kit.py index 1834b48b..f17be043 100644 --- a/skills/studio/scripts/studio/commands/kit.py +++ b/skills/studio/scripts/studio/commands/kit.py @@ -996,12 +996,7 @@ def _collect_registered_kit_metadata( except (OSError, ValueError): resources = {} if not isinstance(resources, dict) or not resources: - kit_dir, kit_rel_path = _resolve_registered_kit_metadata_target( - studio_dir, - kit_slug, - kit_entry, - ) - return _collect_kit_metadata(kit_dir, kit_slug, kit_rel_path) + return {"skill_nav": "", "agents_content": ""} result: Dict[str, str] = {"skill_nav": "", "agents_content": ""} agents_parts: List[str] = [] diff --git a/tests/test_agents_coverage.py b/tests/test_agents_coverage.py index 8e23d1f0..dbcbc176 100644 --- a/tests/test_agents_coverage.py +++ b/tests/test_agents_coverage.py @@ -1152,7 +1152,7 @@ def test_detects_cursor_follow_link(self): self.assertTrue(_has_non_openai_install_signal(root)) - def test_detects_legacy_copilot_instructions(self): + def test_ignores_legacy_copilot_instructions(self): from studio.commands.agents import _has_non_openai_install_signal with TemporaryDirectory() as tmpdir: @@ -1161,7 +1161,7 @@ def test_detects_legacy_copilot_instructions(self): legacy_ci.parent.mkdir(parents=True) legacy_ci.write_text("# Constructor Studio\nLegacy instructions\n", encoding="utf-8") - self.assertTrue(_has_non_openai_install_signal(root)) + self.assertFalse(_has_non_openai_install_signal(root)) def test_ignores_legacy_copilot_read_errors(self): from studio.commands.agents import _has_non_openai_install_signal @@ -1263,8 +1263,8 @@ def test_agents_not_configured(self): results = { "copilot": { - "workflows": {"updated": [], "created": ["/p/.github/prompts/cypilot-generate.prompt.md"]}, - "skills": {"updated": [], "created": ["/p/.github/copilot-instructions.md"]}, + "workflows": {"updated": [], "created": ["/p/.github/prompts/cf-generate.prompt.md"]}, + "skills": {"updated": [], "created": ["/p/.github/prompts/cf.prompt.md"]}, }, } err = io.StringIO() @@ -2185,8 +2185,8 @@ def test_copilot_detected_via_marker_file(self): from studio.commands.agents import _AGENT_MARKERS self.assertIn(".github/.cf-installed", _AGENT_MARKERS.get("copilot", [])) - def test_copilot_generates_repo_wide_instructions(self): - """Copilot generation must produce .github/copilot-instructions.md (always-on).""" + def test_copilot_generates_prompt_entrypoint(self): + """Copilot generation must produce the shared prompt entrypoint and marker.""" from studio.commands.agents import _process_single_agent, _default_agents_config with TemporaryDirectory() as td: @@ -2201,44 +2201,14 @@ def test_copilot_generates_repo_wide_instructions(self): (cpt / ".core" / "workflows").mkdir(parents=True, exist_ok=True) cfg = _default_agents_config() _process_single_agent("copilot", root, cpt, cfg, None, dry_run=False) - instructions = root / ".github" / "copilot-instructions.md" - self.assertTrue(instructions.exists(), ".github/copilot-instructions.md must be generated") - content = instructions.read_text(encoding="utf-8") - self.assertNotIn("ALWAYS open and follow", content) + prompt = root / ".github" / "prompts" / "cf.prompt.md" + self.assertTrue(prompt.exists(), ".github/prompts/cf.prompt.md must be generated") # Marker file must also be created marker = root / ".github" / ".cf-installed" self.assertTrue(marker.exists(), ".github/.cf-installed marker must be created") - def test_copilot_cleanup_removes_legacy_skill_follow_line(self): - """Regeneration must clean the old generated follow-line from copilot-instructions.md.""" - from studio.commands.agents import _process_single_agent, _default_agents_config - - with TemporaryDirectory() as td: - root = (Path(td) / "proj").resolve() - root.mkdir() - (root / ".git").mkdir() - cpt = root / "cypilot" - cpt.mkdir() - core_skill = cpt / ".core" / "skills" / "studio" / "SKILL.md" - core_skill.parent.mkdir(parents=True) - core_skill.write_text("---\nname: studio\ndescription: Test\n---\nContent\n") - (cpt / ".core" / "workflows").mkdir(parents=True, exist_ok=True) - - instructions = root / ".github" / "copilot-instructions.md" - instructions.parent.mkdir(parents=True, exist_ok=True) - instructions.write_text( - "# Constructor Studio\n\nALWAYS open and follow `{cf-studio-path}/.core/skills/studio/SKILL.md`\n", - encoding="utf-8", - ) - - cfg = _default_agents_config() - _process_single_agent("copilot", root, cpt, cfg, None, dry_run=False) - - content = instructions.read_text(encoding="utf-8") - self.assertNotIn("ALWAYS open and follow", content) - def test_user_authored_copilot_instructions_preserved(self): - """User-authored copilot-instructions.md must NOT be overwritten by generation.""" + """User-authored copilot-instructions.md must be left untouched by generation.""" from studio.commands.agents import _process_single_agent, _default_agents_config with TemporaryDirectory() as td: @@ -2263,12 +2233,8 @@ def test_user_authored_copilot_instructions_preserved(self): # User content must be preserved self.assertEqual(instructions.read_text(encoding="utf-8"), user_content) - # Should appear in skipped skipped = result.get("skills", {}).get("skipped", []) - self.assertTrue( - any("user-authored" in s for s in skipped), - f"Expected user-authored skip notice, got: {skipped}", - ) + self.assertFalse(skipped, f"Did not expect copilot-instructions handling, got: {skipped}") # Marker IS created even when copilot-instructions.md is user-authored, # because other Copilot outputs (prompts, shared skills) are still managed. marker = root / ".github" / ".cf-installed" @@ -2324,12 +2290,12 @@ def test_legacy_windsurf_non_cypilot_not_detected(self): self.assertFalse(_is_agent_installed("windsurf", root)) def test_legacy_copilot_fallback_detection(self): - """Shared _is_agent_installed has legacy Copilot fallback via Constructor Studio-managed copilot-instructions.md.""" + """Shared _is_agent_installed must no longer rely on copilot-instructions.md.""" import importlib src = importlib.util.find_spec("studio.commands.agents").origin source = Path(src).read_text(encoding="utf-8") - self.assertIn("legacy_ci", source) - self.assertIn('startswith("# Constructor Studio")', source) + self.assertNotIn("legacy_ci", source) + self.assertNotIn('startswith("# Constructor Studio")', source) def test_openai_legacy_requires_codex_agents_content(self): """Shared _is_agent_installed requires .codex/agents/ with content, not bare .codex/ directory.""" diff --git a/tests/test_cli_integration.py b/tests/test_cli_integration.py index ec932765..c49a1138 100644 --- a/tests/test_cli_integration.py +++ b/tests/test_cli_integration.py @@ -1471,7 +1471,6 @@ def test_all_agents_use_at_path(self): ], "copilot": [ ".agents/skills/cf/SKILL.md", - ".github/copilot-instructions.md", ".github/prompts/cf.prompt.md", ], "cursor": [ diff --git a/tests/test_init_update_footprint.py b/tests/test_init_update_footprint.py index 31a76164..a6d4d73a 100644 --- a/tests/test_init_update_footprint.py +++ b/tests/test_init_update_footprint.py @@ -186,7 +186,8 @@ def fake_install_default_kit(studio_dir, _interactive, _actions, _errors): assert ".bootstrap/config/kits/sdlc/" in gitignore assert ".bootstrap/config/kits/\n" not in gitignore assert ".github/\n" not in gitignore - assert ".github/prompts/cf*.prompt.md" in gitignore + assert ".github/prompts/cf.prompt.md" in gitignore + assert ".github/copilot-instructions.md" not in gitignore core = (root / ".bootstrap" / "config" / "core.toml").read_text(encoding="utf-8") assert 'tracking = "ignored"' in core assert (root / ".bootstrap" / "whatsnew.toml").is_file() @@ -226,8 +227,8 @@ def test_init_tracked_runtime_and_agents_are_not_gitignored(): gitignore = (root / ".gitignore").read_text(encoding="utf-8") assert ".bootstrap/.core/" not in gitignore assert ".bootstrap/.gen/" not in gitignore - assert ".codex/agents/cf*.toml" not in gitignore - assert ".github/prompts/cf*.prompt.md" not in gitignore + assert ".codex/.cf-installed" not in gitignore + assert ".github/prompts/cf.prompt.md" not in gitignore core = (root / ".bootstrap" / "config" / "core.toml").read_text(encoding="utf-8") assert 'runtime_tracking = "tracked"' in core assert 'agent_tracking = "tracked"' in core diff --git a/tests/test_kit.py b/tests/test_kit.py index f4b09a39..2d779b35 100644 --- a/tests/test_kit.py +++ b/tests/test_kit.py @@ -5824,7 +5824,7 @@ def test_ignores_unregistered_config_kit_dirs(self): self.assertFalse((adapter / ".gen" / "SKILL.md").exists()) self.assertNotIn("# Loose Agents", gen_agents) - def test_uses_default_installed_kit_path_when_path_not_explicitly_registered(self): + def test_skips_default_installed_kit_path_when_resources_not_registered(self): from studio.commands.kit import regenerate_gen_aggregates from studio.utils import toml_utils with TemporaryDirectory() as td: @@ -5852,7 +5852,7 @@ def test_uses_default_installed_kit_path_when_path_not_explicitly_registered(sel gen_agents = (adapter / ".gen" / "AGENTS.md").read_text(encoding="utf-8") self.assertFalse((adapter / ".gen" / "SKILL.md").exists()) - self.assertIn("# Default Agents", gen_agents) + self.assertNotIn("# Default Agents", gen_agents) def test_deletes_legacy_generated_skill_aggregate(self): from studio.commands.kit import regenerate_gen_aggregates @@ -5876,7 +5876,7 @@ def test_deletes_legacy_generated_skill_aggregate(self): self.assertEqual(result["gen_skill"], "deleted") self.assertFalse(legacy_skill.exists()) - def test_uses_registered_custom_kit_path(self): + def test_skips_registered_custom_kit_path_when_resources_not_registered(self): from studio.commands.kit import regenerate_gen_aggregates from studio.utils import toml_utils with TemporaryDirectory() as td: @@ -5905,9 +5905,9 @@ def test_uses_registered_custom_kit_path(self): gen_agents = (adapter / ".gen" / "AGENTS.md").read_text(encoding="utf-8") self.assertFalse((adapter / ".gen" / "SKILL.md").exists()) - self.assertIn("# Custom Agents", gen_agents) + self.assertNotIn("# Custom Agents", gen_agents) - def test_uses_registered_absolute_custom_kit_path(self): + def test_skips_registered_absolute_custom_kit_path_when_resources_not_registered(self): from studio.commands.kit import regenerate_gen_aggregates from studio.utils import toml_utils with TemporaryDirectory() as td: @@ -5936,7 +5936,7 @@ def test_uses_registered_absolute_custom_kit_path(self): gen_agents = (adapter / ".gen" / "AGENTS.md").read_text(encoding="utf-8") self.assertFalse((adapter / ".gen" / "SKILL.md").exists()) - self.assertIn("# Custom Agents", gen_agents) + self.assertNotIn("# Custom Agents", gen_agents) def test_uses_registered_windows_drive_custom_kit_path(self): from studio.commands.kit import regenerate_gen_aggregates diff --git a/tests/test_update.py b/tests/test_update.py index d7d68aa7..205a8c47 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -1966,8 +1966,8 @@ def test_copilot_detected_via_cypilot_installed_marker(self): ) self.assertIn("copilot", result) - def test_legacy_copilot_detected_via_managed_instructions(self): - """Legacy Copilot install detected via Constructor Studio-managed copilot-instructions.md.""" + def test_legacy_copilot_instructions_do_not_trigger_detection(self): + """Legacy copilot-instructions.md alone must not trigger Copilot detection.""" from studio.commands.update import _maybe_regenerate_agents with TemporaryDirectory() as td: root = Path(td) / "proj" @@ -1984,7 +1984,7 @@ def test_legacy_copilot_detected_via_managed_instructions(self): result = _maybe_regenerate_agents( {"skills": "updated"}, {}, root, cypilot_dir, ) - self.assertIn("copilot", result) + self.assertNotIn("copilot", result) def test_user_copilot_instructions_does_not_trigger_detection(self): """User-authored .github/copilot-instructions.md must NOT trigger Copilot detection.""" From ab5f27ecb17313818bdfcbeeb78fdd662c7d5467 Mon Sep 17 00:00:00 2001 From: ainetx Date: Thu, 18 Jun 2026 16:06:55 +0300 Subject: [PATCH 2/2] fix: update version to v1.5.5 and upgrade sdlc kit to v1.2.1 with enhanced resource metadata Signed-off-by: ainetx --- .bootstrap/config/core.toml | 4 - .gitignore | 367 +++---------------- skills/studio/scripts/studio/commands/kit.py | 43 +-- tests/test_kit.py | 52 +-- tests/test_kit_manifest_install.py | 40 ++ 5 files changed, 99 insertions(+), 407 deletions(-) diff --git a/.bootstrap/config/core.toml b/.bootstrap/config/core.toml index b3ef373f..e0e8b0f7 100644 --- a/.bootstrap/config/core.toml +++ b/.bootstrap/config/core.toml @@ -174,10 +174,6 @@ examples = "feature_example" template = "prd_template" examples = "prd_example" -[kits.sdlc.resources.skill] -path = "config/kits/sdlc/SKILL.md" -kind = "skill" - [kits.sdlc.resources.agents] path = "config/kits/sdlc/AGENTS.md" kind = "rule" diff --git a/.gitignore b/.gitignore index ec020468..52d8f567 100644 --- a/.gitignore +++ b/.gitignore @@ -36,329 +36,54 @@ build/ # Files matched here are owned by Constructor Studio and may be overwritten. .bootstrap/.core/ .bootstrap/.gen/ -.agents/skills/cf-analyze/SKILL.md -.agents/skills/cf-auto-config/SKILL.md -.agents/skills/cf-brainstorm/SKILL.md -.agents/skills/cf-brave-new-world/SKILL.md -.agents/skills/cf-coding/SKILL.md -.agents/skills/cf-debug-prompts/SKILL.md -.agents/skills/cf-explain/SKILL.md -.agents/skills/cf-explore/SKILL.md -.agents/skills/cf-generate/SKILL.md -.agents/skills/cf-help/SKILL.md -.agents/skills/cf-kit/SKILL.md -.agents/skills/cf-map/SKILL.md -.agents/skills/cf-plan/SKILL.md -.agents/skills/cf-sdlc-change-impact-analysis/SKILL.md -.agents/skills/cf-sdlc-decompose/SKILL.md -.agents/skills/cf-sdlc-doc-adr/SKILL.md -.agents/skills/cf-sdlc-doc-design/SKILL.md -.agents/skills/cf-sdlc-doc-feature/SKILL.md -.agents/skills/cf-sdlc-doc-prd/SKILL.md -.agents/skills/cf-sdlc-implement/SKILL.md -.agents/skills/cf-sdlc-migrate-openspec/SKILL.md -.agents/skills/cf-sdlc-pr-review/SKILL.md -.agents/skills/cf-sdlc-pr-status/SKILL.md -.agents/skills/cf-sdlc-reverse-engineer/SKILL.md -.agents/skills/cf-studio/SKILL.md -.agents/skills/cf-workspace/SKILL.md -.agents/skills/cf-write-docs/SKILL.md -.agents/skills/cf-write-skills/SKILL.md -.agents/skills/cf/SKILL.md -.claude/agents/cf-analyze-planner.md -.claude/agents/cf-brainstorm-expert.md -.claude/agents/cf-brainstorm-facilitator.md -.claude/agents/cf-brainstorm-panel.md -.claude/agents/cf-code-bug-finder.md -.claude/agents/cf-codegen.md -.claude/agents/cf-deterministic-validator.md -.claude/agents/cf-diff-scope-resolver.md -.claude/agents/cf-explorer.md -.claude/agents/cf-generate-author-junior.md -.claude/agents/cf-generate-author-lead.md -.claude/agents/cf-generate-author-middle.md -.claude/agents/cf-generate-author-senior.md -.claude/agents/cf-generate-author.md -.claude/agents/cf-generate-coder-casual.md -.claude/agents/cf-generate-coder-smart.md -.claude/agents/cf-generate-collector.md -.claude/agents/cf-generate-planner.md -.claude/agents/cf-generate-prompt-engineer-casual.md -.claude/agents/cf-generate-prompt-engineer-smart.md -.claude/agents/cf-migrate-migrator.md -.claude/agents/cf-migrate-planner.md -.claude/agents/cf-migrate-scanner.md -.claude/agents/cf-migrate-verifier.md -.claude/agents/cf-pdsl-author.md -.claude/agents/cf-pdsl-reviewer.md -.claude/agents/cf-pdsl-transformer.md -.claude/agents/cf-phase-compiler-isolated.md -.claude/agents/cf-phase-compiler.md -.claude/agents/cf-phase-runner-isolated.md -.claude/agents/cf-phase-runner.md -.claude/agents/cf-pr-review.md -.claude/agents/cf-prompt-bug-finder.md -.claude/agents/cf-ralphex.md -.claude/agents/cf-semantic-reviewer-artifact.md -.claude/agents/cf-semantic-reviewer-code.md -.claude/agents/cf-semantic-reviewer-consistency.md -.claude/agents/cf-semantic-reviewer-freeform.md -.claude/agents/cf-semantic-reviewer-prompt.md -.claude/agents/cf-storytelling-context-pack.md -.claude/agents/cf-storytelling-export.md -.claude/agents/cf-storytelling-gate.md -.claude/agents/cf-storytelling-preflight.md -.claude/agents/cf-storytelling-wrap.md -.claude/skills/cf-analyze/SKILL.md -.claude/skills/cf-auto-config/SKILL.md -.claude/skills/cf-brainstorm/SKILL.md -.claude/skills/cf-brave-new-world/SKILL.md -.claude/skills/cf-coding/SKILL.md -.claude/skills/cf-debug-prompts/SKILL.md -.claude/skills/cf-explain/SKILL.md -.claude/skills/cf-explore/SKILL.md -.claude/skills/cf-generate/SKILL.md -.claude/skills/cf-help/SKILL.md -.claude/skills/cf-kit/SKILL.md -.claude/skills/cf-map/SKILL.md -.claude/skills/cf-plan/SKILL.md -.claude/skills/cf-sdlc-change-impact-analysis/SKILL.md -.claude/skills/cf-sdlc-decompose/SKILL.md -.claude/skills/cf-sdlc-doc-adr/SKILL.md -.claude/skills/cf-sdlc-doc-design/SKILL.md -.claude/skills/cf-sdlc-doc-feature/SKILL.md -.claude/skills/cf-sdlc-doc-prd/SKILL.md -.claude/skills/cf-sdlc-implement/SKILL.md -.claude/skills/cf-sdlc-migrate-openspec/SKILL.md -.claude/skills/cf-sdlc-pr-review/SKILL.md -.claude/skills/cf-sdlc-pr-status/SKILL.md -.claude/skills/cf-sdlc-reverse-engineer/SKILL.md -.claude/skills/cf-studio/SKILL.md -.claude/skills/cf-workspace/SKILL.md -.claude/skills/cf-write-docs/SKILL.md -.claude/skills/cf-write-skills/SKILL.md -.claude/skills/cf/SKILL.md +.agents/skills/cf/ +.agents/skills/cf-*/ +.agents/skills/studio-*/ +.agents/skills/cypilot-*/ +.agents/skills/cf-constructor-*/ +.codex/agents/cf*.toml +.codex/agents/studio-*.toml +.codex/agents/cypilot-*.toml +.codex/agents/cf-constructor-*.toml +.codex/agents/storytelling-*.toml .codex/.cf-installed -.codex/agents/cf-analyze-planner.toml -.codex/agents/cf-brainstorm-expert.toml -.codex/agents/cf-brainstorm-facilitator.toml -.codex/agents/cf-brainstorm-panel.toml -.codex/agents/cf-code-bug-finder.toml -.codex/agents/cf-codegen.toml -.codex/agents/cf-deterministic-validator.toml -.codex/agents/cf-diff-scope-resolver.toml -.codex/agents/cf-explorer.toml -.codex/agents/cf-generate-author-junior.toml -.codex/agents/cf-generate-author-lead.toml -.codex/agents/cf-generate-author-middle.toml -.codex/agents/cf-generate-author-senior.toml -.codex/agents/cf-generate-author.toml -.codex/agents/cf-generate-coder-casual.toml -.codex/agents/cf-generate-coder-smart.toml -.codex/agents/cf-generate-collector.toml -.codex/agents/cf-generate-planner.toml -.codex/agents/cf-generate-prompt-engineer-casual.toml -.codex/agents/cf-generate-prompt-engineer-smart.toml -.codex/agents/cf-migrate-migrator.toml -.codex/agents/cf-migrate-planner.toml -.codex/agents/cf-migrate-scanner.toml -.codex/agents/cf-migrate-verifier.toml -.codex/agents/cf-pdsl-author.toml -.codex/agents/cf-pdsl-reviewer.toml -.codex/agents/cf-pdsl-transformer.toml -.codex/agents/cf-phase-compiler-isolated.toml -.codex/agents/cf-phase-compiler.toml -.codex/agents/cf-phase-runner-isolated.toml -.codex/agents/cf-phase-runner.toml -.codex/agents/cf-pr-review.toml -.codex/agents/cf-prompt-bug-finder.toml -.codex/agents/cf-ralphex.toml -.codex/agents/cf-semantic-reviewer-artifact.toml -.codex/agents/cf-semantic-reviewer-code.toml -.codex/agents/cf-semantic-reviewer-consistency.toml -.codex/agents/cf-semantic-reviewer-freeform.toml -.codex/agents/cf-semantic-reviewer-prompt.toml -.codex/agents/cf-storytelling-context-pack.toml -.codex/agents/cf-storytelling-export.toml -.codex/agents/cf-storytelling-gate.toml -.codex/agents/cf-storytelling-preflight.toml -.codex/agents/cf-storytelling-wrap.toml -.cursor/agents/cf-analyze-planner.md -.cursor/agents/cf-brainstorm-expert.md -.cursor/agents/cf-brainstorm-facilitator.md -.cursor/agents/cf-brainstorm-panel.md -.cursor/agents/cf-code-bug-finder.md -.cursor/agents/cf-codegen.md -.cursor/agents/cf-deterministic-validator.md -.cursor/agents/cf-diff-scope-resolver.md -.cursor/agents/cf-explorer.md -.cursor/agents/cf-generate-author-junior.md -.cursor/agents/cf-generate-author-lead.md -.cursor/agents/cf-generate-author-middle.md -.cursor/agents/cf-generate-author-senior.md -.cursor/agents/cf-generate-author.md -.cursor/agents/cf-generate-coder-casual.md -.cursor/agents/cf-generate-coder-smart.md -.cursor/agents/cf-generate-collector.md -.cursor/agents/cf-generate-planner.md -.cursor/agents/cf-generate-prompt-engineer-casual.md -.cursor/agents/cf-generate-prompt-engineer-smart.md -.cursor/agents/cf-migrate-migrator.md -.cursor/agents/cf-migrate-planner.md -.cursor/agents/cf-migrate-scanner.md -.cursor/agents/cf-migrate-verifier.md -.cursor/agents/cf-pdsl-author.md -.cursor/agents/cf-pdsl-reviewer.md -.cursor/agents/cf-pdsl-transformer.md -.cursor/agents/cf-phase-compiler-isolated.md -.cursor/agents/cf-phase-compiler.md -.cursor/agents/cf-phase-runner-isolated.md -.cursor/agents/cf-phase-runner.md -.cursor/agents/cf-pr-review.md -.cursor/agents/cf-prompt-bug-finder.md -.cursor/agents/cf-ralphex.md -.cursor/agents/cf-semantic-reviewer-artifact.md -.cursor/agents/cf-semantic-reviewer-code.md -.cursor/agents/cf-semantic-reviewer-consistency.md -.cursor/agents/cf-semantic-reviewer-freeform.md -.cursor/agents/cf-semantic-reviewer-prompt.md -.cursor/agents/cf-storytelling-context-pack.md -.cursor/agents/cf-storytelling-export.md -.cursor/agents/cf-storytelling-gate.md -.cursor/agents/cf-storytelling-preflight.md -.cursor/agents/cf-storytelling-wrap.md -.cursor/commands/cf-analyze.md -.cursor/commands/cf-auto-config.md -.cursor/commands/cf-brainstorm.md -.cursor/commands/cf-brave-new-world.md -.cursor/commands/cf-coding.md -.cursor/commands/cf-debug-prompts.md -.cursor/commands/cf-explain.md -.cursor/commands/cf-explore.md -.cursor/commands/cf-generate.md -.cursor/commands/cf-help.md -.cursor/commands/cf-kit.md -.cursor/commands/cf-map.md -.cursor/commands/cf-plan.md -.cursor/commands/cf-sdlc-change-impact-analysis.md -.cursor/commands/cf-sdlc-decompose.md -.cursor/commands/cf-sdlc-doc-adr.md -.cursor/commands/cf-sdlc-doc-design.md -.cursor/commands/cf-sdlc-doc-feature.md -.cursor/commands/cf-sdlc-doc-prd.md -.cursor/commands/cf-sdlc-implement.md -.cursor/commands/cf-sdlc-migrate-openspec.md -.cursor/commands/cf-sdlc-pr-review.md -.cursor/commands/cf-sdlc-pr-status.md -.cursor/commands/cf-sdlc-reverse-engineer.md -.cursor/commands/cf-studio.md -.cursor/commands/cf-workspace.md -.cursor/commands/cf-write-docs.md -.cursor/commands/cf-write-skills.md -.cursor/commands/cf.md +.codex/.constructor-studio-installed +.claude/skills/cf/ +.claude/skills/cf-*/ +.claude/commands/cf*.md +.claude/commands/studio-*.md +.claude/commands/cypilot-*.md +.claude/commands/cf-constructor-*.md +.claude/agents/cf*.md +.claude/agents/studio-*.md +.claude/agents/cypilot-*.md +.claude/agents/cf-constructor-*.md +.claude/agents/storytelling-*.md +.cursor/commands/cf*.md +.cursor/commands/studio-*.md +.cursor/commands/cypilot-*.md +.cursor/commands/cf-constructor-*.md +.cursor/agents/cf*.md +.cursor/agents/studio-*.md +.cursor/agents/cypilot-*.md +.cursor/agents/cf-constructor-*.md +.cursor/agents/storytelling-*.md +.github/prompts/cf*.prompt.md +.github/prompts/studio-*.prompt.md +.github/prompts/cypilot-*.prompt.md +.github/prompts/cf-constructor-*.prompt.md +.github/agents/cf*.md +.github/agents/studio-*.md +.github/agents/cypilot-*.md +.github/agents/cf-constructor-*.md +.github/agents/storytelling-*.md .github/.cf-installed -.github/agents/cf-analyze-planner.agent.md -.github/agents/cf-brainstorm-expert.agent.md -.github/agents/cf-brainstorm-facilitator.agent.md -.github/agents/cf-brainstorm-panel.agent.md -.github/agents/cf-code-bug-finder.agent.md -.github/agents/cf-codegen.agent.md -.github/agents/cf-deterministic-validator.agent.md -.github/agents/cf-diff-scope-resolver.agent.md -.github/agents/cf-explorer.agent.md -.github/agents/cf-generate-author-junior.agent.md -.github/agents/cf-generate-author-lead.agent.md -.github/agents/cf-generate-author-middle.agent.md -.github/agents/cf-generate-author-senior.agent.md -.github/agents/cf-generate-author.agent.md -.github/agents/cf-generate-coder-casual.agent.md -.github/agents/cf-generate-coder-smart.agent.md -.github/agents/cf-generate-collector.agent.md -.github/agents/cf-generate-planner.agent.md -.github/agents/cf-generate-prompt-engineer-casual.agent.md -.github/agents/cf-generate-prompt-engineer-smart.agent.md -.github/agents/cf-migrate-migrator.agent.md -.github/agents/cf-migrate-planner.agent.md -.github/agents/cf-migrate-scanner.agent.md -.github/agents/cf-migrate-verifier.agent.md -.github/agents/cf-pdsl-author.agent.md -.github/agents/cf-pdsl-reviewer.agent.md -.github/agents/cf-pdsl-transformer.agent.md -.github/agents/cf-phase-compiler-isolated.agent.md -.github/agents/cf-phase-compiler.agent.md -.github/agents/cf-phase-runner-isolated.agent.md -.github/agents/cf-phase-runner.agent.md -.github/agents/cf-pr-review.agent.md -.github/agents/cf-prompt-bug-finder.agent.md -.github/agents/cf-ralphex.agent.md -.github/agents/cf-semantic-reviewer-artifact.agent.md -.github/agents/cf-semantic-reviewer-code.agent.md -.github/agents/cf-semantic-reviewer-consistency.agent.md -.github/agents/cf-semantic-reviewer-freeform.agent.md -.github/agents/cf-semantic-reviewer-prompt.agent.md -.github/agents/cf-storytelling-context-pack.agent.md -.github/agents/cf-storytelling-export.agent.md -.github/agents/cf-storytelling-gate.agent.md -.github/agents/cf-storytelling-preflight.agent.md -.github/agents/cf-storytelling-wrap.agent.md -.github/prompts/cf-analyze.prompt.md -.github/prompts/cf-auto-config.prompt.md -.github/prompts/cf-brainstorm.prompt.md -.github/prompts/cf-brave-new-world.prompt.md -.github/prompts/cf-coding.prompt.md -.github/prompts/cf-debug-prompts.prompt.md -.github/prompts/cf-explain.prompt.md -.github/prompts/cf-explore.prompt.md -.github/prompts/cf-generate.prompt.md -.github/prompts/cf-help.prompt.md -.github/prompts/cf-kit.prompt.md -.github/prompts/cf-map.prompt.md -.github/prompts/cf-plan.prompt.md -.github/prompts/cf-sdlc-change-impact-analysis.prompt.md -.github/prompts/cf-sdlc-decompose.prompt.md -.github/prompts/cf-sdlc-doc-adr.prompt.md -.github/prompts/cf-sdlc-doc-design.prompt.md -.github/prompts/cf-sdlc-doc-feature.prompt.md -.github/prompts/cf-sdlc-doc-prd.prompt.md -.github/prompts/cf-sdlc-implement.prompt.md -.github/prompts/cf-sdlc-migrate-openspec.prompt.md -.github/prompts/cf-sdlc-pr-review.prompt.md -.github/prompts/cf-sdlc-pr-status.prompt.md -.github/prompts/cf-sdlc-reverse-engineer.prompt.md -.github/prompts/cf-studio.prompt.md -.github/prompts/cf-workspace.prompt.md -.github/prompts/cf-write-docs.prompt.md -.github/prompts/cf-write-skills.prompt.md -.github/prompts/cf.prompt.md -.windsurf/workflows/cf-analyze.md -.windsurf/workflows/cf-auto-config.md -.windsurf/workflows/cf-brainstorm.md -.windsurf/workflows/cf-brave-new-world.md -.windsurf/workflows/cf-coding.md -.windsurf/workflows/cf-debug-prompts.md -.windsurf/workflows/cf-explain.md -.windsurf/workflows/cf-explore.md -.windsurf/workflows/cf-generate.md -.windsurf/workflows/cf-help.md -.windsurf/workflows/cf-kit.md -.windsurf/workflows/cf-map.md -.windsurf/workflows/cf-plan.md -.windsurf/workflows/cf-sdlc-change-impact-analysis.md -.windsurf/workflows/cf-sdlc-decompose.md -.windsurf/workflows/cf-sdlc-doc-adr.md -.windsurf/workflows/cf-sdlc-doc-design.md -.windsurf/workflows/cf-sdlc-doc-feature.md -.windsurf/workflows/cf-sdlc-doc-prd.md -.windsurf/workflows/cf-sdlc-implement.md -.windsurf/workflows/cf-sdlc-migrate-openspec.md -.windsurf/workflows/cf-sdlc-pr-review.md -.windsurf/workflows/cf-sdlc-pr-status.md -.windsurf/workflows/cf-sdlc-reverse-engineer.md -.windsurf/workflows/cf-studio.md -.windsurf/workflows/cf-workspace.md -.windsurf/workflows/cf-write-docs.md -.windsurf/workflows/cf-write-skills.md -.windsurf/workflows/cf.md +.github/.constructor-studio-installed +.github/copilot-instructions.md +.windsurf/workflows/cf*.md +.windsurf/workflows/studio-*.md +.windsurf/workflows/cypilot-*.md +.windsurf/workflows/cf-constructor-*.md .bootstrap/config/kits/sdlc/ # END Constructor Studio diff --git a/skills/studio/scripts/studio/commands/kit.py b/skills/studio/scripts/studio/commands/kit.py index f17be043..7f4c4276 100644 --- a/skills/studio/scripts/studio/commands/kit.py +++ b/skills/studio/scripts/studio/commands/kit.py @@ -687,6 +687,7 @@ def _is_registered_kit_path_absolute(registered_kit_path: str) -> bool: _is_posix_absolute_path(registered_kit_path) or _is_windows_absolute_path(registered_kit_path) ) +# @cpt-end:cpt-studio-algo-kit-content-mgmt:p1:inst-collect-metadata-fn # @cpt-begin:cpt-studio-algo-kit-manifest-install:p1:inst-manifest-persist-relative-only @@ -916,32 +917,6 @@ def _registered_slug_for_local_kit_path( return matches[0] if len(matches) == 1 else "" -def _collect_kit_metadata( - config_kit_dir: Optional[Path], - kit_slug: str, - registered_kit_path: Optional[str] = None, -) -> Dict[str, str]: - """Read installed kit files and return metadata for .gen/ aggregation. - - Returns dict with: - agents_content — raw content of kit's AGENTS.md for ``.gen/AGENTS.md`` - """ - # @cpt-begin:cpt-studio-algo-kit-content-mgmt:p1:inst-collect-metadata - del kit_slug, registered_kit_path - result: Dict[str, str] = {"skill_nav": "", "agents_content": ""} - - agents_path = config_kit_dir / _KIT_AGENTS_FILE if config_kit_dir is not None else None - if agents_path is not None and agents_path.is_file(): - try: - result["agents_content"] = agents_path.read_text(encoding="utf-8") - except OSError: - pass - - return result - # @cpt-end:cpt-studio-algo-kit-content-mgmt:p1:inst-collect-metadata -# @cpt-end:cpt-studio-algo-kit-content-mgmt:p1:inst-collect-metadata-fn - - def _binding_is_public_metadata_resource(binding: Dict[str, Any], kind: str) -> bool: public_value = binding.get("public") if isinstance(public_value, bool): @@ -951,6 +926,7 @@ def _binding_is_public_metadata_resource(binding: Dict[str, Any], kind: str) -> return kind in {"skill", "rule"} +# @cpt-begin:cpt-studio-algo-kit-content-mgmt:p1:inst-collect-metadata def _collect_registered_kit_metadata( studio_dir: Path, kit_slug: str, @@ -1029,6 +1005,7 @@ def _collect_registered_kit_metadata( pass result["agents_content"] = "\n\n".join(part for part in agents_parts if part) return result +# @cpt-end:cpt-studio-algo-kit-content-mgmt:p1:inst-collect-metadata # --------------------------------------------------------------------------- @@ -4235,19 +4212,23 @@ def _sync_manifest_resource_bindings( config_dir: Path, kit_slug: str, ) -> Optional[Dict[str, Dict[str, Any]]]: - """Merge existing resource bindings with any new manifest resources. + """Sync resource bindings to the current manifest declaration set. - Returns merged bindings dict, or None if there is no manifest. + Returns synced bindings dict, or None if there is no manifest. """ if manifest is None: return None existing_raw = _read_kits_from_core_toml(config_dir).get(kit_slug, {}).get("resources", {}) - merged: Dict[str, Dict[str, Any]] = {} + existing: Dict[str, Dict[str, Any]] = {} for res_id, binding in existing_raw.items(): if isinstance(binding, dict): - merged[res_id] = binding + existing[res_id] = dict(binding) elif isinstance(binding, str): - merged[res_id] = {"path": binding} + existing[res_id] = {"path": binding} + merged: Dict[str, Dict[str, Any]] = {} + for res in getattr(manifest, "resources", []): + if getattr(res, "id", None) in existing: + merged[str(res.id)] = dict(existing[str(res.id)]) kit_root_rel = _resolve_manifest_kit_root_rel(manifest, merged, kit_slug) for res in getattr(manifest, "resources", []): if res.id not in merged: diff --git a/tests/test_kit.py b/tests/test_kit.py index 2d779b35..11ab372d 100644 --- a/tests/test_kit.py +++ b/tests/test_kit.py @@ -5092,57 +5092,7 @@ def test_existing_dir_overwritten(self): self.assertFalse((dst / "artifacts" / "PRD" / "old.md").exists()) -# --------------------------------------------------------------------------- -# _collect_kit_metadata OSError -# --------------------------------------------------------------------------- - -class TestCollectKitMetadataOsError(unittest.TestCase): - def test_agents_read_oserror(self): - from studio.commands.kit import _collect_kit_metadata - with TemporaryDirectory() as td: - kit_dir = Path(td) / "sdlc" - kit_dir.mkdir() - agents = kit_dir / "AGENTS.md" - agents.mkdir() # directory, not file — read will fail - meta = _collect_kit_metadata(kit_dir, "sdlc") - self.assertEqual(meta["agents_content"], "") - - def test_skill_nav_ignores_registered_custom_path(self): - from studio.commands.kit import _collect_kit_metadata - with TemporaryDirectory() as td: - kit_dir = Path(td) / "custom-kits" / "sdlc" - kit_dir.mkdir(parents=True) - (kit_dir / "SKILL.md").write_text("# Skill\n", encoding="utf-8") - meta = _collect_kit_metadata(kit_dir, "sdlc", "custom-kits/sdlc") - self.assertEqual(meta["skill_nav"], "") - - def test_skill_nav_ignores_absolute_registered_custom_path(self): - from studio.commands.kit import _collect_kit_metadata - with TemporaryDirectory() as td: - kit_dir = Path(td) / "custom-kits" / "sdlc" - kit_dir.mkdir(parents=True) - (kit_dir / "SKILL.md").write_text("# Skill\n", encoding="utf-8") - meta = _collect_kit_metadata(kit_dir, "sdlc", kit_dir.as_posix()) - self.assertEqual(meta["skill_nav"], "") - - def test_skill_nav_ignores_windows_drive_registered_custom_path(self): - from studio.commands.kit import _collect_kit_metadata - with TemporaryDirectory() as td: - kit_dir = Path(td) / "custom-kits" / "sdlc" - kit_dir.mkdir(parents=True) - (kit_dir / "SKILL.md").write_text("# Skill\n", encoding="utf-8") - meta = _collect_kit_metadata(kit_dir, "sdlc", "C:/external-kits/sdlc") - self.assertEqual(meta["skill_nav"], "") - - def test_skill_nav_ignores_windows_backslash_registered_custom_path(self): - from studio.commands.kit import _collect_kit_metadata - with TemporaryDirectory() as td: - kit_dir = Path(td) / "custom-kits" / "sdlc" - kit_dir.mkdir(parents=True) - (kit_dir / "SKILL.md").write_text("# Skill\n", encoding="utf-8") - meta = _collect_kit_metadata(kit_dir, "sdlc", r"C:\external-kits\sdlc") - self.assertEqual(meta["skill_nav"], "") - +class TestCollectRegisteredKitMetadata(unittest.TestCase): def test_registered_resource_metadata_uses_public_bindings_only(self): from studio.commands.kit import _collect_registered_kit_metadata diff --git a/tests/test_kit_manifest_install.py b/tests/test_kit_manifest_install.py index 2bbe40a7..fe78af7f 100644 --- a/tests/test_kit_manifest_install.py +++ b/tests/test_kit_manifest_install.py @@ -281,6 +281,46 @@ def test_sync_manifest_resource_bindings_preserves_artifact_bindings(self): ) self.assertNotIn("artifacts", merged["feature-template"]) + def test_sync_manifest_resource_bindings_drops_removed_resources(self): + from studio.commands.kit import _sync_manifest_resource_bindings + from studio.utils import toml_utils + + with TemporaryDirectory() as td: + config = Path(td) / "config" + config.mkdir() + toml_utils.dump({ + "version": "1.0", + "project_root": "..", + "kits": { + "mykit": { + "format": "CFS", + "path": "config/kits/mykit", + "resources": { + "skill": {"path": "config/kits/mykit/SKILL.md"}, + "stale": {"path": "config/kits/mykit/stale.md"}, + }, + }, + }, + }, config / "core.toml") + manifest = SimpleNamespace( + root="{cf-studio-path}/config/kits/{slug}", + resources=[ + SimpleNamespace( + id="skill", + kind="skill", + install_path="SKILL.md", + default_path="SKILL.md", + public=False, + artifact_bindings={}, + ), + ], + ) + + merged = _sync_manifest_resource_bindings(manifest, config, "mykit") + + assert merged is not None + self.assertEqual(set(merged.keys()), {"skill"}) + def test_manifest_resource_bindings_writer_persists_artifact_bindings(self): from studio.commands.kit import _manifest_resource_bindings