Setting the file. One moment. Project Profile · Agent Skill Stack · github/awesome-copilot · Skills Docs11.10
OpenAI
scripts/project_profile.py
scripts/project_profile.py
Python·113 lines·4 KB
13
14
15SKILL_NAME = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
16
17
18def validate_skill_name(value: str) -> str:
19 if not SKILL_NAME.fullmatch(value):
20 raise argparse.ArgumentTypeError(f"invalid Skill name: {value!r}")
21 return value
22
23
24def parse_route(raw: str, active: set[str]) -> dict[str, object]:
25 if "=" not in raw:
26 raise ValueError(f"route must use 'intent=primary[,helper]': {raw!r}")
27 intent, raw_skills = raw.split("=", 1)
28 intent = intent.strip()
29 skills = [item.strip() for item in raw_skills.split(",") if item.strip()]
30 if not intent or not skills:
31 raise ValueError(f"route has no intent or Skill: {raw!r}")
32 invalid = [name for name in skills if not SKILL_NAME.fullmatch(name)]
33 if invalid:
34 raise ValueError("route contains invalid Skill names: " + ", ".join(invalid))
35 missing = [name for name in skills if name not in active]
36 if missing:
37 raise ValueError("route refers to Skills not listed with --skill: " + ", ".join(missing))
38 return {
39 "intent": intent,
40 "primary": skills[0],
41 "supporting": skills[1:],
42 }
43
44
45def atomic_write_json(path: Path, payload: dict[str, object]) -> None:
46 path.parent.mkdir(parents=True, exist_ok=True)
47 with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
48 json.dump(payload, handle, ensure_ascii=False, indent=2)
49 handle.write("\n")
50 temporary = handle.name
51 os.replace(temporary, path)
52
53
54def main() -> int:
55 parser = argparse.ArgumentParser(description=__doc__)
56 parser.add_argument("--project", required=True, help="Project root")
57 parser.add_argument("--name", required=True, help="Plain profile name")
58 parser.add_argument("--skill", action="append", required=True, type=validate_skill_name, help="Active Skill; repeatable")
59 parser.add_argument("--route", action="append", default=[], help="Intent route: intent=primary[,helper]")
60 parser.add_argument("--strict", action="store_true", help="Do not search outside this profile automatically")
61 parser.add_argument("--apply", action="store_true", help="Write the profile; default is preview only")
62 parser.add_argument("--update", action="store_true", help="Replace an existing profile; requires --apply")
63 args = parser.parse_args()
64
65 if args.update and not args.apply:
66 raise SystemExit("--update requires --apply")
67
68 project = Path(os.path.expandvars(os.path.expanduser(args.project))).resolve()
69 if project.is_symlink() or not project.is_dir():
70 raise SystemExit(f"project is not a regular directory: {project}")
71
72 active_skills = list(dict.fromkeys(args.skill))
73 active_set = set(active_skills)
74 try:
75 routes = [parse_route(raw, active_set) for raw in args.route]
76 except ValueError as exc:
77 raise SystemExit(str(exc)) from exc
78
79 profile_path = project / ".codex" / "skill-stack.json"
80 if profile_path.exists() and not args.update:
81 raise SystemExit(f"profile already exists; refusing to overwrite: {profile_path}")
82
83 payload: dict[str, object] = {
84 "schema": 1,
85 "profile_name": args.name.strip(),
86 "project_root": str(project),
87 "generated_at": datetime.now(timezone.utc).isoformat(),
88 "active_skills": active_skills,
89 "routes": routes,
90 "routing": {
91 "preference": "profile-first",
92 "outside_search": "never" if args.strict else "only-for-uncovered-capabilities",
93 },
94 "privacy": "This profile stores routing preferences only. It contains no prompts, usage history, or feedback logs.",
95 "technical_note": "A profile guides routing. Actual hard scoping requires project-local Skill installation when supported by the client.",
96 }
97
98 if args.apply:
99 atomic_write_json(profile_path, payload)
100 status = "updated" if args.update else "created"
101 else:
102 status = "preview"
103
104 print(json.dumps({
105 "status": status,
106 "profile_path": str(profile_path),
107 "profile": payload,
108 }, ensure_ascii=False, indent=2))
109 return 0
110
111
112if __name__ == "__main__":
113 raise SystemExit(main())