sherpa-skill 2.0.5 → 2.0.7

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 (247) hide show
  1. package/.agents/plugins/marketplace.json +11 -0
  2. package/.agents/skills/sherpa/SKILL.md +161 -0
  3. package/.agents/skills/sherpa/commands/README.md +43 -0
  4. package/.agents/skills/sherpa/commands/manifest.json +589 -0
  5. package/.agents/skills/sherpa/commands/sherpa-research.md +147 -0
  6. package/.agents/skills/sherpa/core/commands/router.py +171 -0
  7. package/.agents/skills/sherpa/core/configuration/manager.py +504 -0
  8. package/.agents/skills/sherpa/core/ideas/engine.py +564 -0
  9. package/.agents/skills/sherpa/docs/commands/README.md +96 -0
  10. package/.agents/skills/sherpa/docs/configuration/runtime-verification.md +64 -0
  11. package/.agents/skills/sherpa/rules/demo-preparation-rules.md +543 -0
  12. package/.agents/skills/sherpa/rules/engineering-rules.md +221 -0
  13. package/.agents/skills/sherpa/rules/final-audit-rules.md +824 -0
  14. package/.agents/skills/sherpa/rules/implementation-rules.md +127 -0
  15. package/.agents/skills/sherpa/rules/judge-rules.md +443 -0
  16. package/.agents/skills/sherpa/rules/mentor-rules.md +109 -0
  17. package/.agents/skills/sherpa/rules/postmortem-rules.md +69 -0
  18. package/.agents/skills/sherpa/rules/presentation-rules.md +783 -0
  19. package/.agents/skills/sherpa/rules/red-team-rules.md +185 -0
  20. package/.agents/skills/sherpa/rules/research-rules.md +184 -0
  21. package/.agents/skills/sherpa/rules/score-optimization-rules.md +355 -0
  22. package/.agents/skills/sherpa/rules/scoring-rules.md +216 -0
  23. package/.agents/skills/sherpa/rules/submission-rules.md +41 -0
  24. package/.agents/skills/sherpa/rules/testing-security-rules.md +87 -0
  25. package/.agents/skills/sherpa/rules/ux-rules.md +89 -0
  26. package/.agents/skills/sherpa/src/config_manager.py +474 -0
  27. package/.agents/skills/sherpa/src/verification.py +458 -0
  28. package/.agents/skills/sherpa/templates/audit-finding.md +20 -0
  29. package/.agents/skills/sherpa/templates/claim-audit.md +15 -0
  30. package/.agents/skills/sherpa/templates/configuration/hackathon.json +21 -0
  31. package/.agents/skills/sherpa/templates/configuration/integrations.env.example +3 -0
  32. package/.agents/skills/sherpa/templates/configuration/project.env.example +12 -0
  33. package/.agents/skills/sherpa/templates/configuration/providers.env.example +4 -0
  34. package/.agents/skills/sherpa/templates/configuration/settings.json +22 -0
  35. package/.agents/skills/sherpa/templates/consistency-audit.md +17 -0
  36. package/.agents/skills/sherpa/templates/decision-log.md +18 -0
  37. package/.agents/skills/sherpa/templates/demo-checkpoint.md +42 -0
  38. package/.agents/skills/sherpa/templates/demo-evidence.md +32 -0
  39. package/.agents/skills/sherpa/templates/demo-finding.md +112 -0
  40. package/.agents/skills/sherpa/templates/demo-flow.md +52 -0
  41. package/.agents/skills/sherpa/templates/demo-plan.md +265 -0
  42. package/.agents/skills/sherpa/templates/demo-verification.md +57 -0
  43. package/.agents/skills/sherpa/templates/final-audit.md +387 -0
  44. package/.agents/skills/sherpa/templates/final-risk.md +17 -0
  45. package/.agents/skills/sherpa/templates/final-verification.md +57 -0
  46. package/.agents/skills/sherpa/templates/gap-analysis.md +113 -0
  47. package/.agents/skills/sherpa/templates/idea-comparison.md +129 -0
  48. package/.agents/skills/sherpa/templates/idea-evaluation.md +251 -0
  49. package/.agents/skills/sherpa/templates/implementation-plan.md +96 -0
  50. package/.agents/skills/sherpa/templates/implementation-task.md +57 -0
  51. package/.agents/skills/sherpa/templates/improvement-candidate.md +135 -0
  52. package/.agents/skills/sherpa/templates/improvement.md +18 -0
  53. package/.agents/skills/sherpa/templates/incident.md +20 -0
  54. package/.agents/skills/sherpa/templates/judge-criterion.md +41 -0
  55. package/.agents/skills/sherpa/templates/judge-question.md +41 -0
  56. package/.agents/skills/sherpa/templates/judge-session.md +125 -0
  57. package/.agents/skills/sherpa/templates/judge-simulation.md +264 -0
  58. package/.agents/skills/sherpa/templates/lesson.md +14 -0
  59. package/.agents/skills/sherpa/templates/postmortem.md +87 -0
  60. package/.agents/skills/sherpa/templates/presentation-evidence.md +33 -0
  61. package/.agents/skills/sherpa/templates/presentation-plan.md +349 -0
  62. package/.agents/skills/sherpa/templates/presentation-qna.md +29 -0
  63. package/.agents/skills/sherpa/templates/presentation-risk.md +42 -0
  64. package/.agents/skills/sherpa/templates/presentation-verification.md +57 -0
  65. package/.agents/skills/sherpa/templates/project-plan.md +242 -0
  66. package/.agents/skills/sherpa/templates/project-state.md +84 -0
  67. package/.agents/skills/sherpa/templates/red-team-finding.md +73 -0
  68. package/.agents/skills/sherpa/templates/red-team-review.md +105 -0
  69. package/.agents/skills/sherpa/templates/red-team-scenario.md +43 -0
  70. package/.agents/skills/sherpa/templates/red-team-verification.md +31 -0
  71. package/.agents/skills/sherpa/templates/repo-analysis.md +204 -0
  72. package/.agents/skills/sherpa/templates/requirement-audit.md +15 -0
  73. package/.agents/skills/sherpa/templates/research-comparison.md +124 -0
  74. package/.agents/skills/sherpa/templates/research-plan.md +93 -0
  75. package/.agents/skills/sherpa/templates/research-report.md +92 -0
  76. package/.agents/skills/sherpa/templates/research-source.md +77 -0
  77. package/.agents/skills/sherpa/templates/score-optimization-plan.md +197 -0
  78. package/.agents/skills/sherpa/templates/security-finding.md +45 -0
  79. package/.agents/skills/sherpa/templates/security-review.md +64 -0
  80. package/.agents/skills/sherpa/templates/slide-claim.md +47 -0
  81. package/.agents/skills/sherpa/templates/slide-plan.md +127 -0
  82. package/.agents/skills/sherpa/templates/submission-artifact.md +15 -0
  83. package/.agents/skills/sherpa/templates/submission-checklist.md +24 -0
  84. package/.agents/skills/sherpa/templates/submission-field.md +14 -0
  85. package/.agents/skills/sherpa/templates/submission-link.md +14 -0
  86. package/.agents/skills/sherpa/templates/submission-plan.md +31 -0
  87. package/.agents/skills/sherpa/templates/submission-readiness.md +18 -0
  88. package/.agents/skills/sherpa/templates/submission-requirement.md +12 -0
  89. package/.agents/skills/sherpa/templates/submission-risk.md +14 -0
  90. package/.agents/skills/sherpa/templates/submission-snapshot.md +20 -0
  91. package/.agents/skills/sherpa/templates/submission-verification.md +12 -0
  92. package/.agents/skills/sherpa/templates/test-plan.md +59 -0
  93. package/.agents/skills/sherpa/templates/test-result.md +46 -0
  94. package/.agents/skills/sherpa/templates/timeline.md +11 -0
  95. package/.agents/skills/sherpa/templates/ux-finding.md +56 -0
  96. package/.agents/skills/sherpa/templates/ux-flow.md +46 -0
  97. package/.agents/skills/sherpa/templates/ux-review.md +113 -0
  98. package/.agents/skills/sherpa/templates/ux-verification.md +32 -0
  99. package/.agents/skills/sherpa/workflows/configuration.md +23 -0
  100. package/.agents/skills/sherpa/workflows/demo-preparation.md +399 -0
  101. package/.agents/skills/sherpa/workflows/final-audit.md +713 -0
  102. package/.agents/skills/sherpa/workflows/idea-evaluation.md +239 -0
  103. package/.agents/skills/sherpa/workflows/idea-intelligence.md +69 -0
  104. package/.agents/skills/sherpa/workflows/implementation.md +42 -0
  105. package/.agents/skills/sherpa/workflows/init.md +102 -0
  106. package/.agents/skills/sherpa/workflows/judge-simulation.md +408 -0
  107. package/.agents/skills/sherpa/workflows/lifecycle.md +51 -0
  108. package/.agents/skills/sherpa/workflows/next.md +96 -0
  109. package/.agents/skills/sherpa/workflows/postmortem.md +76 -0
  110. package/.agents/skills/sherpa/workflows/presentation.md +522 -0
  111. package/.agents/skills/sherpa/workflows/project-planning.md +327 -0
  112. package/.agents/skills/sherpa/workflows/red-team.md +373 -0
  113. package/.agents/skills/sherpa/workflows/research.md +142 -0
  114. package/.agents/skills/sherpa/workflows/score-optimization.md +276 -0
  115. package/.agents/skills/sherpa/workflows/status.md +107 -0
  116. package/.agents/skills/sherpa/workflows/submission.md +46 -0
  117. package/.agents/skills/sherpa/workflows/testing-security.md +44 -0
  118. package/.agents/skills/sherpa/workflows/ux-review.md +59 -0
  119. package/.claude-plugin/marketplace.json +21 -0
  120. package/.claude-plugin/plugin.json +13 -0
  121. package/.codex-plugin/plugin.json +13 -0
  122. package/GEMINI.md +3 -0
  123. package/README.md +14 -8
  124. package/SKILL.md +161 -0
  125. package/distribution/cli/sherpa +7 -2
  126. package/distribution/npm/sherpa.mjs +15 -8
  127. package/gemini-extension.json +7 -0
  128. package/package.json +31 -3
  129. package/plugin.json +20 -0
  130. package/qwen-extension.json +6 -0
  131. package/skills/sherpa/SKILL.md +161 -0
  132. package/skills/sherpa/commands/README.md +43 -0
  133. package/skills/sherpa/commands/manifest.json +589 -0
  134. package/skills/sherpa/commands/sherpa-research.md +147 -0
  135. package/skills/sherpa/core/commands/router.py +171 -0
  136. package/skills/sherpa/core/configuration/manager.py +504 -0
  137. package/skills/sherpa/core/ideas/engine.py +564 -0
  138. package/skills/sherpa/docs/commands/README.md +96 -0
  139. package/skills/sherpa/docs/configuration/runtime-verification.md +64 -0
  140. package/skills/sherpa/rules/demo-preparation-rules.md +543 -0
  141. package/skills/sherpa/rules/engineering-rules.md +221 -0
  142. package/skills/sherpa/rules/final-audit-rules.md +824 -0
  143. package/skills/sherpa/rules/implementation-rules.md +127 -0
  144. package/skills/sherpa/rules/judge-rules.md +443 -0
  145. package/skills/sherpa/rules/mentor-rules.md +109 -0
  146. package/skills/sherpa/rules/postmortem-rules.md +69 -0
  147. package/skills/sherpa/rules/presentation-rules.md +783 -0
  148. package/skills/sherpa/rules/red-team-rules.md +185 -0
  149. package/skills/sherpa/rules/research-rules.md +184 -0
  150. package/skills/sherpa/rules/score-optimization-rules.md +355 -0
  151. package/skills/sherpa/rules/scoring-rules.md +216 -0
  152. package/skills/sherpa/rules/submission-rules.md +41 -0
  153. package/skills/sherpa/rules/testing-security-rules.md +87 -0
  154. package/skills/sherpa/rules/ux-rules.md +89 -0
  155. package/skills/sherpa/src/config_manager.py +474 -0
  156. package/skills/sherpa/src/verification.py +458 -0
  157. package/skills/sherpa/templates/audit-finding.md +20 -0
  158. package/skills/sherpa/templates/claim-audit.md +15 -0
  159. package/skills/sherpa/templates/configuration/hackathon.json +21 -0
  160. package/skills/sherpa/templates/configuration/integrations.env.example +3 -0
  161. package/skills/sherpa/templates/configuration/project.env.example +12 -0
  162. package/skills/sherpa/templates/configuration/providers.env.example +4 -0
  163. package/skills/sherpa/templates/configuration/settings.json +22 -0
  164. package/skills/sherpa/templates/consistency-audit.md +17 -0
  165. package/skills/sherpa/templates/decision-log.md +18 -0
  166. package/skills/sherpa/templates/demo-checkpoint.md +42 -0
  167. package/skills/sherpa/templates/demo-evidence.md +32 -0
  168. package/skills/sherpa/templates/demo-finding.md +112 -0
  169. package/skills/sherpa/templates/demo-flow.md +52 -0
  170. package/skills/sherpa/templates/demo-plan.md +265 -0
  171. package/skills/sherpa/templates/demo-verification.md +57 -0
  172. package/skills/sherpa/templates/final-audit.md +387 -0
  173. package/skills/sherpa/templates/final-risk.md +17 -0
  174. package/skills/sherpa/templates/final-verification.md +57 -0
  175. package/skills/sherpa/templates/gap-analysis.md +113 -0
  176. package/skills/sherpa/templates/idea-comparison.md +129 -0
  177. package/skills/sherpa/templates/idea-evaluation.md +251 -0
  178. package/skills/sherpa/templates/implementation-plan.md +96 -0
  179. package/skills/sherpa/templates/implementation-task.md +57 -0
  180. package/skills/sherpa/templates/improvement-candidate.md +135 -0
  181. package/skills/sherpa/templates/improvement.md +18 -0
  182. package/skills/sherpa/templates/incident.md +20 -0
  183. package/skills/sherpa/templates/judge-criterion.md +41 -0
  184. package/skills/sherpa/templates/judge-question.md +41 -0
  185. package/skills/sherpa/templates/judge-session.md +125 -0
  186. package/skills/sherpa/templates/judge-simulation.md +264 -0
  187. package/skills/sherpa/templates/lesson.md +14 -0
  188. package/skills/sherpa/templates/postmortem.md +87 -0
  189. package/skills/sherpa/templates/presentation-evidence.md +33 -0
  190. package/skills/sherpa/templates/presentation-plan.md +349 -0
  191. package/skills/sherpa/templates/presentation-qna.md +29 -0
  192. package/skills/sherpa/templates/presentation-risk.md +42 -0
  193. package/skills/sherpa/templates/presentation-verification.md +57 -0
  194. package/skills/sherpa/templates/project-plan.md +242 -0
  195. package/skills/sherpa/templates/project-state.md +84 -0
  196. package/skills/sherpa/templates/red-team-finding.md +73 -0
  197. package/skills/sherpa/templates/red-team-review.md +105 -0
  198. package/skills/sherpa/templates/red-team-scenario.md +43 -0
  199. package/skills/sherpa/templates/red-team-verification.md +31 -0
  200. package/skills/sherpa/templates/repo-analysis.md +204 -0
  201. package/skills/sherpa/templates/requirement-audit.md +15 -0
  202. package/skills/sherpa/templates/research-comparison.md +124 -0
  203. package/skills/sherpa/templates/research-plan.md +93 -0
  204. package/skills/sherpa/templates/research-report.md +92 -0
  205. package/skills/sherpa/templates/research-source.md +77 -0
  206. package/skills/sherpa/templates/score-optimization-plan.md +197 -0
  207. package/skills/sherpa/templates/security-finding.md +45 -0
  208. package/skills/sherpa/templates/security-review.md +64 -0
  209. package/skills/sherpa/templates/slide-claim.md +47 -0
  210. package/skills/sherpa/templates/slide-plan.md +127 -0
  211. package/skills/sherpa/templates/submission-artifact.md +15 -0
  212. package/skills/sherpa/templates/submission-checklist.md +24 -0
  213. package/skills/sherpa/templates/submission-field.md +14 -0
  214. package/skills/sherpa/templates/submission-link.md +14 -0
  215. package/skills/sherpa/templates/submission-plan.md +31 -0
  216. package/skills/sherpa/templates/submission-readiness.md +18 -0
  217. package/skills/sherpa/templates/submission-requirement.md +12 -0
  218. package/skills/sherpa/templates/submission-risk.md +14 -0
  219. package/skills/sherpa/templates/submission-snapshot.md +20 -0
  220. package/skills/sherpa/templates/submission-verification.md +12 -0
  221. package/skills/sherpa/templates/test-plan.md +59 -0
  222. package/skills/sherpa/templates/test-result.md +46 -0
  223. package/skills/sherpa/templates/timeline.md +11 -0
  224. package/skills/sherpa/templates/ux-finding.md +56 -0
  225. package/skills/sherpa/templates/ux-flow.md +46 -0
  226. package/skills/sherpa/templates/ux-review.md +113 -0
  227. package/skills/sherpa/templates/ux-verification.md +32 -0
  228. package/skills/sherpa/workflows/configuration.md +23 -0
  229. package/skills/sherpa/workflows/demo-preparation.md +399 -0
  230. package/skills/sherpa/workflows/final-audit.md +713 -0
  231. package/skills/sherpa/workflows/idea-evaluation.md +239 -0
  232. package/skills/sherpa/workflows/idea-intelligence.md +69 -0
  233. package/skills/sherpa/workflows/implementation.md +42 -0
  234. package/skills/sherpa/workflows/init.md +102 -0
  235. package/skills/sherpa/workflows/judge-simulation.md +408 -0
  236. package/skills/sherpa/workflows/lifecycle.md +51 -0
  237. package/skills/sherpa/workflows/next.md +96 -0
  238. package/skills/sherpa/workflows/postmortem.md +76 -0
  239. package/skills/sherpa/workflows/presentation.md +522 -0
  240. package/skills/sherpa/workflows/project-planning.md +327 -0
  241. package/skills/sherpa/workflows/red-team.md +373 -0
  242. package/skills/sherpa/workflows/research.md +142 -0
  243. package/skills/sherpa/workflows/score-optimization.md +276 -0
  244. package/skills/sherpa/workflows/status.md +107 -0
  245. package/skills/sherpa/workflows/submission.md +46 -0
  246. package/skills/sherpa/workflows/testing-security.md +44 -0
  247. package/skills/sherpa/workflows/ux-review.md +59 -0
