devspec 0.1.0__py3-none-any.whl
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.
- devspec-0.1.0.dist-info/METADATA +522 -0
- devspec-0.1.0.dist-info/RECORD +133 -0
- devspec-0.1.0.dist-info/WHEEL +4 -0
- devspec-0.1.0.dist-info/entry_points.txt +2 -0
- devspec-0.1.0.dist-info/licenses/LICENSE +201 -0
- devspec_installer/__init__.py +3 -0
- devspec_installer/__main__.py +5 -0
- devspec_installer/cli.py +615 -0
- devspec_installer/payload/.agents/rules/devspec-workflow.md +25 -0
- devspec_installer/payload/.agents/skills/devspec-clarify.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-codebase-structure.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-coding-standards.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-diagram.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-extract.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-finalize.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-implement.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-projectcontext.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-review.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-rules.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-story.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-tasks.md +14 -0
- devspec_installer/payload/.agents/skills/devspec-techstack.md +14 -0
- devspec_installer/payload/.claude/skills/devspec-clarify/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-codebase-structure/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-coding-standards/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-diagram/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-extract/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-finalize/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-implement/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-projectcontext/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-review/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-rules/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-story/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-tasks/SKILL.md +13 -0
- devspec_installer/payload/.claude/skills/devspec-techstack/SKILL.md +13 -0
- devspec_installer/payload/.cursor/rules/devspec-workflow.mdc +23 -0
- devspec_installer/payload/.gemini/commands/devspec/clarify.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/codebase-structure.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/coding-standards.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/diagram.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/extract.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/finalize.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/implement.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/projectcontext.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/review.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/rules.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/story.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/tasks.toml +14 -0
- devspec_installer/payload/.gemini/commands/devspec/techstack.toml +14 -0
- devspec_installer/payload/.github/agents/devspec.clarify.agent.md +39 -0
- devspec_installer/payload/.github/agents/devspec.codebase-structure.agent.md +39 -0
- devspec_installer/payload/.github/agents/devspec.coding-standards.agent.md +40 -0
- devspec_installer/payload/.github/agents/devspec.diagram.agent.md +76 -0
- devspec_installer/payload/.github/agents/devspec.extract.agent.md +91 -0
- devspec_installer/payload/.github/agents/devspec.finalize.agent.md +51 -0
- devspec_installer/payload/.github/agents/devspec.implement-task.agent.md +67 -0
- devspec_installer/payload/.github/agents/devspec.projectcontext.agent.md +34 -0
- devspec_installer/payload/.github/agents/devspec.review.agent.md +42 -0
- devspec_installer/payload/.github/agents/devspec.rules.agent.md +35 -0
- devspec_installer/payload/.github/agents/devspec.story.agent.md +54 -0
- devspec_installer/payload/.github/agents/devspec.tasks.agent.md +44 -0
- devspec_installer/payload/.github/agents/devspec.techstack.agent.md +35 -0
- devspec_installer/payload/.github/prompts/PATTERNS.md +278 -0
- devspec_installer/payload/.github/prompts/README.md +92 -0
- devspec_installer/payload/.github/prompts/devspec.clarify.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.codebase-structure.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.coding-standards.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.diagram.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.extract.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.finalize.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.implement.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.projectcontext.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.review.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.rules.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.story.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.tasks.prompt.md +11 -0
- devspec_installer/payload/.github/prompts/devspec.techstack.prompt.md +11 -0
- devspec_installer/payload/.github/skills/exploration-recovery/SKILL.md +45 -0
- devspec_installer/payload/.github/workflows/python-package-ci.yml +42 -0
- devspec_installer/payload/.github/workflows/python-package-publish.yml +69 -0
- devspec_installer/payload/.github/workflows/winget-package-publish.yml +110 -0
- devspec_installer/payload/AGENTS.md +80 -0
- devspec_installer/payload/GEMINI.md +35 -0
- devspec_installer/payload/README.md +301 -0
- devspec_installer/payload/devspec/adapters/README.md +53 -0
- devspec_installer/payload/devspec/adapters/antigravity.md +52 -0
- devspec_installer/payload/devspec/adapters/claude-code.md +32 -0
- devspec_installer/payload/devspec/adapters/codex-skills/devspec-workflow/SKILL.md +17 -0
- devspec_installer/payload/devspec/adapters/codex.md +22 -0
- devspec_installer/payload/devspec/adapters/command-registry.md +38 -0
- devspec_installer/payload/devspec/adapters/compatibility-matrix.md +21 -0
- devspec_installer/payload/devspec/adapters/copilot.md +20 -0
- devspec_installer/payload/devspec/adapters/cursor.md +22 -0
- devspec_installer/payload/devspec/adapters/enterprise-governance.md +36 -0
- devspec_installer/payload/devspec/adapters/gemini-cli.md +54 -0
- devspec_installer/payload/devspec/adapters/validation-flows.md +90 -0
- devspec_installer/payload/devspec/architecture/_template/artifact-queue.md +27 -0
- devspec_installer/payload/devspec/architecture/_template/decision.md +45 -0
- devspec_installer/payload/devspec/architecture/_template/diagram.md +62 -0
- devspec_installer/payload/devspec/architecture/_template/overview.md +37 -0
- devspec_installer/payload/devspec/architecture/artifact-queue.md +27 -0
- devspec_installer/payload/devspec/architecture/diagrams/README.md +44 -0
- devspec_installer/payload/devspec/architecture/overview.md +37 -0
- devspec_installer/payload/devspec/constitution.md +26 -0
- devspec_installer/payload/devspec/foundation/_template/codebase-structure.md +64 -0
- devspec_installer/payload/devspec/foundation/_template/coding-standards.md +46 -0
- devspec_installer/payload/devspec/foundation/_template/discovery-exclusions.md +52 -0
- devspec_installer/payload/devspec/foundation/_template/exploration-state.md +15 -0
- devspec_installer/payload/devspec/foundation/_template/extraction-state.md +45 -0
- devspec_installer/payload/devspec/foundation/_template/project-context.md +37 -0
- devspec_installer/payload/devspec/foundation/_template/provider-integrations.md +94 -0
- devspec_installer/payload/devspec/foundation/_template/rules.md +54 -0
- devspec_installer/payload/devspec/foundation/_template/tech-stack.md +49 -0
- devspec_installer/payload/devspec/foundation/codebase-structure.md +64 -0
- devspec_installer/payload/devspec/foundation/coding-standards.md +46 -0
- devspec_installer/payload/devspec/foundation/discovery-exclusions.md +52 -0
- devspec_installer/payload/devspec/foundation/extraction-state.md +45 -0
- devspec_installer/payload/devspec/foundation/project-context.md +33 -0
- devspec_installer/payload/devspec/foundation/provider-integrations.md +94 -0
- devspec_installer/payload/devspec/foundation/rules.md +52 -0
- devspec_installer/payload/devspec/foundation/tech-stack.md +49 -0
- devspec_installer/payload/devspec/glossary.md +111 -0
- devspec_installer/payload/devspec/work-items/_template/clarify.md +28 -0
- devspec_installer/payload/devspec/work-items/_template/decisions.md +9 -0
- devspec_installer/payload/devspec/work-items/_template/diagrams.md +42 -0
- devspec_installer/payload/devspec/work-items/_template/finalize.md +65 -0
- devspec_installer/payload/devspec/work-items/_template/implement.md +63 -0
- devspec_installer/payload/devspec/work-items/_template/meta.md +63 -0
- devspec_installer/payload/devspec/work-items/_template/notes.md +7 -0
- devspec_installer/payload/devspec/work-items/_template/review.md +41 -0
- devspec_installer/payload/devspec/work-items/_template/story.md +59 -0
- devspec_installer/payload/devspec/work-items/_template/tasks.md +38 -0
- devspec_installer/payload/packaging/devspec-profiles.json +60 -0
devspec_installer/cli.py
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import fnmatch
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from importlib import resources
|
|
12
|
+
from pathlib import Path, PurePosixPath
|
|
13
|
+
from typing import Iterable
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
MANIFEST_PATH = PurePosixPath("devspec/.install-manifest.json")
|
|
19
|
+
PROFILES_PATH = PurePosixPath("packaging/devspec-profiles.json")
|
|
20
|
+
|
|
21
|
+
FRAMEWORK_OWNED = "framework-owned"
|
|
22
|
+
PROJECT_OWNED = "project-owned"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class PayloadFile:
|
|
27
|
+
path: PurePosixPath
|
|
28
|
+
source: Path
|
|
29
|
+
ownership: str
|
|
30
|
+
digest: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class CopyPlan:
|
|
35
|
+
files: list[PayloadFile]
|
|
36
|
+
conflicts: list[str]
|
|
37
|
+
skipped: list[str]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class VersionStatus:
|
|
42
|
+
installed: str | None
|
|
43
|
+
package: str
|
|
44
|
+
status: str
|
|
45
|
+
label: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(argv: list[str] | None = None) -> int:
|
|
49
|
+
parser = build_parser()
|
|
50
|
+
args = parser.parse_args(argv)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
return args.func(args)
|
|
54
|
+
except DevspecError as exc:
|
|
55
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
56
|
+
return 2
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
60
|
+
parser = argparse.ArgumentParser(
|
|
61
|
+
prog="devspec",
|
|
62
|
+
description="Install, diff, sync, and validate devspec framework files.",
|
|
63
|
+
)
|
|
64
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
65
|
+
|
|
66
|
+
version_parser = subparsers.add_parser("version", help="Print the devspec CLI version.")
|
|
67
|
+
version_parser.set_defaults(func=cmd_version)
|
|
68
|
+
|
|
69
|
+
init_parser = subparsers.add_parser("init", help="Install devspec files into a target repository.")
|
|
70
|
+
add_target(init_parser)
|
|
71
|
+
add_profile(init_parser)
|
|
72
|
+
init_parser.add_argument("--repo-state", choices=["new", "existing"], required=True)
|
|
73
|
+
init_parser.add_argument("--force", action="store_true", help="Overwrite conflicting framework files.")
|
|
74
|
+
init_parser.set_defaults(func=cmd_init)
|
|
75
|
+
|
|
76
|
+
diff_parser = subparsers.add_parser("diff", help="Compare target files with the packaged framework.")
|
|
77
|
+
add_target(diff_parser)
|
|
78
|
+
add_profile(diff_parser, required=False)
|
|
79
|
+
diff_parser.set_defaults(func=cmd_diff)
|
|
80
|
+
|
|
81
|
+
sync_parser = subparsers.add_parser("sync", help="Update installed framework-owned files.")
|
|
82
|
+
add_target(sync_parser)
|
|
83
|
+
add_profile(sync_parser)
|
|
84
|
+
sync_parser.add_argument("--dry-run", action="store_true", help="Show planned changes without writing files.")
|
|
85
|
+
sync_parser.add_argument("--force", action="store_true", help="Overwrite modified framework-owned files.")
|
|
86
|
+
sync_parser.set_defaults(func=cmd_sync)
|
|
87
|
+
|
|
88
|
+
doctor_parser = subparsers.add_parser("doctor", help="Validate a devspec installation.")
|
|
89
|
+
add_target(doctor_parser)
|
|
90
|
+
add_profile(doctor_parser, required=False)
|
|
91
|
+
doctor_parser.set_defaults(func=cmd_doctor)
|
|
92
|
+
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def add_target(parser: argparse.ArgumentParser) -> None:
|
|
97
|
+
parser.add_argument("--target", default=".", help="Target repository root. Defaults to current directory.")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def add_profile(parser: argparse.ArgumentParser, required: bool = True) -> None:
|
|
101
|
+
parser.add_argument("--profile", default=None if required else "all", required=required, help="Install profile.")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cmd_version(_args: argparse.Namespace) -> int:
|
|
105
|
+
print(__version__)
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def cmd_init(args: argparse.Namespace) -> int:
|
|
110
|
+
payload = Payload()
|
|
111
|
+
target = resolve_target(args.target)
|
|
112
|
+
files = payload.resolve_profile_files(args.profile)
|
|
113
|
+
plan = create_copy_plan(files, target, force=args.force, mode="init")
|
|
114
|
+
|
|
115
|
+
if plan.conflicts:
|
|
116
|
+
print_report("Conflicts", plan.conflicts)
|
|
117
|
+
print("No files were written. Re-run with --force only after reviewing the conflicts.")
|
|
118
|
+
return 1
|
|
119
|
+
|
|
120
|
+
written = write_files(plan.files, target, dry_run=False)
|
|
121
|
+
manifest = build_manifest(args.profile, args.repo_state, plan.files)
|
|
122
|
+
write_manifest(target, manifest)
|
|
123
|
+
|
|
124
|
+
print(f"Installed devspec profile '{args.profile}' into {target}")
|
|
125
|
+
print(f"Files written: {written}")
|
|
126
|
+
if plan.skipped:
|
|
127
|
+
print_report("Skipped unchanged files", plan.skipped)
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def cmd_diff(args: argparse.Namespace) -> int:
|
|
132
|
+
payload = Payload()
|
|
133
|
+
target = resolve_target(args.target)
|
|
134
|
+
profile = args.profile or profile_from_manifest(target) or "all"
|
|
135
|
+
files = payload.resolve_profile_files(profile)
|
|
136
|
+
manifest = read_manifest(target)
|
|
137
|
+
report = diff_files(files, target, manifest, profile)
|
|
138
|
+
|
|
139
|
+
print_version_status(version_status(manifest))
|
|
140
|
+
print_diff_report(report)
|
|
141
|
+
return 1 if report["missing"] or report["modified"] or report["stale"] or report["profile"] else 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def cmd_sync(args: argparse.Namespace) -> int:
|
|
145
|
+
payload = Payload()
|
|
146
|
+
target = resolve_target(args.target)
|
|
147
|
+
manifest = read_manifest(target)
|
|
148
|
+
files = payload.resolve_profile_files(args.profile)
|
|
149
|
+
|
|
150
|
+
plan = create_sync_plan(files, target, manifest, force=args.force)
|
|
151
|
+
if plan.conflicts:
|
|
152
|
+
print_version_status(version_status(manifest))
|
|
153
|
+
print_report("Conflicts", plan.conflicts)
|
|
154
|
+
print("No files were written. Run with --dry-run first, then use --force only for reviewed framework-owned files.")
|
|
155
|
+
return 1
|
|
156
|
+
|
|
157
|
+
if args.dry_run:
|
|
158
|
+
print_version_status(version_status(manifest))
|
|
159
|
+
print(f"Dry run for devspec profile '{args.profile}' in {target}")
|
|
160
|
+
print(f"Files that would be written: {len(plan.files)}")
|
|
161
|
+
else:
|
|
162
|
+
written = write_files(plan.files, target, dry_run=False)
|
|
163
|
+
repo_state = manifest.get("repo_state", "existing") if manifest else "existing"
|
|
164
|
+
write_manifest(target, build_manifest(args.profile, repo_state, files))
|
|
165
|
+
print_version_status(version_status(manifest))
|
|
166
|
+
print(f"Synchronized devspec profile '{args.profile}' in {target}")
|
|
167
|
+
print(f"Files written: {written}")
|
|
168
|
+
if plan.skipped:
|
|
169
|
+
print_report("Skipped files", plan.skipped)
|
|
170
|
+
return 0
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cmd_doctor(args: argparse.Namespace) -> int:
|
|
174
|
+
payload = Payload()
|
|
175
|
+
target = resolve_target(args.target)
|
|
176
|
+
profile = args.profile or profile_from_manifest(target) or "all"
|
|
177
|
+
files = payload.resolve_profile_files(profile)
|
|
178
|
+
installed_paths = {str(item.path) for item in files}
|
|
179
|
+
|
|
180
|
+
errors: list[str] = []
|
|
181
|
+
warnings: list[str] = []
|
|
182
|
+
|
|
183
|
+
for item in files:
|
|
184
|
+
if not item.source.exists():
|
|
185
|
+
errors.append(f"profile '{profile}' references missing payload file: {item.path}")
|
|
186
|
+
|
|
187
|
+
manifest = read_manifest(target)
|
|
188
|
+
status = version_status(manifest)
|
|
189
|
+
if manifest is None:
|
|
190
|
+
warnings.append(f"install manifest is missing: {MANIFEST_PATH}")
|
|
191
|
+
else:
|
|
192
|
+
manifest_profile = manifest.get("profile")
|
|
193
|
+
if manifest_profile and manifest_profile != profile:
|
|
194
|
+
warnings.append(f"profile mismatch: manifest has '{manifest_profile}', doctor checked '{profile}'")
|
|
195
|
+
if status.status == "unknown":
|
|
196
|
+
warnings.append("manifest devspec_version is missing or invalid")
|
|
197
|
+
elif status.status == "upgrade":
|
|
198
|
+
warnings.append(f"installed devspec version '{status.installed}' is older than package version '{status.package}'")
|
|
199
|
+
elif status.status == "downgrade":
|
|
200
|
+
warnings.append(f"installed devspec version '{status.installed}' is newer than package version '{status.package}'")
|
|
201
|
+
|
|
202
|
+
for item in files:
|
|
203
|
+
if not (target / as_local_path(item.path)).exists():
|
|
204
|
+
warnings.append(f"target is missing installed file: {item.path}")
|
|
205
|
+
|
|
206
|
+
validate_command_registry(payload, installed_paths, errors)
|
|
207
|
+
validate_adapter_wrappers(profile, payload, installed_paths, errors)
|
|
208
|
+
|
|
209
|
+
if errors:
|
|
210
|
+
print_report("Errors", errors)
|
|
211
|
+
if warnings:
|
|
212
|
+
print_report("Warnings", warnings)
|
|
213
|
+
if not errors and not warnings:
|
|
214
|
+
print(f"devspec doctor passed for profile '{profile}' in {target}")
|
|
215
|
+
return 1 if errors else 0
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class DevspecError(RuntimeError):
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class Payload:
|
|
223
|
+
def __init__(self) -> None:
|
|
224
|
+
self.root = find_source_root() or materialized_resource_root()
|
|
225
|
+
self.profiles = self._load_profiles()
|
|
226
|
+
|
|
227
|
+
def _load_profiles(self) -> dict:
|
|
228
|
+
path = self.root / as_local_path(PROFILES_PATH)
|
|
229
|
+
if not path.exists():
|
|
230
|
+
raise DevspecError(f"profile manifest not found in payload: {PROFILES_PATH}")
|
|
231
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
232
|
+
data = json.load(handle)
|
|
233
|
+
profiles = data.get("profiles")
|
|
234
|
+
if not isinstance(profiles, dict):
|
|
235
|
+
raise DevspecError("profile manifest must contain a 'profiles' object")
|
|
236
|
+
return profiles
|
|
237
|
+
|
|
238
|
+
def resolve_profile_files(self, profile: str) -> list[PayloadFile]:
|
|
239
|
+
if profile not in self.profiles:
|
|
240
|
+
available = ", ".join(sorted(self.profiles))
|
|
241
|
+
raise DevspecError(f"unknown profile '{profile}'. Available profiles: {available}")
|
|
242
|
+
|
|
243
|
+
patterns = self._resolve_patterns(profile, seen=set())
|
|
244
|
+
paths: dict[PurePosixPath, PayloadFile] = {}
|
|
245
|
+
for pattern in patterns:
|
|
246
|
+
for path in iter_pattern_matches(self.root, pattern):
|
|
247
|
+
rel = to_posix(path.relative_to(self.root))
|
|
248
|
+
if should_exclude_payload(rel):
|
|
249
|
+
continue
|
|
250
|
+
paths[rel] = PayloadFile(
|
|
251
|
+
path=rel,
|
|
252
|
+
source=path,
|
|
253
|
+
ownership=classify_ownership(rel),
|
|
254
|
+
digest=sha256_file(path),
|
|
255
|
+
)
|
|
256
|
+
return [paths[key] for key in sorted(paths)]
|
|
257
|
+
|
|
258
|
+
def _resolve_patterns(self, profile: str, seen: set[str]) -> list[str]:
|
|
259
|
+
if profile in seen:
|
|
260
|
+
raise DevspecError(f"cyclic profile inheritance at '{profile}'")
|
|
261
|
+
branch = {*seen, profile}
|
|
262
|
+
data = self.profiles[profile]
|
|
263
|
+
patterns: list[str] = []
|
|
264
|
+
for parent in data.get("extends", []):
|
|
265
|
+
patterns.extend(self._resolve_patterns(parent, branch))
|
|
266
|
+
patterns.extend(data.get("includes", []))
|
|
267
|
+
return patterns
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def find_source_root() -> Path | None:
|
|
271
|
+
current = Path(__file__).resolve()
|
|
272
|
+
for parent in current.parents:
|
|
273
|
+
if (parent / "devspec/adapters/command-registry.md").exists() and (parent / "packaging/devspec-profiles.json").exists():
|
|
274
|
+
return parent
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def materialized_resource_root() -> Path:
|
|
279
|
+
resource = resources.files("devspec_installer").joinpath("payload")
|
|
280
|
+
if not resource.is_dir():
|
|
281
|
+
raise DevspecError("packaged payload is missing")
|
|
282
|
+
return Path(str(resource))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def iter_pattern_matches(root: Path, pattern: str) -> Iterable[Path]:
|
|
286
|
+
normalized = pattern.replace("\\", "/")
|
|
287
|
+
if normalized.endswith("/**"):
|
|
288
|
+
base = root / as_local_path(PurePosixPath(normalized[:-3]))
|
|
289
|
+
if base.exists():
|
|
290
|
+
yield from (path for path in base.rglob("*") if path.is_file())
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
candidate = root / as_local_path(PurePosixPath(normalized))
|
|
294
|
+
if candidate.is_file():
|
|
295
|
+
yield candidate
|
|
296
|
+
return
|
|
297
|
+
if candidate.is_dir():
|
|
298
|
+
yield from (path for path in candidate.rglob("*") if path.is_file())
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
for path in root.rglob("*"):
|
|
302
|
+
if path.is_file() and fnmatch.fnmatch(str(to_posix(path.relative_to(root))), normalized):
|
|
303
|
+
yield path
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def should_exclude_payload(path: PurePosixPath) -> bool:
|
|
307
|
+
parts = path.parts
|
|
308
|
+
if any(part in {".git", ".vs", "__pycache__", ".pytest_cache", ".ruff_cache", "dist", "build"} for part in parts):
|
|
309
|
+
return True
|
|
310
|
+
if path.name.endswith((".pyc", ".pyo")):
|
|
311
|
+
return True
|
|
312
|
+
if path == MANIFEST_PATH:
|
|
313
|
+
return True
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def classify_ownership(path: PurePosixPath) -> str:
|
|
318
|
+
parts = path.parts
|
|
319
|
+
if path in {PurePosixPath("devspec/constitution.md"), PurePosixPath("devspec/glossary.md")}:
|
|
320
|
+
return PROJECT_OWNED
|
|
321
|
+
if len(parts) == 3 and parts[0] == "devspec" and parts[1] == "foundation" and path.suffix == ".md":
|
|
322
|
+
return PROJECT_OWNED
|
|
323
|
+
if len(parts) == 3 and parts[0] == "devspec" and parts[1] == "architecture" and path.suffix == ".md":
|
|
324
|
+
return PROJECT_OWNED
|
|
325
|
+
if len(parts) >= 4 and parts[:3] == ("devspec", "architecture", "diagrams") and path.suffix == ".md":
|
|
326
|
+
return PROJECT_OWNED
|
|
327
|
+
if len(parts) >= 3 and parts[:2] == ("devspec", "work-items") and parts[2] != "_template":
|
|
328
|
+
return PROJECT_OWNED
|
|
329
|
+
return FRAMEWORK_OWNED
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def create_copy_plan(files: list[PayloadFile], target: Path, force: bool, mode: str) -> CopyPlan:
|
|
333
|
+
conflicts: list[str] = []
|
|
334
|
+
skipped: list[str] = []
|
|
335
|
+
writable: list[PayloadFile] = []
|
|
336
|
+
for item in files:
|
|
337
|
+
destination = target / as_local_path(item.path)
|
|
338
|
+
if not destination.exists():
|
|
339
|
+
writable.append(item)
|
|
340
|
+
continue
|
|
341
|
+
destination_hash = sha256_file(destination)
|
|
342
|
+
if destination_hash == item.digest:
|
|
343
|
+
skipped.append(str(item.path))
|
|
344
|
+
continue
|
|
345
|
+
if item.ownership == PROJECT_OWNED and mode == "sync":
|
|
346
|
+
skipped.append(f"{item.path} (project-owned)")
|
|
347
|
+
continue
|
|
348
|
+
if force and item.ownership == FRAMEWORK_OWNED:
|
|
349
|
+
writable.append(item)
|
|
350
|
+
continue
|
|
351
|
+
conflicts.append(f"{item.path} already exists and differs")
|
|
352
|
+
return CopyPlan(files=writable, conflicts=conflicts, skipped=skipped)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def create_sync_plan(files: list[PayloadFile], target: Path, manifest: dict | None, force: bool) -> CopyPlan:
|
|
356
|
+
conflicts: list[str] = []
|
|
357
|
+
skipped: list[str] = []
|
|
358
|
+
writable: list[PayloadFile] = []
|
|
359
|
+
manifest_files = {entry["path"]: entry for entry in (manifest or {}).get("files", []) if isinstance(entry, dict) and "path" in entry}
|
|
360
|
+
|
|
361
|
+
for item in files:
|
|
362
|
+
destination = target / as_local_path(item.path)
|
|
363
|
+
if item.ownership == PROJECT_OWNED and destination.exists():
|
|
364
|
+
skipped.append(f"{item.path} (project-owned)")
|
|
365
|
+
continue
|
|
366
|
+
if not destination.exists():
|
|
367
|
+
writable.append(item)
|
|
368
|
+
continue
|
|
369
|
+
destination_hash = sha256_file(destination)
|
|
370
|
+
if destination_hash == item.digest:
|
|
371
|
+
skipped.append(str(item.path))
|
|
372
|
+
continue
|
|
373
|
+
previous = manifest_files.get(str(item.path), {}).get("sha256")
|
|
374
|
+
if previous and destination_hash == previous:
|
|
375
|
+
writable.append(item)
|
|
376
|
+
continue
|
|
377
|
+
if force and item.ownership == FRAMEWORK_OWNED:
|
|
378
|
+
writable.append(item)
|
|
379
|
+
continue
|
|
380
|
+
conflicts.append(f"{item.path} has local changes")
|
|
381
|
+
return CopyPlan(files=writable, conflicts=conflicts, skipped=skipped)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def write_files(files: list[PayloadFile], target: Path, dry_run: bool) -> int:
|
|
385
|
+
count = 0
|
|
386
|
+
for item in files:
|
|
387
|
+
destination = target / as_local_path(item.path)
|
|
388
|
+
if dry_run:
|
|
389
|
+
count += 1
|
|
390
|
+
continue
|
|
391
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
392
|
+
shutil.copyfile(item.source, destination)
|
|
393
|
+
count += 1
|
|
394
|
+
return count
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def build_manifest(profile: str, repo_state: str, files: list[PayloadFile]) -> dict:
|
|
398
|
+
return {
|
|
399
|
+
"schema_version": 1,
|
|
400
|
+
"devspec_version": __version__,
|
|
401
|
+
"profile": profile,
|
|
402
|
+
"repo_state": repo_state,
|
|
403
|
+
"installed_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
|
404
|
+
"files": [
|
|
405
|
+
{
|
|
406
|
+
"path": str(item.path),
|
|
407
|
+
"sha256": item.digest,
|
|
408
|
+
"ownership": item.ownership,
|
|
409
|
+
}
|
|
410
|
+
for item in sorted(files, key=lambda value: value.path)
|
|
411
|
+
],
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def write_manifest(target: Path, manifest: dict) -> None:
|
|
416
|
+
path = target / as_local_path(MANIFEST_PATH)
|
|
417
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
418
|
+
path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def read_manifest(target: Path) -> dict | None:
|
|
422
|
+
path = target / as_local_path(MANIFEST_PATH)
|
|
423
|
+
if not path.exists():
|
|
424
|
+
return None
|
|
425
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
426
|
+
return json.load(handle)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def profile_from_manifest(target: Path) -> str | None:
|
|
430
|
+
manifest = read_manifest(target)
|
|
431
|
+
if not manifest:
|
|
432
|
+
return None
|
|
433
|
+
profile = manifest.get("profile")
|
|
434
|
+
return profile if isinstance(profile, str) else None
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def version_status(manifest: dict | None) -> VersionStatus:
|
|
438
|
+
if manifest is None:
|
|
439
|
+
return VersionStatus(installed=None, package=__version__, status="not-installed", label="not installed")
|
|
440
|
+
|
|
441
|
+
installed = manifest.get("devspec_version")
|
|
442
|
+
if not isinstance(installed, str):
|
|
443
|
+
return VersionStatus(installed=None, package=__version__, status="unknown", label="unknown")
|
|
444
|
+
|
|
445
|
+
installed_version = parse_semver(installed)
|
|
446
|
+
package_version = parse_semver(__version__)
|
|
447
|
+
if installed_version is None or package_version is None:
|
|
448
|
+
return VersionStatus(installed=installed, package=__version__, status="unknown", label="unknown")
|
|
449
|
+
if installed_version == package_version:
|
|
450
|
+
return VersionStatus(installed=installed, package=__version__, status="same", label="up to date")
|
|
451
|
+
if installed_version < package_version:
|
|
452
|
+
return VersionStatus(installed=installed, package=__version__, status="upgrade", label="upgrade available")
|
|
453
|
+
return VersionStatus(installed=installed, package=__version__, status="downgrade", label="newer than package")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def parse_semver(value: str) -> tuple[int, int, int] | None:
|
|
457
|
+
parts = value.split(".")
|
|
458
|
+
if len(parts) != 3:
|
|
459
|
+
return None
|
|
460
|
+
try:
|
|
461
|
+
parsed = tuple(int(part) for part in parts)
|
|
462
|
+
except ValueError:
|
|
463
|
+
return None
|
|
464
|
+
return parsed if all(part >= 0 for part in parsed) else None
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def diff_files(files: list[PayloadFile], target: Path, manifest: dict | None, profile: str) -> dict[str, list[str]]:
|
|
468
|
+
report = {"missing": [], "modified": [], "stale": [], "protected": [], "profile": []}
|
|
469
|
+
manifest_files = {entry["path"]: entry for entry in (manifest or {}).get("files", []) if isinstance(entry, dict) and "path" in entry}
|
|
470
|
+
|
|
471
|
+
if manifest and manifest.get("profile") != profile:
|
|
472
|
+
report["profile"].append(f"manifest profile is '{manifest.get('profile')}', requested profile is '{profile}'")
|
|
473
|
+
|
|
474
|
+
for item in files:
|
|
475
|
+
destination = target / as_local_path(item.path)
|
|
476
|
+
if item.ownership == PROJECT_OWNED:
|
|
477
|
+
report["protected"].append(str(item.path))
|
|
478
|
+
if not destination.exists():
|
|
479
|
+
report["missing"].append(str(item.path))
|
|
480
|
+
continue
|
|
481
|
+
destination_hash = sha256_file(destination)
|
|
482
|
+
if destination_hash == item.digest:
|
|
483
|
+
continue
|
|
484
|
+
previous = manifest_files.get(str(item.path), {}).get("sha256")
|
|
485
|
+
if previous and destination_hash == previous:
|
|
486
|
+
report["stale"].append(str(item.path))
|
|
487
|
+
else:
|
|
488
|
+
report["modified"].append(str(item.path))
|
|
489
|
+
return report
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def validate_command_registry(payload: Payload, installed_paths: set[str], errors: list[str]) -> None:
|
|
493
|
+
registry = payload.root / "devspec/adapters/command-registry.md"
|
|
494
|
+
if not registry.exists():
|
|
495
|
+
errors.append("missing command registry in payload")
|
|
496
|
+
return
|
|
497
|
+
for line in registry.read_text(encoding="utf-8").splitlines():
|
|
498
|
+
if not line.startswith("| `/devspec."):
|
|
499
|
+
continue
|
|
500
|
+
columns = [column.strip() for column in line.strip("|").split("|")]
|
|
501
|
+
if len(columns) < 5:
|
|
502
|
+
continue
|
|
503
|
+
prompt = strip_markdown_code(columns[3])
|
|
504
|
+
agent = strip_markdown_code(columns[4])
|
|
505
|
+
for required in (prompt, agent):
|
|
506
|
+
if required and required not in installed_paths:
|
|
507
|
+
errors.append(f"registry references missing profile file: {required}")
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def validate_adapter_wrappers(profile: str, payload: Payload, installed_paths: set[str], errors: list[str]) -> None:
|
|
511
|
+
commands = command_names(payload)
|
|
512
|
+
profiles_to_check = expanded_profile_names(payload, profile)
|
|
513
|
+
|
|
514
|
+
if "claude" in profiles_to_check:
|
|
515
|
+
for command in commands:
|
|
516
|
+
name = command.removeprefix("/").replace(".", "-")
|
|
517
|
+
required = f".claude/skills/{name}/SKILL.md"
|
|
518
|
+
if required not in installed_paths:
|
|
519
|
+
errors.append(f"Claude profile missing wrapper: {required}")
|
|
520
|
+
if "gemini" in profiles_to_check:
|
|
521
|
+
for command in commands:
|
|
522
|
+
suffix = command.removeprefix("/devspec.")
|
|
523
|
+
required = f".gemini/commands/devspec/{suffix}.toml"
|
|
524
|
+
if required not in installed_paths:
|
|
525
|
+
errors.append(f"Gemini profile missing wrapper: {required}")
|
|
526
|
+
if "antigravity" in profiles_to_check:
|
|
527
|
+
for command in commands:
|
|
528
|
+
name = command.removeprefix("/").replace(".", "-")
|
|
529
|
+
required = f".agents/skills/{name}.md"
|
|
530
|
+
if required not in installed_paths:
|
|
531
|
+
errors.append(f"Antigravity profile missing wrapper: {required}")
|
|
532
|
+
if "cursor" in profiles_to_check and ".cursor/rules/devspec-workflow.mdc" not in installed_paths:
|
|
533
|
+
errors.append("Cursor profile missing .cursor/rules/devspec-workflow.mdc")
|
|
534
|
+
if "codex" in profiles_to_check and "AGENTS.md" not in installed_paths:
|
|
535
|
+
errors.append("Codex profile missing AGENTS.md")
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def command_names(payload: Payload) -> list[str]:
|
|
539
|
+
registry = payload.root / "devspec/adapters/command-registry.md"
|
|
540
|
+
names: list[str] = []
|
|
541
|
+
for line in registry.read_text(encoding="utf-8").splitlines():
|
|
542
|
+
if line.startswith("| `/devspec."):
|
|
543
|
+
columns = [column.strip() for column in line.strip("|").split("|")]
|
|
544
|
+
names.append(strip_markdown_code(columns[0]))
|
|
545
|
+
return names
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def expanded_profile_names(payload: Payload, profile: str) -> set[str]:
|
|
549
|
+
names: set[str] = set()
|
|
550
|
+
|
|
551
|
+
def visit(name: str) -> None:
|
|
552
|
+
if name in names:
|
|
553
|
+
return
|
|
554
|
+
names.add(name)
|
|
555
|
+
for parent in payload.profiles[name].get("extends", []):
|
|
556
|
+
visit(parent)
|
|
557
|
+
|
|
558
|
+
visit(profile)
|
|
559
|
+
return names
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def strip_markdown_code(value: str) -> str:
|
|
563
|
+
return value.strip().strip("`")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def print_version_status(status: VersionStatus) -> None:
|
|
567
|
+
if status.status == "not-installed":
|
|
568
|
+
installed = "not installed"
|
|
569
|
+
else:
|
|
570
|
+
installed = status.installed or "unknown"
|
|
571
|
+
print(f"Installed version: {installed}")
|
|
572
|
+
print(f"Package version: {status.package}")
|
|
573
|
+
print(f"Version status: {status.label}")
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def print_diff_report(report: dict[str, list[str]]) -> None:
|
|
577
|
+
empty = True
|
|
578
|
+
for title, values in (
|
|
579
|
+
("Profile mismatches", report["profile"]),
|
|
580
|
+
("Missing files", report["missing"]),
|
|
581
|
+
("Modified files", report["modified"]),
|
|
582
|
+
("Stale files", report["stale"]),
|
|
583
|
+
("Protected project-owned files", report["protected"]),
|
|
584
|
+
):
|
|
585
|
+
if values:
|
|
586
|
+
empty = False
|
|
587
|
+
print_report(title, values)
|
|
588
|
+
if empty:
|
|
589
|
+
print("No devspec differences found.")
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def print_report(title: str, values: list[str]) -> None:
|
|
593
|
+
print(f"{title}:")
|
|
594
|
+
for value in values:
|
|
595
|
+
print(f" - {value}")
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def resolve_target(value: str) -> Path:
|
|
599
|
+
return Path(value).expanduser().resolve()
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def sha256_file(path: Path) -> str:
|
|
603
|
+
digest = hashlib.sha256()
|
|
604
|
+
with path.open("rb") as handle:
|
|
605
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
606
|
+
digest.update(chunk)
|
|
607
|
+
return digest.hexdigest()
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def to_posix(path: Path) -> PurePosixPath:
|
|
611
|
+
return PurePosixPath(path.as_posix())
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def as_local_path(path: PurePosixPath) -> Path:
|
|
615
|
+
return Path(*path.parts)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on devspec workflow, artifact, and no-intent-drift rules for Google Antigravity.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Devspec Workflow Rules
|
|
7
|
+
|
|
8
|
+
When the user invokes or references a `/devspec.*` workflow, treat it as command intent from `devspec/adapters/command-registry.md`.
|
|
9
|
+
|
|
10
|
+
Follow these rules:
|
|
11
|
+
|
|
12
|
+
- Read `devspec/adapters/command-registry.md` before acting on a `devspec` command.
|
|
13
|
+
- Preserve the original intent of the canonical Copilot prompt and agent files named in the registry.
|
|
14
|
+
- Use Git-tracked `devspec/` artifacts for recovery before relying on chat history, Antigravity artifacts, memory, or task lists.
|
|
15
|
+
- Preserve required inputs, output artifacts, status values, gates, handoff order, and recovery behavior.
|
|
16
|
+
- Preserve structured question behavior from `.github/prompts/PATTERNS.md#interactive-question-pattern`; if clickable options are unavailable, render the same option labels as text and preserve the recommended option.
|
|
17
|
+
- Use `devspec/glossary.md` for status values.
|
|
18
|
+
- Use `devspec/foundation/codebase-structure.md` for repository access requirements.
|
|
19
|
+
- Use `devspec/adapters/validation-flows.md` for enterprise acceptance checks.
|
|
20
|
+
- Keep provider credentials, tokens, user settings, and secrets outside prompt, rule, skill, and artifact files.
|
|
21
|
+
- Record unsupported Antigravity behavior as a limitation instead of changing workflow semantics.
|
|
22
|
+
|
|
23
|
+
Do not recommend unregistered commands such as `/devspec.plan`, `/devspec.architecture`, `/devspec.provider-integrations`, `/devspec.queue`, or `/devspec.decisions`.
|
|
24
|
+
|
|
25
|
+
For Antigravity execution, prefer strict or review-requesting permission posture for commands, non-workspace file access, browser actions, MCP calls, and artifact application unless the project has explicitly approved broader access.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devspec-clarify
|
|
3
|
+
description: Run /devspec.clarify using the canonical devspec command registry and Copilot reference contract.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Execute canonical command `/devspec.clarify`.
|
|
7
|
+
|
|
8
|
+
- Read `devspec/adapters/command-registry.md` for the command contract.
|
|
9
|
+
- Read `.github/prompts/devspec.clarify.prompt.md` and `.github/agents/devspec.clarify.agent.md` as the source of intent.
|
|
10
|
+
- Preserve required inputs, output artifacts, status values, gates, handoff order, and recovery behavior.
|
|
11
|
+
- Use Git-tracked `devspec/` artifacts for recovery before relying on chat history or Antigravity artifacts.
|
|
12
|
+
- Treat unsupported Antigravity behavior as an adapter limitation, not a workflow change.
|
|
13
|
+
|
|
14
|
+
Command input comes from the user's current message.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devspec-codebase-structure
|
|
3
|
+
description: Run /devspec.codebase-structure using the canonical devspec command registry and Copilot reference contract.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Execute canonical command `/devspec.codebase-structure`.
|
|
7
|
+
|
|
8
|
+
- Read `devspec/adapters/command-registry.md` for the command contract.
|
|
9
|
+
- Read `.github/prompts/devspec.codebase-structure.prompt.md` and `.github/agents/devspec.codebase-structure.agent.md` as the source of intent.
|
|
10
|
+
- Preserve required inputs, output artifacts, status values, gates, handoff order, and recovery behavior.
|
|
11
|
+
- Use Git-tracked `devspec/` artifacts for recovery before relying on chat history or Antigravity artifacts.
|
|
12
|
+
- Treat unsupported Antigravity behavior as an adapter limitation, not a workflow change.
|
|
13
|
+
|
|
14
|
+
Command input comes from the user's current message.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devspec-coding-standards
|
|
3
|
+
description: Run /devspec.coding-standards using the canonical devspec command registry and Copilot reference contract.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Execute canonical command `/devspec.coding-standards`.
|
|
7
|
+
|
|
8
|
+
- Read `devspec/adapters/command-registry.md` for the command contract.
|
|
9
|
+
- Read `.github/prompts/devspec.coding-standards.prompt.md` and `.github/agents/devspec.coding-standards.agent.md` as the source of intent.
|
|
10
|
+
- Preserve required inputs, output artifacts, status values, gates, handoff order, and recovery behavior.
|
|
11
|
+
- Use Git-tracked `devspec/` artifacts for recovery before relying on chat history or Antigravity artifacts.
|
|
12
|
+
- Treat unsupported Antigravity behavior as an adapter limitation, not a workflow change.
|
|
13
|
+
|
|
14
|
+
Command input comes from the user's current message.
|