vstack 2.0.3__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. {vstack-2.0.3 → vstack-2.1.0}/PKG-INFO +10 -2
  2. {vstack-2.0.3 → vstack-2.1.0}/README-pypi.md +9 -1
  3. {vstack-2.0.3 → vstack-2.1.0}/pyproject.toml +1 -1
  4. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/artifacts/protocol.py +6 -3
  5. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/install.py +83 -12
  6. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/parser.py +11 -4
  7. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/service.py +13 -10
  8. {vstack-2.0.3 → vstack-2.1.0}/LICENSE +0 -0
  9. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/__init__.py +0 -0
  10. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/__main__.py +0 -0
  11. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/_partials/agent-skill-boundary.md +0 -0
  12. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/architect/config.yaml +0 -0
  13. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/architect/template.md +0 -0
  14. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/designer/config.yaml +0 -0
  15. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/designer/template.md +0 -0
  16. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/engineer/config.yaml +0 -0
  17. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/engineer/template.md +0 -0
  18. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/product/config.yaml +0 -0
  19. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/product/template.md +0 -0
  20. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/release/config.yaml +0 -0
  21. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/release/template.md +0 -0
  22. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/tester/config.yaml +0 -0
  23. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/agents/tester/template.md +0 -0
  24. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/instructions/git/config.yaml +0 -0
  25. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/instructions/git/template.md +0 -0
  26. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/instructions/python/config.yaml +0 -0
  27. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/instructions/python/template.md +0 -0
  28. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/prompts/code-review/config.yaml +0 -0
  29. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/prompts/code-review/template.md +0 -0
  30. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/_partials/base-branch.md +0 -0
  31. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/_partials/observability-checklist.md +0 -0
  32. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/_partials/run-tests.md +0 -0
  33. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/_partials/skill-context.md +0 -0
  34. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/adr/config.yaml +0 -0
  35. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/adr/template.md +0 -0
  36. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/analyse/config.yaml +0 -0
  37. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/analyse/template.md +0 -0
  38. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/architecture/config.yaml +0 -0
  39. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/architecture/template.md +0 -0
  40. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/cicd/config.yaml +0 -0
  41. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/cicd/template.md +0 -0
  42. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/code-review/config.yaml +0 -0
  43. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/code-review/template.md +0 -0
  44. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/concise/config.yaml +0 -0
  45. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/concise/template.md +0 -0
  46. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/consult/config.yaml +0 -0
  47. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/consult/template.md +0 -0
  48. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/container/config.yaml +0 -0
  49. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/container/template.md +0 -0
  50. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/debug/config.yaml +0 -0
  51. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/debug/template.md +0 -0
  52. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/dependency/config.yaml +0 -0
  53. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/dependency/template.md +0 -0
  54. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/design/config.yaml +0 -0
  55. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/design/template.md +0 -0
  56. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/docs/config.yaml +0 -0
  57. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/docs/template.md +0 -0
  58. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/explore/config.yaml +0 -0
  59. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/explore/template.md +0 -0
  60. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/guardrails/config.yaml +0 -0
  61. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/guardrails/template.md +0 -0
  62. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/incident/config.yaml +0 -0
  63. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/incident/template.md +0 -0
  64. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/inspect/config.yaml +0 -0
  65. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/inspect/template.md +0 -0
  66. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/migrate/config.yaml +0 -0
  67. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/migrate/template.md +0 -0
  68. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/onboard/config.yaml +0 -0
  69. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/onboard/template.md +0 -0
  70. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/openapi/config.yaml +0 -0
  71. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/openapi/template.md +0 -0
  72. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/performance/config.yaml +0 -0
  73. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/performance/template.md +0 -0
  74. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/pr/config.yaml +0 -0
  75. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/pr/template.md +0 -0
  76. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/refactor/config.yaml +0 -0
  77. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/refactor/template.md +0 -0
  78. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/release-notes/config.yaml +0 -0
  79. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/release-notes/template.md +0 -0
  80. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/requirements/config.yaml +0 -0
  81. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/requirements/template.md +0 -0
  82. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/security/config.yaml +0 -0
  83. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/security/template.md +0 -0
  84. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/verify/config.yaml +0 -0
  85. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/verify/template.md +0 -0
  86. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/vision/config.yaml +0 -0
  87. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/_templates/skills/vision/template.md +0 -0
  88. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/agents/__init__.py +0 -0
  89. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/agents/config.py +0 -0
  90. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/agents/constants.py +0 -0
  91. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/agents/generator.py +0 -0
  92. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/artifacts/__init__.py +0 -0
  93. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/artifacts/config.py +0 -0
  94. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/artifacts/constants.py +0 -0
  95. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/artifacts/generator.py +0 -0
  96. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/artifacts/models.py +0 -0
  97. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/__init__.py +0 -0
  98. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/base.py +0 -0
  99. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/catalog.py +0 -0
  100. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/constants.py +0 -0
  101. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/helpers.py +0 -0
  102. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/interface.py +0 -0
  103. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/manifest.py +0 -0
  104. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/registry.py +0 -0
  105. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/report.py +0 -0
  106. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/status.py +0 -0
  107. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/uninstall.py +0 -0
  108. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/validate.py +0 -0
  109. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/cli/verify.py +0 -0
  110. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/constants.py +0 -0
  111. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/frontmatter/__init__.py +0 -0
  112. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/frontmatter/parser.py +0 -0
  113. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/frontmatter/schema.py +0 -0
  114. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/frontmatter/serializer.py +0 -0
  115. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/instructions/__init__.py +0 -0
  116. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/instructions/config.py +0 -0
  117. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/instructions/constants.py +0 -0
  118. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/instructions/generator.py +0 -0
  119. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/main.py +0 -0
  120. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/manifest/__init__.py +0 -0
  121. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/manifest/store.py +0 -0
  122. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/models.py +0 -0
  123. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/prompts/__init__.py +0 -0
  124. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/prompts/config.py +0 -0
  125. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/prompts/constants.py +0 -0
  126. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/prompts/generator.py +0 -0
  127. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/skills/__init__.py +0 -0
  128. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/skills/config.py +0 -0
  129. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/skills/constants.py +0 -0
  130. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/skills/generator.py +0 -0
  131. {vstack-2.0.3 → vstack-2.1.0}/src/vstack/skills/models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vstack