@@ -0,0 +1,504 @@
1
+ #!/usr/bin/env python3
2
+ """sherpa project configuration utility (Python standard library only).
3
+ Never prints, logs, or persists secret values. Use from the target project root.
4
+ """
5
+ from __future__ import annotations
6
+ import argparse
7
+ import datetime as dt
8
+ import json
9
+ import os
10
+ import re
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Any
15
+ from urllib.parse import urlparse
16
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
17
+
18
+ UNKNOWN = "UNKNOWN"
19
+ STATE_DIRECTORY = ".sherpa"
20
+ LEGACY_STATE_DIRECTORY = ".hackathon"
21
+ BOOLEAN_KEYS = {
22
+ "ENABLE_WEB_RESEARCH", "ENABLE_AUTO_VERIFY", "ENABLE_RED_TEAM", "ENABLE_VIDEO_ASSIST",
23
+ "REQUIRE_HUMAN_APPROVAL",
24
+ }
25
+ INTEGER_LIMITS = {"MAX_AUTO_ITERATIONS": (1, 20), "MAX_AUTO_FIXES": (0, 50)}
26
+ SECRET_ENV_KEYS = ("AI_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "GITHUB_TOKEN")
27
+ SECRET_FILE_NAMES = {"credentials.json", "service-account.json", "secrets.json"}
28
+ SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "dist", "build", "coverage", "__pycache__"}
29
+ SECRET_PATTERNS = (
30
+ ("private key", re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----")),
31
+ ("AWS access key", re.compile(r"\bAKIA[0-9A-Z]{16}\b")),
32
+ ("OpenAI-style API key", re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b")),
33
+ ("GitHub token", re.compile(r"\bgh[pousr]_[A-Za-z0-9]{30,}\b")),
34
+ ("Google API key", re.compile(r"\bAIza[0-9A-Za-z_-]{30,}\b")),
35
+ ("Bearer token", re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]{20,}")),
36
+ ("password or secret assignment", re.compile(r"(?i)\b(?:password|passwd|client_secret|api[_-]?key)\s*[:=]\s*[\"']?[^\s\"'<>]{8,}")),
37
+ ("database URL with password", re.compile(r"(?i)\b(?:postgres|mysql|mongodb)(?:ql)?://[^:\s/]+:[^@\s/]{4,}@")),
38
+ )
39
+
40
+
41
+ def parse_env_file(path: Path) -> tuple[dict[str, str], list[str]]:
42
+ """Parse a small dotenv subset; diagnostics never include source values."""
43
+ values: dict[str, str] = {}
44
+ issues: list[str] = []
45
+ if not path.exists():
46
+ return values, issues
47
+ try:
48
+ lines = path.read_text(encoding="utf-8-sig").splitlines()
49
+ except (OSError, UnicodeError):
50
+ return values, [".env could not be read as UTF-8"]
51
+ key_re = re.compile(r"^[A-Z_][A-Z0-9_]*$")
52
+ for number, raw in enumerate(lines, 1):
53
+ line = raw.strip()
54
+ if not line or line.startswith("#"):
55
+ continue
56
+ if line.startswith("export "):
57
+ line = line[7:].lstrip()
58
+ if "=" not in line:
59
+ issues.append(f".env line {number}: expected KEY=value")
60
+ continue
61
+ key, value = line.split("=", 1)
62
+ key = key.strip()
63
+ if not key_re.fullmatch(key):
64
+ issues.append(f".env line {number}: invalid variable name")
65
+ continue
66
+ if key in values:
67
+ issues.append(f".env line {number}: duplicate variable {key}")
68
+ continue
69
+ value = value.strip()
70
+ if value.startswith(('"', "'")):
71
+ quote = value[0]
72
+ if len(value) < 2 or not value.endswith(quote):
73
+ issues.append(f".env line {number}: unterminated quoted value for {key}")
74
+ continue
75
+ value = value[1:-1]
76
+ elif " #" in value:
77
+ value = value.split(" #", 1)[0].rstrip()
78
+ values[key] = value
79
+ return values, issues
80
+
81
+
82
+ def resolve_value(explicit: Any, project: Any, process_env: Any, dotenv: Any, default: Any = None) -> Any:
83
+ """Precedence: explicit > project > process env > .env > default > UNKNOWN.
84
+ Empty and UNKNOWN values are unresolved and fall through; literal UNKNOWN is
85
+ returned when no lower-precedence source supplies a value.
86
+ """
87
+ for value in (explicit, project, process_env, dotenv, default):
88
+ if value is None:
89
+ continue
90
+ if isinstance(value, str) and value.strip() == "":
91
+ continue
92
+ if isinstance(value, str) and value.strip().upper() == UNKNOWN:
93
+ continue
94
+ return value
95
+ return UNKNOWN
96
+
97
+
98
+ def load_json(path: Path) -> tuple[dict[str, Any], list[str]]:
99
+ if not path.exists():
100
+ return {}, [f"Missing {path.name}"]
101
+ try:
102
+ value = json.loads(path.read_text(encoding="utf-8-sig"))
103
+ if not isinstance(value, dict):
104
+ return {}, [f"{path.name}: top level must be a JSON object"]
105
+ return value, []
106
+ except (json.JSONDecodeError, UnicodeError, OSError) as exc:
107
+ if isinstance(exc, json.JSONDecodeError):
108
+ return {}, [f"{path.name}:{exc.lineno}: malformed JSON"]
109
+ return {}, [f"{path.name}: could not be read"]
110
+
111
+
112
+ def nonempty(value: Any) -> bool:
113
+ return value is not None and not (isinstance(value, str) and value.strip() in ("", UNKNOWN))
114
+
115
+
116
+ def validate_url(value: Any) -> str:
117
+ if not nonempty(value):
118
+ return "UNKNOWN"
119
+ try:
120
+ parsed = urlparse(str(value))
121
+ return "CONFIGURED" if parsed.scheme in ("http", "https") and parsed.hostname else "INVALID"
122
+ except ValueError:
123
+ return "INVALID"
124
+
125
+
126
+ def validate_timezone(value: Any) -> str:
127
+ if not nonempty(value):
128
+ return "UNKNOWN"
129
+ try:
130
+ ZoneInfo(str(value))
131
+ return "CONFIGURED"
132
+ except ZoneInfoNotFoundError:
133
+ return "INVALID"
134
+ except (ValueError, OSError):
135
+ return "INVALID"
136
+
137
+
138
+ def validate_deadline(value: Any) -> str:
139
+ if not nonempty(value):
140
+ return "UNKNOWN"
141
+ try:
142
+ dt.datetime.fromisoformat(str(value).replace("Z", "+00:00"))
143
+ return "CONFIGURED"
144
+ except (ValueError, TypeError):
145
+ return "INVALID"
146
+
147
+
148
+ def git_output(root: Path, *args: str) -> subprocess.CompletedProcess[str]:
149
+ try:
150
+ return subprocess.run(["git", *args], cwd=root, capture_output=True, text=True, check=False)
151
+ except (OSError, subprocess.SubprocessError):
152
+ return subprocess.CompletedProcess(["git", *args], 127, "", "git unavailable")
153
+
154
+
155
+ def is_git_root(root: Path) -> bool:
156
+ result = git_output(root, "rev-parse", "--show-toplevel")
157
+ if result.returncode:
158
+ return False
159
+ try:
160
+ return Path(result.stdout.strip()).resolve() == root.resolve()
161
+ except OSError:
162
+ return False
163
+
164
+
165
+ def is_env_ignored(root: Path) -> bool:
166
+ result = git_output(root, "check-ignore", "--quiet", ".env")
167
+ if result.returncode == 0:
168
+ return True
169
+ ignore = root / ".gitignore"
170
+ if not ignore.is_file():
171
+ return False
172
+ patterns = [x.strip() for x in ignore.read_text(encoding="utf-8-sig").splitlines() if x.strip() and not x.lstrip().startswith("#")]
173
+ ignored = ".env" in patterns or ".env.*" in patterns
174
+ if "!.env" in patterns:
175
+ ignored = False
176
+ return ignored
177
+
178
+
179
+ def tracked_env(root: Path) -> bool:
180
+ if not is_git_root(root):
181
+ return False
182
+ result = git_output(root, "ls-files", "--error-unmatch", ".env")
183
+ return result.returncode == 0
184
+
185
+
186
+ def scan_secrets(root: Path) -> list[str]:
187
+ """Return redacted type/path/line indicators; never return matched text."""
188
+ findings: list[str] = []
189
+ for directory, dirs, files in os.walk(root):
190
+ dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
191
+ base = Path(directory)
192
+ for name in files:
193
+ path = base / name
194
+ rel = path.relative_to(root).as_posix()
195
+ if name in SECRET_FILE_NAMES or path.suffix.lower() in (".pem", ".key", ".p12", ".pfx", ".jks"):
196
+ findings.append(f"sensitive credential file: {rel}")
197
+ continue
198
+ if name == ".env" or (name.startswith(".env.") and not name.endswith(".example")):
199
+ continue
200
+ if path.suffix.lower() not in (".md", ".txt", ".json", ".jsonc", ".yaml", ".yml", ".toml", ".py", ".js", ".ts", ".tsx", ".jsx", ".html", ".xml", ".sh", ".ps1", ".env", ".example"):
201
+ continue
202
+ try:
203
+ if path.stat().st_size > 1_000_000:
204
+ continue
205
+ lines = path.read_text(encoding="utf-8-sig").splitlines()
206
+ except (OSError, UnicodeError):
207
+ continue
208
+ for number, line in enumerate(lines, 1):
209
+ for label, pattern in SECRET_PATTERNS:
210
+ if pattern.search(line):
211
+ findings.append(f"{label}: {rel}:{number}")
212
+ break
213
+ return findings
214
+
215
+
216
+ def env_value(key: str, dotenv: dict[str, str], process_env: dict[str, str]) -> Any:
217
+ return resolve_value(None, None, process_env.get(key), dotenv.get(key))
218
+
219
+
220
+ def project_state_dir(root: Path) -> Path:
221
+ """Prefer Sherpa state; read the former directory as a one-release fallback."""
222
+ current = root / STATE_DIRECTORY
223
+ legacy = root / LEGACY_STATE_DIRECTORY
224
+ if current.exists():
225
+ return current
226
+ if legacy.exists():
227
+ return legacy
228
+ return current
229
+
230
+
231
+ def legacy_state_warning(root: Path) -> str | None:
232
+ if not (root / STATE_DIRECTORY).exists() and (root / LEGACY_STATE_DIRECTORY).exists():
233
+ return ("Deprecated .hackathon/ state is being read. Run `sherpa migrate` to copy it "
234
+ "to .sherpa/; the original directory will be preserved.")
235
+ return None
236
+
237
+
238
+ def config_snapshot(root: Path, process_env: dict[str, str] | None = None) -> dict[str, Any]:
239
+ process_env = dict(os.environ if process_env is None else process_env)
240
+ state_root = project_state_dir(root)
241
+ hackathon, h_errors = load_json(state_root / "hackathon.json")
242
+ settings, s_errors = load_json(state_root / "settings.json")
243
+ dotenv, env_errors = parse_env_file(root / ".env")
244
+ # Missing configuration is an expected pre-INIT state, not a malformed one.
245
+ errors = [e for e in h_errors + s_errors + env_errors if not e.startswith("Missing ")]
246
+ result: dict[str, Any] = {"errors": errors, "warnings": [], "fields": {}, "secret_findings": [], "secret_configured": False}
247
+ warning = legacy_state_warning(root)
248
+ if warning:
249
+ result["warnings"].append(warning)
250
+
251
+ def value(path: tuple[str, ...], env_key: str | None = None, default: Any = None) -> Any:
252
+ current: Any = hackathon
253
+ for segment in path:
254
+ current = current.get(segment) if isinstance(current, dict) else None
255
+ return resolve_value(None, current, process_env.get(env_key) if env_key else None,
256
+ dotenv.get(env_key) if env_key else None, default)
257
+
258
+ if not (state_root / "project-state.md").exists():
259
+ result["warnings"].append("Missing project-state.md")
260
+ result["fields"]["Current phase"] = UNKNOWN
261
+ else:
262
+ try:
263
+ state_text = (state_root / "project-state.md").read_text(encoding="utf-8-sig")
264
+ phase_match = re.search(r"(?im)^\|\s*Phase\s*\|\s*([^|]+?)\s*\|", state_text)
265
+ result["fields"]["Current phase"] = phase_match.group(1).strip() if phase_match and phase_match.group(1).strip().upper() != UNKNOWN else UNKNOWN
266
+ except (OSError, UnicodeError):
267
+ result["fields"]["Current phase"] = UNKNOWN
268
+ result["fields"]["Hackathon name"] = value(("name",), "HACKATHON_NAME")
269
+ result["fields"]["Hackathon URL status"] = validate_url(value(("url",), "HACKATHON_URL"))
270
+ result["fields"]["Platform"] = value(("platform",), "HACKATHON_PLATFORM")
271
+ result["fields"]["Edition"] = value(("edition",), "HACKATHON_EDITION")
272
+ result["fields"]["Deadline status"] = validate_deadline(value(("deadline", "datetime"), "HACKATHON_DEADLINE"))
273
+ result["fields"]["Timezone status"] = validate_timezone(value(("deadline", "timezone"), "HACKATHON_TIMEZONE"))
274
+ for check_name in ("Hackathon URL status", "Deadline status", "Timezone status"):
275
+ if result["fields"][check_name] == "INVALID": result["errors"].append(f"{check_name}: invalid value")
276
+ for label, field, env_key in (("Project name", ("project", "name"), "PROJECT_NAME"),
277
+ ("Project path", ("project", "path"), "PROJECT_PATH"),
278
+ ("Repository", ("project", "repository"), "GITHUB_REPOSITORY"),
279
+ ("Deployment URL status", ("project", "deployment"), "DEPLOYMENT_URL")):
280
+ val = value(field, env_key)
281
+ if label == "Project path" and nonempty(val):
282
+ path = Path(str(val)); path = path if path.is_absolute() else (root / path)
283
+ result["fields"][label] = "CONFIGURED" if path.exists() else "INVALID"
284
+ elif label == "Repository" and nonempty(val):
285
+ result["fields"][label] = "CONFIGURED" if (validate_url(val) == "CONFIGURED" or re.fullmatch(r"[\w.-]+/[\w.-]+", str(val))) else "INVALID"
286
+ elif label == "Deployment URL status":
287
+ result["fields"][label] = validate_url(val)
288
+ else:
289
+ result["fields"][label] = "CONFIGURED" if nonempty(val) else "UNKNOWN"
290
+
291
+ setting_fields = (
292
+ ("Agent platform", ("agent_platform",), "AGENT_PLATFORM", None),
293
+ ("AI provider", ("ai_provider",), "AI_PROVIDER", None),
294
+ ("AI model", ("ai_model",), "AI_MODEL", None),
295
+ ("Verification", ("verification", "enabled"), "ENABLE_AUTO_VERIFY", True),
296
+ ("Red team", ("red_team", "enabled"), "ENABLE_RED_TEAM", True),
297
+ ("Research", ("research", "enabled"), "ENABLE_WEB_RESEARCH", True),
298
+ ("Video assist", ("video", "enabled"), "ENABLE_VIDEO_ASSIST", True),
299
+ ("Max iterations", ("autonomous_loop", "max_iterations"), "MAX_AUTO_ITERATIONS", 5),
300
+ ("Max auto fixes", ("autonomous_loop", "max_auto_fixes"), "MAX_AUTO_FIXES", 10),
301
+ ("Human approval", ("autonomous_loop", "require_human_approval"), "REQUIRE_HUMAN_APPROVAL", True),
302
+ )
303
+ for label, path, env_key, default in setting_fields:
304
+ current: Any = settings
305
+ for segment in path:
306
+ current = current.get(segment) if isinstance(current, dict) else None
307
+ resolved = resolve_value(None, current, process_env.get(env_key), dotenv.get(env_key), default)
308
+ if label in ("Max iterations", "Max auto fixes"):
309
+ try:
310
+ number = int(resolved)
311
+ low, high = INTEGER_LIMITS[env_key]
312
+ result["fields"][label] = number if low <= number <= high else "INVALID"
313
+ except (ValueError, TypeError): result["fields"][label] = "INVALID" if resolved != UNKNOWN else UNKNOWN
314
+ elif label in ("Verification", "Red team", "Research", "Video assist", "Human approval"):
315
+ if isinstance(resolved, bool): result["fields"][label] = resolved
316
+ elif str(resolved).lower() in ("true", "false"): result["fields"][label] = str(resolved).lower() == "true"
317
+ else: result["fields"][label] = "INVALID" if resolved != UNKNOWN else UNKNOWN
318
+ else:
319
+ result["fields"][label] = resolved if nonempty(resolved) else UNKNOWN
320
+ if result["fields"].get(label) == "INVALID": result["errors"].append(f"{label}: invalid value")
321
+
322
+ result["fields"]["Git repository"] = "CONFIGURED" if is_git_root(root) else "MISSING"
323
+ result["secret_configured"] = any(nonempty(process_env.get(k)) or nonempty(dotenv.get(k)) for k in SECRET_ENV_KEYS)
324
+
325
+ for key in BOOLEAN_KEYS:
326
+ raw = process_env.get(key, dotenv.get(key))
327
+ if raw is not None and str(raw).strip() and str(raw).lower() not in ("true", "false"):
328
+ result["errors"].append(f"{key}: expected true or false")
329
+ for key, (low, high) in INTEGER_LIMITS.items():
330
+ raw = process_env.get(key, dotenv.get(key))
331
+ if raw is not None and str(raw).strip():
332
+ try: number = int(raw)
333
+ except (ValueError, TypeError): result["errors"].append(f"{key}: expected an integer"); continue
334
+ if not low <= number <= high: result["errors"].append(f"{key}: must be between {low} and {high}")
335
+ if result["fields"].get("Human approval") is False:
336
+ result["warnings"].append("WARNING: Human approval is disabled for autonomous operations.")
337
+ if (root / ".env").exists() and not is_env_ignored(root):
338
+ result["errors"].append(".env exists but is not ignored by Git")
339
+ if tracked_env(root):
340
+ result["errors"].append(".env is tracked by Git; remove it from the index and rotate any exposed credentials")
341
+ result["secret_findings"] = scan_secrets(root)
342
+ if result["secret_findings"]:
343
+ result["errors"].append(f"Secret scan found {len(result['secret_findings'])} indicator(s) in scanned files")
344
+ if result["errors"]:
345
+ result["status"] = "INVALID"
346
+ elif not (state_root / "hackathon.json").exists() and not (state_root / "settings.json").exists():
347
+ result["status"] = "MISSING"
348
+ else:
349
+ unknowns = sum(1 for val in result["fields"].values() if val == UNKNOWN)
350
+ result["status"] = "CONFIGURED" if unknowns == 0 else "PARTIALLY_CONFIGURED"
351
+ return result
352
+
353
+
354
+ def print_overview(snapshot: dict[str, Any]) -> None:
355
+ f = snapshot["fields"]
356
+ print("Sherpa CONFIGURATION")
357
+ print(f"Status: {snapshot['status']}")
358
+ print(f"Current phase: {f.get('Current phase', UNKNOWN)}")
359
+ for label in ("Hackathon name", "Platform", "Edition", "Project name", "Project path"):
360
+ value = f.get(label, UNKNOWN)
361
+ print(f"{label}: {value if nonempty(value) else UNKNOWN}")
362
+ for label in ("Hackathon URL status", "Deadline status", "Timezone status", "Repository", "Deployment URL status"):
363
+ print(f"{label}: {f.get(label, UNKNOWN)}")
364
+ print(f"AI provider: {f.get('AI provider', UNKNOWN)}")
365
+ print(f"AI credential: {'Configured' if snapshot.get('secret_configured') else 'Missing'}")
366
+ for label in ("Verification", "Red team", "Research", "Video assist"):
367
+ value=f.get(label, UNKNOWN); print(f"{label}: {value if value is not UNKNOWN else 'UNKNOWN'}")
368
+ print(f"Max iterations: {f.get('Max iterations', UNKNOWN)}")
369
+ print(f"Max auto fixes: {f.get('Max auto fixes', UNKNOWN)}")
370
+ print(f"Human approval: {'REQUIRED' if f.get('Human approval') is True else 'DISABLED' if f.get('Human approval') is False else UNKNOWN}")
371
+ for warning in snapshot["warnings"]: print(warning)
372
+
373
+
374
+ def print_check(snapshot: dict[str, Any], root: Path) -> None:
375
+ print(f"Configuration: {snapshot['status']}")
376
+ for label, value in snapshot["fields"].items(): print(f"{label}: {value}")
377
+ dotenv, _ = parse_env_file(root / ".env")
378
+ for key in SECRET_ENV_KEYS: print(f"{key}: {'Configured' if nonempty(os.environ.get(key)) or nonempty(dotenv.get(key)) else 'Missing'}")
379
+ print(f".env ignored: {'YES' if is_env_ignored(root) else 'NO / NOT APPLICABLE'}")
380
+ print(f".env tracked: {'YES' if tracked_env(root) else 'NO'}")
381
+ if snapshot["secret_findings"]:
382
+ print("Secret scan: FAIL — indicators found (values redacted)")
383
+ for finding in snapshot["secret_findings"]: print(f" {finding}")
384
+ else:
385
+ print("Secret scan: PASS — no known secret patterns detected in the scanned scope")
386
+ for warning in snapshot["warnings"]: print(warning)
387
+ for error in snapshot["errors"]: print(f"ERROR: {error}")
388
+ if not snapshot["errors"]:
389
+ print("No configuration errors detected. External provider connectivity was not tested.")
390
+ elif snapshot["status"] == "MISSING":
391
+ print("Configuration files are missing; run INIT to create them without overwriting existing data.")
392
+
393
+
394
+ def ensure_gitignore(root: Path) -> list[str]:
395
+ path = root / ".gitignore"
396
+ original = path.read_text(encoding="utf-8-sig") if path.exists() else ""
397
+ required = (".env", ".env.*", "!.env.sherpa.example", "!.env.*.example", "*.key", "*.pem",
398
+ "credentials.json", "service-account.json", "secrets.json")
399
+ present = set(line.strip() for line in original.splitlines())
400
+ additions = [line for line in required if line not in present]
401
+ if additions:
402
+ prefix = original
403
+ if prefix and not prefix.endswith(("\n", "\r")): prefix += "\n"
404
+ if prefix and not prefix.endswith("\n\n"): prefix += "\n"
405
+ path.write_text(prefix + "# sherpa local environment and credentials\n" + "\n".join(additions) + "\n", encoding="utf-8")
406
+ return additions
407
+
408
+
409
+ def init_project(root: Path, create_env: bool = False) -> tuple[list[str], list[str]]:
410
+ root = root.resolve()
411
+ if not root.is_dir(): raise ValueError("project root does not exist")
412
+ if ((root / "SKILL.md").exists() and (root / "workflows" / "lifecycle.md").exists()
413
+ and (root / "commands" / "manifest.json").exists()):
414
+ raise ValueError("refusing to initialize the sherpa framework repository; run from a hackathon project root")
415
+ template_root = Path(__file__).resolve().parents[2]
416
+ created: list[str] = []; preserved: list[str] = []
417
+ state_root = project_state_dir(root)
418
+ warning = legacy_state_warning(root)
419
+ if warning:
420
+ raise ValueError(warning + " Run `sherpa migrate` before initializing or writing state.")
421
+ state_root = root / STATE_DIRECTORY
422
+ destinations = {
423
+ ".env.sherpa.example": template_root / ".env.sherpa.example",
424
+ f"{state_root.name}/hackathon.json": template_root / "templates/configuration/hackathon.json",
425
+ f"{state_root.name}/settings.json": template_root / "templates/configuration/settings.json",
426
+ f"{state_root.name}/config/project.env.example": template_root / "templates/configuration/project.env.example",
427
+ f"{state_root.name}/config/providers.env.example": template_root / "templates/configuration/providers.env.example",
428
+ f"{state_root.name}/config/integrations.env.example": template_root / "templates/configuration/integrations.env.example",
429
+ f"{state_root.name}/project-state.md": template_root / "templates/project-state.md",
430
+ }
431
+ for relative, source in destinations.items():
432
+ target = root / relative
433
+ if target.exists():
434
+ preserved.append(relative)
435
+ continue
436
+ target.parent.mkdir(parents=True, exist_ok=True)
437
+ content = source.read_text(encoding="utf-8-sig")
438
+ if relative == f"{state_root.name}/hackathon.json":
439
+ data=json.loads(content); data["project"]["path"]=str(root); content=json.dumps(data,indent=2)+"\n"
440
+ if relative == f"{state_root.name}/project-state.md":
441
+ content=content.replace("| Phase | UNKNOWN |", "| Phase | Understand Rules |", 1)
442
+ content=content.replace("| State last updated | UNKNOWN |", f"| State last updated | {dt.datetime.now(dt.timezone.utc).isoformat(timespec='seconds')} |", 1)
443
+ target.write_text(content,encoding="utf-8")
444
+ created.append(relative)
445
+ if create_env:
446
+ target=root/".env"
447
+ if target.exists(): preserved.append(".env")
448
+ else:
449
+ target.write_text((template_root/".env.sherpa.example").read_text(encoding="utf-8-sig"),encoding="utf-8")
450
+ created.append(".env")
451
+ additions=ensure_gitignore(root)
452
+ if additions: created.append(".gitignore rules: "+", ".join(additions))
453
+ else: preserved.append(".gitignore (required protection already present)")
454
+ return created,preserved
455
+
456
+
457
+ def parse_overrides(values: list[str]) -> dict[str, str]:
458
+ result={}
459
+ for item in values:
460
+ if "=" not in item: raise ValueError("--override must use KEY=VALUE; value omitted from diagnostics")
461
+ key,value=item.split("=",1)
462
+ if not re.fullmatch(r"[A-Z_][A-Z0-9_]*",key): raise ValueError("invalid --override variable name")
463
+ result[key]=value
464
+ return result
465
+
466
+
467
+ def main(argv: list[str] | None = None) -> int:
468
+ parser=argparse.ArgumentParser(description="Sherpa project configuration utility")
469
+ parser.add_argument("--project-root",default=".",help="hackathon project root (default: current directory)")
470
+ sub=parser.add_subparsers(dest="command",required=True)
471
+ sub.add_parser("config",help="show redacted configuration overview")
472
+ check=sub.add_parser("check",help="validate config, environment, Git protection, and secrets")
473
+ check.add_argument("--override",action="append",default=[],metavar="KEY=VALUE",help="transient non-secret precedence override")
474
+ init=sub.add_parser("init",help="create missing project configuration without overwriting")
475
+ init.add_argument("--create-env",action="store_true",help="also create an empty-placeholder .env from .env.example")
476
+ args=parser.parse_args(argv)
477
+ root=Path(args.project_root).expanduser().resolve()
478
+ if not root.is_dir(): print("ERROR: project root does not exist"); return 2
479
+ if args.command=="init":
480
+ try:
481
+ created,preserved=init_project(root,args.create_env)
482
+ print("Sherpa configuration initialization")
483
+ print("Created: "+(", ".join(created) if created else "nothing; all configuration already existed"))
484
+ print("Preserved: "+(", ".join(preserved) if preserved else "none"))
485
+ return 0
486
+ except (OSError,ValueError,json.JSONDecodeError) as exc:
487
+ print(f"ERROR: {exc}"); return 2
488
+ snapshot=config_snapshot(root)
489
+ if args.command=="config": print_overview(snapshot); return 0
490
+ try: overrides=parse_overrides(args.override)
491
+ except ValueError as exc: print(f"ERROR: {exc}"); return 2
492
+ # Overrides affect validation only and are never persisted or printed.
493
+ dotenv,env_errors=parse_env_file(root/".env")
494
+ for key,value in overrides.items():
495
+ if key in SECRET_ENV_KEYS: print("ERROR: secret overrides are not allowed on the command line"); return 2
496
+ dotenv[key]=value
497
+ if overrides:
498
+ isolated=config_snapshot(root,process_env={**os.environ,**overrides})
499
+ snapshot=isolated
500
+ print_check(snapshot, root)
501
+ return 1 if snapshot["errors"] else 0
502
+
503
+ if __name__=="__main__":
504
+ raise SystemExit(main())