3
- Version: 2.0.3
3
+ Version: 2.1.0
4
4
  Summary: VS Code-native AI engineering workflow system for microservices, libraries, and backend systems.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -108,7 +108,15 @@ Profile-wide install (optional defaults for all projects):
108
108
  vstack install --global
109
109
  ```
110
110
 
111
- By default, `vstack install` preserves existing unmanaged files and local edits to tracked files by comparing the current file contents with the SHA-256 checksum recorded in `vstack.json`. Use `--adopt-name <artifact-name>` to start tracking one existing unmanaged file without overwriting it. `vstack uninstall` also preserves locally modified tracked files unless you explicitly pass `--force` or `--force-name <artifact-name>`. Use `vstack manifest status --target ...` (or `vstack status --target ...`) to see what still matches the manifest. If a legacy manifest schema is detected, run `vstack manifest upgrade --target ...` first.
111
+ By default, `vstack install` preserves existing unmanaged files and local edits to tracked files by comparing the current file contents with the SHA-256 checksum recorded in `vstack.json`. Use `--adopt-name <name>` to start tracking one existing unmanaged file without overwriting it. `vstack uninstall` also preserves locally modified tracked files unless you explicitly pass `--force` or `--force-name <name>`. Use `vstack manifest status --target ...` (or `vstack status --target ...`) to see what still matches the manifest. If a legacy manifest schema is detected, run `vstack manifest upgrade --target ...` first.
112
+
113
+ If you already have agents, skills, or other files in `.github/`, run a dry-run first to see what would be preserved before committing:
114
+
115
+ ```bash
116
+ vstack install --dry-run --target /path/to/your/project
117
+ ```
118
+
119
+ The summary lists preserved files as `type/name` selectors (e.g. `agent/engineer`). Resolve each conflict with `--force-name type/name` to overwrite, `--adopt-name type/name` to take ownership without overwriting, or `--force` to overwrite everything.
112
120
 
113
121
  ## Fast troubleshooting
114
122
 
@@ -81,7 +81,15 @@ Profile-wide install (optional defaults for all projects):
81
81
  vstack install --global
82
82
  ```
83
83
 
84
- By default, `vstack install` preserves existing unmanaged files and local edits to tracked files by comparing the current file contents with the SHA-256 checksum recorded in `vstack.json`. Use `--adopt-name <artifact-name>` to start tracking one existing unmanaged file without overwriting it. `vstack uninstall` also preserves locally modified tracked files unless you explicitly pass `--force` or `--force-name <artifact-name>`. Use `vstack manifest status --target ...` (or `vstack status --target ...`) to see what still matches the manifest. If a legacy manifest schema is detected, run `vstack manifest upgrade --target ...` first.
84
+ By default, `vstack install` preserves existing unmanaged files and local edits to tracked files by comparing the current file contents with the SHA-256 checksum recorded in `vstack.json`. Use `--adopt-name <name>` to start tracking one existing unmanaged file without overwriting it. `vstack uninstall` also preserves locally modified tracked files unless you explicitly pass `--force` or `--force-name <name>`. Use `vstack manifest status --target ...` (or `vstack status --target ...`) to see what still matches the manifest. If a legacy manifest schema is detected, run `vstack manifest upgrade --target ...` first.
85
+
86
+ If you already have agents, skills, or other files in `.github/`, run a dry-run first to see what would be preserved before committing:
87
+
88
+ ```bash
89
+ vstack install --dry-run --target /path/to/your/project
90
+ ```
91
+
92
+ The summary lists preserved files as `type/name` selectors (e.g. `agent/engineer`). Resolve each conflict with `--force-name type/name` to overwrite, `--adopt-name type/name` to take ownership without overwriting, or `--force` to overwrite everything.
85
93
 
86
94
  ## Fast troubleshooting
87
95
 
@@ -32,7 +32,7 @@ classifiers = [
32
32
  "Topic :: Software Development :: Build Tools",
33
33
  "Topic :: Software Development :: Libraries :: Python Modules",
34
34
  ]
35
- version = "2.0.3"
35
+ version = "2.1.0"
36
36
 
37
37
  [project.urls]
38
38
  Homepage = "https://github.com/eschaar/vstack"
@@ -15,9 +15,12 @@ from vstack.models import ValidationResult
15
15
 
16
16
 
17
17
  class ArtifactGenerator(Protocol):
18
- """Structural protocol satisfied by :class:`~vstack.skills.generator.SkillGenerator`
19
- and :class:`~vstack.agents.generator.AgentGenerator` (and future generators for
20
- prompts, instructions, etc.).
18
+ """Structural protocol satisfied by all artifact generators.
19
+
20
+ Implemented by :class:`~vstack.skills.generator.SkillGenerator`,
21
+ :class:`~vstack.agents.generator.AgentGenerator`,
22
+ :class:`~vstack.instructions.generator.InstructionGenerator`, and
23
+ :class:`~vstack.prompts.generator.PromptGenerator`.
21
24
  """
22
25
 
23
26
  def generate(self, output_dir: Path) -> ArtifactResult:
@@ -128,22 +128,21 @@ class InstallCommand(BaseCommand):
128
128
  """Print install/update/skip output line for one artifact."""
129
129
  if action == "adopt":
130
130
  print(
131
- f" {colors.CYAN}≈{colors.RESET} {rel}"
131
+ f" {prefix}{colors.CYAN}≈{colors.RESET} {rel}"
132
132
  f" {colors.DIM}adopted — {reason}{colors.RESET}"
133
133
  )
134
134
  return
135
135
 
136
136
  if action == "preserve":
137
- force_hint = " Use --force or --force-name for this artifact."
138
137
  print(
139
- f" {colors.YELLOW}↷{colors.RESET} {rel}"
140
- f" {colors.DIM}preserved — {reason}.{force_hint}{colors.RESET}"
138
+ f" {prefix}{colors.YELLOW}↷{colors.RESET} {rel}"
139
+ f" {colors.DIM}preserved — {reason}{colors.RESET}"
141
140
  )
142
141
  return
143
142
 
144
143
  if action == "skip":
145
144
  print(
146
- f" {colors.YELLOW}↷{colors.RESET} {rel}"
145
+ f" {prefix}{colors.YELLOW}↷{colors.RESET} {rel}"
147
146
  f" {colors.DIM}skipped — already v{existing_version}{colors.RESET}"
148
147
  )
149
148
  return
@@ -245,16 +244,24 @@ class InstallCommand(BaseCommand):
245
244
  existing_entries,
246
245
  new_entries,
247
246
  checksum_algorithm: str,
248
- ) -> None:
249
- """Apply install decision flow for one rendered artifact."""
247
+ ) -> str:
248
+ """Apply install decision flow for one rendered artifact and return the action taken."""
250
249
  out_file = out_dir / gen.output_path(artifact.name)
251
250
  new_version = (artifact.frontmatter or {}).get("version") or VERSION
252
251
  key = f"{gen.config.type_name}/{artifact.name}"
253
252
  existing_entry = existing_entries.get(key)
254
253
  existing_version = existing_entry.version if existing_entry is not None else None
255
254
  rel = service.label(out_file)
256
- force_name = artifact.name in targeted_force_names or rel in targeted_force_names
257
- adopt_name = artifact.name in targeted_adopt_names or rel in targeted_adopt_names
255
+ force_name = (
256
+ artifact.name in targeted_force_names
257
+ or key in targeted_force_names
258
+ or rel in targeted_force_names
259
+ )
260
+ adopt_name = (
261
+ artifact.name in targeted_adopt_names
262
+ or key in targeted_adopt_names
263
+ or rel in targeted_adopt_names
264
+ )
258
265
  adopted_values: tuple[str | None, str] | None = None
259
266
 
260
267
  if artifact.unresolved:
@@ -306,7 +313,7 @@ class InstallCommand(BaseCommand):
306
313
  checksum=content_hash(artifact.content),
307
314
  checksum_algorithm=checksum_algorithm,
308
315
  )
309
- return
316
+ return action
310
317
 
311
318
  if action == "adopt" and adopted_values is not None:
312
319
  adopted_version, adopted_checksum = adopted_values
@@ -318,7 +325,7 @@ class InstallCommand(BaseCommand):
318
325
  checksum=adopted_checksum,
319
326
  checksum_algorithm=checksum_algorithm,
320
327
  )
321
- return
328
+ return action
322
329
 
323
330
  if existing_entry is not None:
324
331
  preserve_existing_entry(
@@ -326,6 +333,7 @@ class InstallCommand(BaseCommand):
326
333
  manifest_key=gen.config.manifest_key,
327
334
  existing_entry=existing_entry,
328
335
  )
336
+ return action
329
337
 
330
338
  @staticmethod
331
339
  def _write_manifest(
@@ -347,6 +355,57 @@ class InstallCommand(BaseCommand):
347
355
  manifest_file.write(manifest)
348
356
  print(f" {colors.DIM}wrote {service.label(manifest_file.path)}{colors.RESET}")
349
357
 
358
+ @staticmethod
359
+ def _print_summary(
360
+ *,
361
+ colors,
362
+ action_counts: dict[str, int],
363
+ preserved_selectors: list[str],
364
+ dry_run: bool,
365
+ ) -> None:
366
+ """Print a readable summary and conflict guidance after an install run."""
367
+ installed = action_counts.get("install", 0)
368
+ updated = action_counts.get("update", 0)
369
+ preserved = action_counts.get("preserve", 0)
370
+ skipped = action_counts.get("skip", 0)
371
+ adopted = action_counts.get("adopt", 0)
372
+
373
+ install_label = "installed"
374
+ summary_title = "Summary (dry-run)" if dry_run else "Summary"
375
+ total = installed + updated + preserved + skipped + adopted
376
+
377
+ print()
378
+ print(f" {colors.BOLD}{summary_title}{colors.RESET}")
379
+ print(f" total processed : {colors.BOLD}{total}{colors.RESET}")
380
+ print(f" {install_label:<15}: {colors.BOLD}{installed}{colors.RESET}")
381
+ print(f" updated : {colors.BOLD}{updated}{colors.RESET}")
382
+ print(f" preserved : {colors.BOLD}{preserved}{colors.RESET}")
383
+ print(f" skipped : {colors.BOLD}{skipped}{colors.RESET}")
384
+ print(f" adopted : {colors.BOLD}{adopted}{colors.RESET}")
385
+
386
+ if preserved:
387
+ noun = "file" if preserved == 1 else "files"
388
+ selectors_suffix = " Preserved selectors:" if preserved_selectors else ""
389
+ print()
390
+ print(
391
+ f" {colors.YELLOW}⚠{colors.RESET} "
392
+ f"{preserved} {noun} preserved — existing files were not overwritten."
393
+ f"{selectors_suffix}"
394
+ )
395
+ if preserved_selectors:
396
+ for selector in sorted(preserved_selectors):
397
+ print(f" - {selector}")
398
+ print(" Next steps:")
399
+ print(f" {colors.DIM}--force{colors.RESET} overwrite all")
400
+ print(
401
+ f" {colors.DIM}--force-name <name|type/name>{colors.RESET} "
402
+ "overwrite one artifact"
403
+ )
404
+ print(
405
+ f" {colors.DIM}--adopt-name <name|type/name>{colors.RESET} "
406
+ "take ownership without overwriting"
407
+ )
408
+
350
409
  @staticmethod
351
410
  def execute(
352
411
  service: CommandService,
@@ -377,13 +436,15 @@ class InstallCommand(BaseCommand):
377
436
 
378
437
  prefix = f"{colors.DIM}[dry-run]{colors.RESET} " if dry_run else ""
379
438
  all_ok = True
439
+ action_counts: dict[str, int] = {}
440
+ preserved_selectors: set[str] = set()
380
441
 
381
442
  for gen in gens:
382
443
  out_dir = install_dir / gen.config.output_subdir
383
444
  artifacts = gen.render_all()
384
445
 
385
446
  for artifact in artifacts:
386
- InstallCommand._install_single_artifact(
447
+ artifact_action = InstallCommand._install_single_artifact(
387
448
  service=service,
388
449
  gen=gen,
389
450
  artifact=artifact,
@@ -399,6 +460,9 @@ class InstallCommand(BaseCommand):
399
460
  new_entries=new_entries,
400
461
  checksum_algorithm=checksum_algorithm,
401
462
  )
463
+ action_counts[artifact_action] = action_counts.get(artifact_action, 0) + 1
464
+ if artifact_action == "preserve":
465
+ preserved_selectors.add(f"{gen.config.type_name}/{artifact.name}")
402
466
 
403
467
  # Verify source for unresolvable issues.
404
468
  verify_result = gen.verify_input()
@@ -407,6 +471,13 @@ class InstallCommand(BaseCommand):
407
471
  print(f" ERROR [{gen.config.type_name}]: {msg.message}", file=sys.stderr)
408
472
  all_ok = False
409
473
 
474
+ InstallCommand._print_summary(
475
+ colors=colors,
476
+ action_counts=action_counts,
477
+ preserved_selectors=sorted(preserved_selectors),
478
+ dry_run=dry_run,
479
+ )
480
+
410
481
  if not dry_run:
411
482
  InstallCommand._write_manifest(
412
483
  service=service,
@@ -177,17 +177,24 @@ class CommandLineParser:
177
177
  "--force-name",
178
178
  dest="force_names",
179
179
  action="append",
180
- metavar="<artifact-name>",
181
- help="Force install one named artifact without overwriting everything",
180
+ metavar="<name|type/name>",
181
+ help=(
182
+ "Force install one named artifact without overwriting everything. "
183
+ "Accepts a plain name (e.g. engineer) or a type/name selector "
184
+ "(e.g. agent/engineer) to disambiguate when multiple artifact types "
185
+ "share the same name. Repeat to target multiple artifacts."
186
+ ),
182
187
  )
183
188
  parser.add_argument(
184
189
  "--adopt-name",
185
190
  action="append",
186
191
  default=None,
187
- metavar="NAME",
192
+ metavar="<name|type/name>",
188
193
  help=(
189
194
  "Adopt only the named existing unmanaged artifact into the manifest without overwriting it. "
190
- "Repeat this option to target multiple names."
195
+ "Accepts a plain name (e.g. engineer) or a type/name selector "
196
+ "(e.g. agent/engineer) to disambiguate when multiple artifact types "
197
+ "share the same name. Repeat to target multiple artifacts."
191
198
  ),
192
199
  )
193
200
  parser.add_argument(
@@ -1,13 +1,16 @@
1
- """All vstack CLI commands as instance methods on :class:`CommandService`.
2
-
3
- The CLI is entirely type-generic: it iterates over
4
- :data:`~vstack.artifacts.type_config.KNOWN_TYPES` for validate, install, and
5
- verify instead of hard-coding skill-specific and agent-specific logic.
6
-
7
- All commands that touch the install root accept a single ``install_dir``
8
- (either workspace ``.github/`` or the VS Code user profile directory)
9
- rather than separate ``skills_dir`` / ``agents_dir`` parameters — per-type
10
- sub-directories are derived from
1
+ """Shared coordinator for vstack CLI command handlers.
2
+
3
+ :class:`CommandService` wires artifact-type generators and manifest access
4
+ into the services that individual :class:`~vstack.cli.base.BaseCommand`
5
+ subclasses use when executing. Commands live in their own modules
6
+ (``install``, ``verify``, ``uninstall``, ``validate``, ``manifest``,
7
+ ``status``) and receive a :class:`~vstack.cli.base.CommandContext` at
8
+ dispatch time.
9
+
10
+ The service is type-generic: it iterates over
11
+ :data:`~vstack.artifacts.type_config.KNOWN_TYPES` rather than hard-coding
12
+ skill-specific or agent-specific logic. Per-type sub-directories are
13
+ derived from
11
14
  :attr:`~vstack.artifacts.type_config.ArtifactTypeConfig.output_subdir`.
12
15
  """
13
16
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes