@neocage/council 1.3.0
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.
- package/CHANGELOG.md +75 -0
- package/LICENSE +22 -0
- package/README.md +258 -0
- package/SKILL.codex.md +250 -0
- package/SKILL.gemini.md +241 -0
- package/SKILL.md +940 -0
- package/SKILL.opencode.md +887 -0
- package/agents/council-ada.md +95 -0
- package/agents/council-aristotle.md +92 -0
- package/agents/council-aurelius.md +95 -0
- package/agents/council-feynman.md +95 -0
- package/agents/council-kahneman.md +95 -0
- package/agents/council-karpathy.md +95 -0
- package/agents/council-lao-tzu.md +95 -0
- package/agents/council-machiavelli.md +95 -0
- package/agents/council-meadows.md +95 -0
- package/agents/council-munger.md +95 -0
- package/agents/council-musashi.md +95 -0
- package/agents/council-rams.md +95 -0
- package/agents/council-socrates.md +93 -0
- package/agents/council-sun-tzu.md +95 -0
- package/agents/council-sutskever.md +95 -0
- package/agents/council-taleb.md +95 -0
- package/agents/council-torvalds.md +95 -0
- package/agents/council-watts.md +95 -0
- package/bin/council.js +42 -0
- package/configs/auto-route-defaults.yaml +44 -0
- package/configs/provider-model-slots.cursor.example.yaml +79 -0
- package/configs/provider-model-slots.example.yaml +51 -0
- package/configs/provider-model-slots.nim.example.yaml +83 -0
- package/install.sh +432 -0
- package/package.json +48 -0
- package/scripts/convert-agents-opencode.py +67 -0
- package/scripts/council-simulation-checklist.sh +313 -0
- package/scripts/detect-providers.sh +141 -0
- package/scripts/gen-star-history.py +155 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
cd "${REPO_DIR}"
|
|
7
|
+
|
|
8
|
+
echo "== Council Simulation Checklist =="
|
|
9
|
+
|
|
10
|
+
pass() { echo "[PASS] $1"; }
|
|
11
|
+
fail() { echo "[FAIL] $1"; exit 1; }
|
|
12
|
+
warn() { echo "[WARN] $1"; }
|
|
13
|
+
|
|
14
|
+
# --- File existence checks ---
|
|
15
|
+
|
|
16
|
+
[[ -f "SKILL.md" ]] || fail "SKILL.md is missing"
|
|
17
|
+
pass "SKILL.md exists"
|
|
18
|
+
[[ -f "SKILL.codex.md" ]] || fail "SKILL.codex.md is missing"
|
|
19
|
+
pass "SKILL.codex.md exists"
|
|
20
|
+
[[ -f "SKILL.gemini.md" ]] || fail "SKILL.gemini.md is missing"
|
|
21
|
+
pass "SKILL.gemini.md exists"
|
|
22
|
+
|
|
23
|
+
if compgen -G "agents/council-*.md" >/dev/null; then
|
|
24
|
+
agent_count=$(compgen -G "agents/council-*.md" | wc -l | tr -d ' ')
|
|
25
|
+
pass "Agent definitions found (count=${agent_count})"
|
|
26
|
+
else
|
|
27
|
+
fail "No agent definitions found under agents/council-*.md"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
[[ -f "configs/provider-model-slots.example.yaml" ]] || fail "configs/provider-model-slots.example.yaml is missing"
|
|
31
|
+
pass "Provider/model slot template exists"
|
|
32
|
+
|
|
33
|
+
# --- Plugin manifest checks ---
|
|
34
|
+
|
|
35
|
+
[[ -f ".claude-plugin/plugin.json" ]] || fail ".claude-plugin/plugin.json is missing"
|
|
36
|
+
[[ -f ".claude-plugin/marketplace.json" ]] || fail ".claude-plugin/marketplace.json is missing"
|
|
37
|
+
[[ -e "skills/council/SKILL.md" ]] || fail "skills/council/SKILL.md (plugin skill path) is missing or a broken symlink"
|
|
38
|
+
pass "Plugin manifests and skill path present"
|
|
39
|
+
|
|
40
|
+
changelog_version=$(sed -nE 's/^## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/p' CHANGELOG.md | head -1)
|
|
41
|
+
plugin_version=$(sed -nE 's/.*"version": "([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p' .claude-plugin/plugin.json | head -1)
|
|
42
|
+
marketplace_version=$(sed -nE 's/.*"version": "([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p' .claude-plugin/marketplace.json | head -1)
|
|
43
|
+
if [[ "$plugin_version" != "$changelog_version" ]] || [[ "$marketplace_version" != "$changelog_version" ]]; then
|
|
44
|
+
fail "Version drift: CHANGELOG=${changelog_version} plugin.json=${plugin_version} marketplace.json=${marketplace_version} — bump the manifests when releasing"
|
|
45
|
+
fi
|
|
46
|
+
pass "Plugin manifest versions match CHANGELOG (${changelog_version})"
|
|
47
|
+
|
|
48
|
+
[[ -f "CLAUDE.md" ]] || warn "CLAUDE.md is missing (recommended for project conventions)"
|
|
49
|
+
|
|
50
|
+
# --- SKILL.md content checks ---
|
|
51
|
+
|
|
52
|
+
grep -q "exploration-orthogonal" SKILL.md || fail "exploration-orthogonal profile missing in SKILL.md"
|
|
53
|
+
pass "exploration-orthogonal profile documented in SKILL.md"
|
|
54
|
+
|
|
55
|
+
grep -q "execution-lean" SKILL.md || fail "execution-lean profile missing in SKILL.md"
|
|
56
|
+
pass "execution-lean profile documented in SKILL.md"
|
|
57
|
+
|
|
58
|
+
grep -q -- "--models" SKILL.md || fail "--models flag missing in SKILL.md"
|
|
59
|
+
pass "--models flag documented in SKILL.md"
|
|
60
|
+
|
|
61
|
+
grep -q -- "--quick" SKILL.md || fail "--quick flag missing in SKILL.md"
|
|
62
|
+
pass "--quick mode documented in SKILL.md"
|
|
63
|
+
|
|
64
|
+
grep -q -- "--duo" SKILL.md || fail "--duo flag missing in SKILL.md"
|
|
65
|
+
pass "--duo mode documented in SKILL.md"
|
|
66
|
+
|
|
67
|
+
grep -q "CHECKPOINT" SKILL.md || fail "Execution checkpoints missing in SKILL.md"
|
|
68
|
+
pass "Execution checkpoints present in SKILL.md"
|
|
69
|
+
|
|
70
|
+
grep -q "VERIFY" SKILL.md || fail "Verification steps missing in SKILL.md"
|
|
71
|
+
pass "Verification steps present in SKILL.md"
|
|
72
|
+
|
|
73
|
+
# Round 2 anonymization (issue #17) — protect against silent regression
|
|
74
|
+
grep -q "ANONYMIZED" SKILL.md || fail "Round 2 anonymization missing in SKILL.md (issue #17)"
|
|
75
|
+
grep -q "Member A" SKILL.md || fail "Member-label vocabulary missing in SKILL.md (issue #17)"
|
|
76
|
+
pass "Round 2 anonymization wired in SKILL.md"
|
|
77
|
+
|
|
78
|
+
grep -q "anonymiz" SKILL.codex.md || fail "Round 2 anonymization missing in SKILL.codex.md (issue #17)"
|
|
79
|
+
pass "Round 2 anonymization wired in SKILL.codex.md"
|
|
80
|
+
|
|
81
|
+
# Anti-conformity directive (issue #19) — must be in every Round 2 prompt
|
|
82
|
+
ac_count_skill=$(grep -c "Anti-conformity directive" SKILL.md || true)
|
|
83
|
+
if [[ "$ac_count_skill" -lt 3 ]]; then
|
|
84
|
+
fail "Anti-conformity directive missing from one or more Round 2 prompts in SKILL.md (issue #19; expected ≥3 occurrences, found ${ac_count_skill})"
|
|
85
|
+
fi
|
|
86
|
+
pass "Anti-conformity directive present in all 3 Round 2 prompts in SKILL.md"
|
|
87
|
+
|
|
88
|
+
grep -q "Anti-conformity directive" SKILL.codex.md || fail "Anti-conformity directive missing in SKILL.codex.md (issue #19)"
|
|
89
|
+
pass "Anti-conformity directive present in SKILL.codex.md"
|
|
90
|
+
|
|
91
|
+
# Chairman role (issue #18) — must be wired into STEP 1.7, STEP 7, flags, and Codex
|
|
92
|
+
grep -q "STEP 1.7" SKILL.md || fail "Chairman selection step missing in SKILL.md (issue #18)"
|
|
93
|
+
grep -q -- "--chairman" SKILL.md || fail "--chairman flag missing in SKILL.md (issue #18)"
|
|
94
|
+
grep -q "CHAIRMAN" SKILL.md || fail "Chairman synthesis step missing in SKILL.md (issue #18)"
|
|
95
|
+
pass "Chairman role wired in SKILL.md (STEP 1.7 + --chairman flag + synthesis step)"
|
|
96
|
+
|
|
97
|
+
grep -q -i "chairman" SKILL.codex.md || fail "Chairman role missing in SKILL.codex.md (issue #18)"
|
|
98
|
+
pass "Chairman role wired in SKILL.codex.md"
|
|
99
|
+
|
|
100
|
+
grep -q "chairman_defaults" configs/auto-route-defaults.yaml || fail "chairman_defaults block missing in auto-route-defaults.yaml (issue #18)"
|
|
101
|
+
pass "Chairman defaults configured in auto-route-defaults.yaml"
|
|
102
|
+
|
|
103
|
+
# Verdict actionability sections (issue #21)
|
|
104
|
+
grep -q "Acceptable Compromises" SKILL.md || fail "Acceptable Compromises section missing in SKILL.md (issue #21)"
|
|
105
|
+
grep -q "Kill Criteria" SKILL.md || fail "Kill Criteria section missing in SKILL.md (issue #21)"
|
|
106
|
+
grep -q "Concrete Next Step" SKILL.md || fail "Concrete Next Step section missing in SKILL.md (issue #21)"
|
|
107
|
+
pass "Verdict actionability sections present in SKILL.md (Acceptable Compromises / Kill Criteria / Concrete Next Step)"
|
|
108
|
+
|
|
109
|
+
grep -q "Acceptable Compromises" SKILL.codex.md || fail "Acceptable Compromises missing in SKILL.codex.md (issue #21)"
|
|
110
|
+
grep -q "Kill Criteria" SKILL.codex.md || fail "Kill Criteria missing in SKILL.codex.md (issue #21)"
|
|
111
|
+
grep -q "Concrete Next Step" SKILL.codex.md || fail "Concrete Next Step missing in SKILL.codex.md (issue #21)"
|
|
112
|
+
pass "Verdict actionability sections present in SKILL.codex.md"
|
|
113
|
+
|
|
114
|
+
# OpenAI-compatible API archetype (issue #16) — must be wired in dispatch + routing
|
|
115
|
+
grep -q "openai_compatible_api" SKILL.md || fail "openai_compatible_api archetype missing in SKILL.md (issue #16)"
|
|
116
|
+
grep -q "base_url" SKILL.md || fail "base_url handling missing in SKILL.md (issue #16)"
|
|
117
|
+
grep -q "api_key_env" SKILL.md || fail "api_key_env handling missing in SKILL.md (issue #16)"
|
|
118
|
+
pass "openai_compatible_api archetype wired in SKILL.md (dispatch + base_url + api_key_env)"
|
|
119
|
+
|
|
120
|
+
grep -q "openai_compatible_api\|openai-compatible\|OpenAI-Compatible" SKILL.codex.md || fail "openai_compatible_api archetype missing in SKILL.codex.md (issue #16)"
|
|
121
|
+
pass "openai_compatible_api archetype wired in SKILL.codex.md"
|
|
122
|
+
|
|
123
|
+
# Session Metadata schema (issue #7 Phase 1)
|
|
124
|
+
grep -q "Session Metadata" SKILL.md || fail "Session Metadata block missing in SKILL.md (issue #7)"
|
|
125
|
+
grep -q "schema_version: 1" SKILL.md || fail "schema_version: 1 marker missing in SKILL.md Session Metadata (issue #7)"
|
|
126
|
+
pass "Session Metadata schema wired in SKILL.md"
|
|
127
|
+
|
|
128
|
+
grep -q "Session Metadata\|session metadata\|session_metadata" SKILL.codex.md || fail "Session Metadata missing in SKILL.codex.md (issue #7)"
|
|
129
|
+
pass "Session Metadata referenced in SKILL.codex.md"
|
|
130
|
+
|
|
131
|
+
# --- SKILL.gemini.md parity checks ---
|
|
132
|
+
# The Gemini variant is the least-exercised of the three coordinator files and
|
|
133
|
+
# is exactly where feature drift shipped once before. Every protocol feature
|
|
134
|
+
# checked for codex above is checked for gemini here.
|
|
135
|
+
|
|
136
|
+
grep -q "anonymiz" SKILL.gemini.md || fail "Round 2 anonymization missing in SKILL.gemini.md (issue #17)"
|
|
137
|
+
pass "Round 2 anonymization wired in SKILL.gemini.md"
|
|
138
|
+
|
|
139
|
+
grep -q "Anti-conformity directive" SKILL.gemini.md || fail "Anti-conformity directive missing in SKILL.gemini.md (issue #19)"
|
|
140
|
+
pass "Anti-conformity directive present in SKILL.gemini.md"
|
|
141
|
+
|
|
142
|
+
grep -q -i "chairman" SKILL.gemini.md || fail "Chairman role missing in SKILL.gemini.md (issue #18)"
|
|
143
|
+
pass "Chairman role wired in SKILL.gemini.md"
|
|
144
|
+
|
|
145
|
+
grep -q "Acceptable Compromises" SKILL.gemini.md || fail "Acceptable Compromises missing in SKILL.gemini.md (issue #21)"
|
|
146
|
+
grep -q "Kill Criteria" SKILL.gemini.md || fail "Kill Criteria missing in SKILL.gemini.md (issue #21)"
|
|
147
|
+
grep -q "Concrete Next Step" SKILL.gemini.md || fail "Concrete Next Step missing in SKILL.gemini.md (issue #21)"
|
|
148
|
+
pass "Verdict actionability sections present in SKILL.gemini.md"
|
|
149
|
+
|
|
150
|
+
grep -q "openai_compatible_api\|openai-compatible\|OpenAI-Compatible" SKILL.gemini.md || fail "openai_compatible_api archetype missing in SKILL.gemini.md (issue #16)"
|
|
151
|
+
pass "openai_compatible_api archetype wired in SKILL.gemini.md"
|
|
152
|
+
|
|
153
|
+
grep -q "Session Metadata\|session metadata\|session_metadata" SKILL.gemini.md || fail "Session Metadata missing in SKILL.gemini.md (issue #7)"
|
|
154
|
+
pass "Session Metadata referenced in SKILL.gemini.md"
|
|
155
|
+
|
|
156
|
+
# --- Structured stance / weighted tally parity (PR #36) ---
|
|
157
|
+
# All three coordinator files must carry the STANCE line, the Vote Tally
|
|
158
|
+
# verdict field, and the 1.5x domain-weight seat. This is the check that
|
|
159
|
+
# would have caught the Gemini regression.
|
|
160
|
+
|
|
161
|
+
for skill_file in SKILL.md SKILL.codex.md SKILL.gemini.md; do
|
|
162
|
+
grep -q "STANCE:" "${skill_file}" || fail "Structured STANCE line missing in ${skill_file} (PR #36)"
|
|
163
|
+
grep -q "Vote Tally" "${skill_file}" || fail "Vote Tally verdict field missing in ${skill_file} (PR #36)"
|
|
164
|
+
grep -q "1\.5" "${skill_file}" || fail "1.5x domain-weight seat missing in ${skill_file} (PR #36)"
|
|
165
|
+
grep -q "2/3" "${skill_file}" || fail "2/3 consensus threshold missing in ${skill_file} (PR #36)"
|
|
166
|
+
done
|
|
167
|
+
pass "Structured stance + weighted tally present in all three SKILL files"
|
|
168
|
+
|
|
169
|
+
# --- Agent structure checks ---
|
|
170
|
+
|
|
171
|
+
required_sections=("Identity" "Grounding Protocol" "Analytical Method" "What You See" "What You Tend to Miss" "When Deliberating" "Output Format (Council Round 2)" "Output Format (Standalone)")
|
|
172
|
+
|
|
173
|
+
agent_structure_ok=true
|
|
174
|
+
for agent_file in agents/council-*.md; do
|
|
175
|
+
agent_name=$(basename "${agent_file}" .md)
|
|
176
|
+
for section in "${required_sections[@]}"; do
|
|
177
|
+
if ! grep -q "## ${section}" "${agent_file}" 2>/dev/null; then
|
|
178
|
+
# Some sections use slightly different headers, try partial match
|
|
179
|
+
section_word=$(echo "${section}" | awk '{print $1}')
|
|
180
|
+
if ! grep -qi "${section_word}" "${agent_file}" 2>/dev/null; then
|
|
181
|
+
warn "${agent_name}: missing section '${section}'"
|
|
182
|
+
agent_structure_ok=false
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
done
|
|
186
|
+
done
|
|
187
|
+
|
|
188
|
+
if [[ "${agent_structure_ok}" == true ]]; then
|
|
189
|
+
pass "All agents have consistent section structure"
|
|
190
|
+
else
|
|
191
|
+
warn "Some agents have inconsistent section structure (see warnings above)"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# --- Grounding protocol placement check ---
|
|
195
|
+
|
|
196
|
+
grounding_early=true
|
|
197
|
+
for agent_file in agents/council-*.md; do
|
|
198
|
+
agent_name=$(basename "${agent_file}" .md)
|
|
199
|
+
grounding_line=$(grep -n "## Grounding Protocol" "${agent_file}" 2>/dev/null | head -1 | cut -d: -f1 || echo "999")
|
|
200
|
+
method_line=$(grep -n "## Analytical Method" "${agent_file}" 2>/dev/null | head -1 | cut -d: -f1 || echo "0")
|
|
201
|
+
if [[ "${grounding_line}" -gt "${method_line}" ]] && [[ "${method_line}" -gt 0 ]]; then
|
|
202
|
+
warn "${agent_name}: Grounding Protocol appears after Analytical Method (should be before)"
|
|
203
|
+
grounding_early=false
|
|
204
|
+
fi
|
|
205
|
+
done
|
|
206
|
+
|
|
207
|
+
if [[ "${grounding_early}" == true ]]; then
|
|
208
|
+
pass "Grounding protocols placed before Analytical Method in all agents"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# --- Triad member validation ---
|
|
212
|
+
|
|
213
|
+
for member_name in aristotle socrates feynman ada sun-tzu machiavelli aurelius lao-tzu torvalds musashi watts karpathy sutskever kahneman meadows munger taleb rams; do
|
|
214
|
+
if [[ ! -f "agents/council-${member_name}.md" ]]; then
|
|
215
|
+
fail "Missing agent file for triad member: council-${member_name}.md"
|
|
216
|
+
fi
|
|
217
|
+
done
|
|
218
|
+
pass "All triad member agent files present"
|
|
219
|
+
|
|
220
|
+
# --- Verdict template dedup check ---
|
|
221
|
+
|
|
222
|
+
if grep -q "^{" demos/verdict-template.md 2>/dev/null; then
|
|
223
|
+
warn "demos/verdict-template.md still contains template placeholders (should point to SKILL.md)"
|
|
224
|
+
fi
|
|
225
|
+
pass "Verdict template dedup check done"
|
|
226
|
+
|
|
227
|
+
# --- Auto-routing checks ---
|
|
228
|
+
|
|
229
|
+
[[ -f "scripts/detect-providers.sh" ]] || fail "scripts/detect-providers.sh is missing"
|
|
230
|
+
[[ -x "scripts/detect-providers.sh" ]] || fail "scripts/detect-providers.sh is not executable"
|
|
231
|
+
pass "detect-providers.sh exists and is executable"
|
|
232
|
+
|
|
233
|
+
if detect_output="$(bash scripts/detect-providers.sh 2>/dev/null)"; then
|
|
234
|
+
if echo "$detect_output" | grep -q '"provider_count"'; then
|
|
235
|
+
pass "detect-providers.sh produces valid JSON"
|
|
236
|
+
else
|
|
237
|
+
fail "detect-providers.sh output missing provider_count field"
|
|
238
|
+
fi
|
|
239
|
+
else
|
|
240
|
+
fail "detect-providers.sh exited with error"
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
[[ -f "configs/auto-route-defaults.yaml" ]] || fail "configs/auto-route-defaults.yaml is missing"
|
|
244
|
+
pass "Auto-route defaults config exists"
|
|
245
|
+
|
|
246
|
+
grep -q -- "--no-auto-route" SKILL.md || fail "--no-auto-route flag missing in SKILL.md"
|
|
247
|
+
pass "--no-auto-route flag documented in SKILL.md"
|
|
248
|
+
|
|
249
|
+
grep -q -- "--dry-route" SKILL.md || fail "--dry-route flag missing in SKILL.md"
|
|
250
|
+
pass "--dry-route flag documented in SKILL.md"
|
|
251
|
+
|
|
252
|
+
affinity_count=0
|
|
253
|
+
for agent_file in agents/council-*.md; do
|
|
254
|
+
if grep -q "provider_affinity" "$agent_file" 2>/dev/null; then
|
|
255
|
+
((affinity_count+=1))
|
|
256
|
+
fi
|
|
257
|
+
done
|
|
258
|
+
if [[ "$affinity_count" -eq "$agent_count" ]]; then
|
|
259
|
+
pass "All agents have provider_affinity in frontmatter"
|
|
260
|
+
else
|
|
261
|
+
warn "Only ${affinity_count}/${agent_count} agents have provider_affinity"
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
reasoning_method_count=0
|
|
265
|
+
for agent_file in agents/council-*.md; do
|
|
266
|
+
if grep -q "reasoning_method" "$agent_file" 2>/dev/null; then
|
|
267
|
+
((reasoning_method_count+=1))
|
|
268
|
+
fi
|
|
269
|
+
done
|
|
270
|
+
if [[ "$reasoning_method_count" -eq "$agent_count" ]]; then
|
|
271
|
+
pass "All agents have reasoning_method in frontmatter"
|
|
272
|
+
else
|
|
273
|
+
warn "Only ${reasoning_method_count}/${agent_count} agents have reasoning_method"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# --- Install script checks ---
|
|
277
|
+
|
|
278
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
279
|
+
shellcheck install.sh
|
|
280
|
+
pass "shellcheck passed for install.sh"
|
|
281
|
+
else
|
|
282
|
+
warn "shellcheck not installed; skipped"
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
TMP_LOG_DIR="$(mktemp -d)"
|
|
286
|
+
trap 'rm -rf "${TMP_LOG_DIR}"' EXIT
|
|
287
|
+
|
|
288
|
+
./install.sh --dry-run >"${TMP_LOG_DIR}/dry-run.log"
|
|
289
|
+
pass "install.sh --dry-run completed"
|
|
290
|
+
|
|
291
|
+
grep -q "Installed .* council agents" "${TMP_LOG_DIR}/dry-run.log" || fail "install dry-run output missing agent install summary"
|
|
292
|
+
pass "install summary output present"
|
|
293
|
+
|
|
294
|
+
./install.sh --dry-run --copy-configs >"${TMP_LOG_DIR}/dry-run-configs.log"
|
|
295
|
+
pass "install.sh --dry-run --copy-configs completed"
|
|
296
|
+
|
|
297
|
+
grep -q "Installed .* config files" "${TMP_LOG_DIR}/dry-run-configs.log" || fail "copy-configs dry-run output missing config install summary"
|
|
298
|
+
pass "config summary output present"
|
|
299
|
+
|
|
300
|
+
./install.sh --dry-run --codex >"${TMP_LOG_DIR}/dry-run-codex.log"
|
|
301
|
+
pass "install.sh --dry-run --codex completed"
|
|
302
|
+
|
|
303
|
+
grep -q "Installed Codex skill to" "${TMP_LOG_DIR}/dry-run-codex.log" || fail "codex dry-run output missing Codex skill summary"
|
|
304
|
+
pass "Codex install summary output present"
|
|
305
|
+
|
|
306
|
+
./install.sh --dry-run --gemini >"${TMP_LOG_DIR}/dry-run-gemini.log"
|
|
307
|
+
pass "install.sh --dry-run --gemini completed"
|
|
308
|
+
|
|
309
|
+
grep -q "gemini-extension.json" "${TMP_LOG_DIR}/dry-run-gemini.log" || fail "gemini dry-run output missing gemini-extension.json manifest step"
|
|
310
|
+
pass "Gemini install manifest step present"
|
|
311
|
+
|
|
312
|
+
echo
|
|
313
|
+
echo "Checklist complete."
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Council of Cognitive Excellence — Provider Detection Script
|
|
5
|
+
# Detects available LLM providers and outputs structured JSON to stdout.
|
|
6
|
+
# Usage: ./scripts/detect-providers.sh
|
|
7
|
+
|
|
8
|
+
TIMEOUT_SECONDS=5
|
|
9
|
+
|
|
10
|
+
# --- Helper functions ---
|
|
11
|
+
|
|
12
|
+
json_provider() {
|
|
13
|
+
local name="$1" available="$2" exec_method="$3" binary="$4" models="$5"
|
|
14
|
+
printf '{"name":"%s","available":%s,"exec_method":"%s","binary":"%s","models":[%s]}' \
|
|
15
|
+
"$name" "$available" "$exec_method" "$binary" "$models"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
check_command() {
|
|
19
|
+
command -v "$1" 2>/dev/null || echo ""
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# macOS-compatible timeout using perl (no coreutils required)
|
|
23
|
+
run_with_timeout() {
|
|
24
|
+
perl -e '
|
|
25
|
+
use POSIX ":sys_wait_h";
|
|
26
|
+
my $timeout = shift @ARGV;
|
|
27
|
+
my $pid = fork();
|
|
28
|
+
if ($pid == 0) { exec @ARGV; exit 1; }
|
|
29
|
+
my $elapsed = 0;
|
|
30
|
+
while ($elapsed < $timeout) {
|
|
31
|
+
if (waitpid($pid, WNOHANG) > 0) { exit ($? >> 8); }
|
|
32
|
+
select(undef, undef, undef, 0.1);
|
|
33
|
+
$elapsed += 0.1;
|
|
34
|
+
}
|
|
35
|
+
kill("TERM", $pid);
|
|
36
|
+
waitpid($pid, 0);
|
|
37
|
+
exit 124;
|
|
38
|
+
' "$TIMEOUT_SECONDS" "$@" 2>/dev/null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# --- Detect each provider ---
|
|
42
|
+
|
|
43
|
+
providers=()
|
|
44
|
+
|
|
45
|
+
# Anthropic — always available (host runtime)
|
|
46
|
+
providers+=("$(json_provider "anthropic" "true" "subagent" "native" '"opus","sonnet","haiku"')")
|
|
47
|
+
|
|
48
|
+
# OpenAI via Codex CLI
|
|
49
|
+
codex_bin="$(check_command codex)"
|
|
50
|
+
if [[ -n "$codex_bin" ]] && run_with_timeout codex --version >/dev/null 2>&1; then
|
|
51
|
+
providers+=("$(json_provider "openai" "true" "codex_exec" "$codex_bin" '"gpt-5.4"')")
|
|
52
|
+
else
|
|
53
|
+
providers+=("$(json_provider "openai" "false" "codex_exec" "${codex_bin:-not_found}" '')")
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Google via Gemini CLI
|
|
57
|
+
gemini_bin="$(check_command gemini)"
|
|
58
|
+
if [[ -n "$gemini_bin" ]] && run_with_timeout gemini --version >/dev/null 2>&1; then
|
|
59
|
+
providers+=("$(json_provider "google" "true" "gemini_cli" "$gemini_bin" '"gemini-3-pro"')")
|
|
60
|
+
else
|
|
61
|
+
providers+=("$(json_provider "google" "false" "gemini_cli" "${gemini_bin:-not_found}" '')")
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Ollama (local models)
|
|
65
|
+
ollama_bin="$(check_command ollama)"
|
|
66
|
+
ollama_available=false
|
|
67
|
+
ollama_models=""
|
|
68
|
+
if [[ -n "$ollama_bin" ]]; then
|
|
69
|
+
# Check if ollama server is running by listing models
|
|
70
|
+
if model_list="$(run_with_timeout ollama list 2>/dev/null)"; then
|
|
71
|
+
ollama_available=true
|
|
72
|
+
# Parse model names from 'ollama list' output (skip header line, take first column)
|
|
73
|
+
ollama_models="$(echo "$model_list" | tail -n +2 | awk '{print $1}' | head -5 | \
|
|
74
|
+
sed 's/.*/"&"/' | paste -sd ',' -)"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
providers+=("$(json_provider "ollama" "$ollama_available" "ollama_run" "${ollama_bin:-not_found}" "$ollama_models")")
|
|
78
|
+
|
|
79
|
+
# Cursor via Cursor CLI (cursor-agent) — model aggregator: serves GPT-5.x,
|
|
80
|
+
# Claude, Gemini, and Grok families through one binary + CURSOR_API_KEY.
|
|
81
|
+
cursor_bin="$(check_command cursor-agent)"
|
|
82
|
+
if [[ -n "$cursor_bin" ]] && run_with_timeout cursor-agent --version >/dev/null 2>&1; then
|
|
83
|
+
# Cross-family defaults so a Cursor seat adds real diversity, not a
|
|
84
|
+
# second Anthropic-biased model. Verify live IDs with `cursor-agent --list-models`.
|
|
85
|
+
providers+=("$(json_provider "cursor_cli" "true" "cursor_cli" "$cursor_bin" '"gpt-5.4-high","claude-opus-4-7-thinking-high","gemini-3-pro","grok-4"')")
|
|
86
|
+
else
|
|
87
|
+
providers+=("$(json_provider "cursor_cli" "false" "cursor_cli" "${cursor_bin:-not_found}" '')")
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# NVIDIA NIM (OpenAI-compatible hosted endpoint at build.nvidia.com)
|
|
91
|
+
# Detection: NVIDIA_API_KEY env var + optional reachability check.
|
|
92
|
+
nim_available=false
|
|
93
|
+
nim_models=""
|
|
94
|
+
nim_endpoint="https://integrate.api.nvidia.com/v1"
|
|
95
|
+
if [[ -n "${NVIDIA_API_KEY:-}" ]]; then
|
|
96
|
+
# Verify the key is not a placeholder (real keys start with nvapi-)
|
|
97
|
+
if [[ "${NVIDIA_API_KEY}" =~ ^nvapi- ]]; then
|
|
98
|
+
# Optional: confirm catalog reachability. Skip if curl missing or offline.
|
|
99
|
+
# The auth header is passed via process substitution so the key never
|
|
100
|
+
# appears in curl's argv (visible to other local users via `ps`).
|
|
101
|
+
if command -v curl >/dev/null 2>&1; then
|
|
102
|
+
if run_with_timeout curl -sf -o /dev/null \
|
|
103
|
+
-H @<(printf 'Authorization: Bearer %s\n' "${NVIDIA_API_KEY}") \
|
|
104
|
+
"${nim_endpoint}/models" 2>/dev/null; then
|
|
105
|
+
nim_available=true
|
|
106
|
+
fi
|
|
107
|
+
else
|
|
108
|
+
# Curl unavailable; trust env var presence + key prefix as availability signal.
|
|
109
|
+
nim_available=true
|
|
110
|
+
fi
|
|
111
|
+
# Default model suggestions (verify live IDs at build.nvidia.com/models).
|
|
112
|
+
nim_models='"deepseek-ai/deepseek-v4-pro","moonshotai/kimi-k2.6","minimaxai/minimax-m2.7","z-ai/glm-5.1","qwen/qwen3.5-397b-a17b"'
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
providers+=("$(json_provider "nvidia_nim" "$nim_available" "openai_compatible_api" "$nim_endpoint" "$nim_models")")
|
|
116
|
+
|
|
117
|
+
# --- Build JSON output ---
|
|
118
|
+
|
|
119
|
+
available_count=0
|
|
120
|
+
for p in "${providers[@]}"; do
|
|
121
|
+
if echo "$p" | grep -q '"available":true'; then
|
|
122
|
+
((available_count+=1))
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
|
|
126
|
+
multi_provider=false
|
|
127
|
+
if [[ "$available_count" -ge 2 ]]; then
|
|
128
|
+
multi_provider=true
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Assemble providers array
|
|
132
|
+
provider_json=""
|
|
133
|
+
for i in "${!providers[@]}"; do
|
|
134
|
+
if [[ $i -gt 0 ]]; then
|
|
135
|
+
provider_json+=","
|
|
136
|
+
fi
|
|
137
|
+
provider_json+="${providers[$i]}"
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
printf '{"providers":[%s],"provider_count":%d,"multi_provider":%s}\n' \
|
|
141
|
+
"$provider_json" "$available_count" "$multi_provider"
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate self-hosted GitHub star-history charts (light + dark SVG).
|
|
3
|
+
|
|
4
|
+
Replaces the api.star-history.com embed, whose backend lowercases repo
|
|
5
|
+
owners and serves an empty chart for this repo. Fetches stargazer
|
|
6
|
+
timestamps via `gh api` (works locally when authenticated and in CI via
|
|
7
|
+
GH_TOKEN) and renders two dependency-free SVGs tuned for GitHub's light
|
|
8
|
+
and dark README surfaces, selected with a <picture> element.
|
|
9
|
+
|
|
10
|
+
Usage: python3 scripts/gen-star-history.py <owner/repo> <out_prefix>
|
|
11
|
+
Writes <out_prefix>-light.svg and <out_prefix>-dark.svg
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
|
|
19
|
+
W, H = 800, 400
|
|
20
|
+
MARGIN = {"l": 56, "r": 24, "t": 52, "b": 40}
|
|
21
|
+
|
|
22
|
+
THEMES = {
|
|
23
|
+
"light": {
|
|
24
|
+
"line": "#2563eb", "area_opacity": 0.10,
|
|
25
|
+
"ink": "#1f2328", "muted": "#59636e", "grid": "#d1d9e0",
|
|
26
|
+
},
|
|
27
|
+
"dark": {
|
|
28
|
+
"line": "#4493f8", "area_opacity": 0.14,
|
|
29
|
+
"ink": "#e6edf3", "muted": "#8b949e", "grid": "#30363d",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
34
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def fetch_star_dates(repo: str) -> list[datetime]:
|
|
38
|
+
out = subprocess.run(
|
|
39
|
+
["gh", "api", f"repos/{repo}/stargazers", "--paginate",
|
|
40
|
+
"-H", "Accept: application/vnd.github.star+json",
|
|
41
|
+
"-q", ".[].starred_at"],
|
|
42
|
+
capture_output=True, text=True, check=True,
|
|
43
|
+
).stdout.split()
|
|
44
|
+
dates = sorted(datetime.fromisoformat(d.replace("Z", "+00:00")) for d in out)
|
|
45
|
+
if not dates:
|
|
46
|
+
raise SystemExit(f"no stargazer data returned for {repo}")
|
|
47
|
+
return dates
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_series(dates: list[datetime]) -> list[tuple[datetime, int]]:
|
|
51
|
+
# Cumulative count per day, then evenly sampled to <= 160 points.
|
|
52
|
+
daily: dict[str, int] = {}
|
|
53
|
+
for i, d in enumerate(dates, start=1):
|
|
54
|
+
daily[d.strftime("%Y-%m-%d")] = i
|
|
55
|
+
pts = [(datetime.fromisoformat(k).replace(tzinfo=timezone.utc), v)
|
|
56
|
+
for k, v in sorted(daily.items())]
|
|
57
|
+
if len(pts) > 160:
|
|
58
|
+
step = (len(pts) - 1) / 159
|
|
59
|
+
pts = [pts[round(i * step)] for i in range(160)]
|
|
60
|
+
return pts
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def nice_step(max_v: int) -> int:
|
|
64
|
+
for step in (50, 100, 250, 500, 1000, 2000, 5000, 10000):
|
|
65
|
+
if max_v / step <= 6:
|
|
66
|
+
return step
|
|
67
|
+
return 20000
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def fmt_k(v: int) -> str:
|
|
71
|
+
if v >= 1000:
|
|
72
|
+
return f"{v/1000:g}K"
|
|
73
|
+
return str(v)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def render(repo: str, pts: list[tuple[datetime, int]], theme: dict) -> str:
|
|
77
|
+
x0, x1 = pts[0][0].timestamp(), pts[-1][0].timestamp()
|
|
78
|
+
y_max_raw = pts[-1][1]
|
|
79
|
+
step = nice_step(y_max_raw)
|
|
80
|
+
y_max = ((y_max_raw // step) + 1) * step
|
|
81
|
+
plot_w = W - MARGIN["l"] - MARGIN["r"]
|
|
82
|
+
plot_h = H - MARGIN["t"] - MARGIN["b"]
|
|
83
|
+
|
|
84
|
+
def sx(ts: float) -> float:
|
|
85
|
+
return MARGIN["l"] + (ts - x0) / max(x1 - x0, 1) * plot_w
|
|
86
|
+
|
|
87
|
+
def sy(v: float) -> float:
|
|
88
|
+
return MARGIN["t"] + plot_h - v / y_max * plot_h
|
|
89
|
+
|
|
90
|
+
line_d = " ".join(
|
|
91
|
+
f"{'M' if i == 0 else 'L'}{sx(d.timestamp()):.1f},{sy(v):.1f}"
|
|
92
|
+
for i, (d, v) in enumerate(pts)
|
|
93
|
+
)
|
|
94
|
+
base_y = sy(0)
|
|
95
|
+
area_d = f"{line_d} L{sx(x1):.1f},{base_y:.1f} L{sx(x0):.1f},{base_y:.1f} Z"
|
|
96
|
+
|
|
97
|
+
grid, ylabels = [], []
|
|
98
|
+
for v in range(step, y_max + 1, step):
|
|
99
|
+
y = sy(v)
|
|
100
|
+
grid.append(f'<line x1="{MARGIN["l"]}" y1="{y:.1f}" x2="{W - MARGIN["r"]}" y2="{y:.1f}" '
|
|
101
|
+
f'stroke="{theme["grid"]}" stroke-width="1"/>')
|
|
102
|
+
ylabels.append(f'<text x="{MARGIN["l"] - 8}" y="{y + 4:.1f}" text-anchor="end" '
|
|
103
|
+
f'fill="{theme["muted"]}" font-size="12">{fmt_k(v)}</text>')
|
|
104
|
+
|
|
105
|
+
xlabels = []
|
|
106
|
+
seen = set()
|
|
107
|
+
for d, _ in pts:
|
|
108
|
+
key = (d.year, d.month)
|
|
109
|
+
if key in seen:
|
|
110
|
+
continue
|
|
111
|
+
seen.add(key)
|
|
112
|
+
ts = datetime(d.year, d.month, 1, tzinfo=timezone.utc).timestamp()
|
|
113
|
+
if ts < x0:
|
|
114
|
+
ts = x0
|
|
115
|
+
label = MONTHS[d.month - 1] + (f" '{str(d.year)[2:]}" if d.month == 1 or len(seen) == 1 else "")
|
|
116
|
+
xlabels.append(f'<text x="{sx(ts):.1f}" y="{H - MARGIN["b"] + 20}" text-anchor="middle" '
|
|
117
|
+
f'fill="{theme["muted"]}" font-size="12">{label}</text>')
|
|
118
|
+
|
|
119
|
+
end_x, end_y = sx(x1), sy(y_max_raw)
|
|
120
|
+
label_anchor = "end"
|
|
121
|
+
label_x = end_x - 10
|
|
122
|
+
updated = pts[-1][0].strftime("%b %-d, %Y")
|
|
123
|
+
|
|
124
|
+
font = "font-family=\"-apple-system,'Segoe UI',Helvetica,Arial,sans-serif\""
|
|
125
|
+
return f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}" role="img" aria-label="Cumulative GitHub stars for {repo}: {y_max_raw:,} as of {updated}">
|
|
126
|
+
<g {font}>
|
|
127
|
+
<text x="{MARGIN["l"]}" y="24" fill="{theme["ink"]}" font-size="15" font-weight="600">{repo} — GitHub stars</text>
|
|
128
|
+
<text x="{W - MARGIN["r"] - 8}" y="24" text-anchor="end" fill="{theme["muted"]}" font-size="11">updated {updated}</text>
|
|
129
|
+
{''.join(grid)}
|
|
130
|
+
<line x1="{MARGIN["l"]}" y1="{base_y:.1f}" x2="{W - MARGIN["r"]}" y2="{base_y:.1f}" stroke="{theme["grid"]}" stroke-width="1"/>
|
|
131
|
+
{''.join(ylabels)}
|
|
132
|
+
{''.join(xlabels)}
|
|
133
|
+
<path d="{area_d}" fill="{theme["line"]}" fill-opacity="{theme["area_opacity"]}" stroke="none"/>
|
|
134
|
+
<path d="{line_d}" fill="none" stroke="{theme["line"]}" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
|
135
|
+
<circle cx="{end_x:.1f}" cy="{end_y:.1f}" r="4" fill="{theme["line"]}"/>
|
|
136
|
+
<text x="{label_x:.1f}" y="{end_y - 10:.1f}" text-anchor="{label_anchor}" fill="{theme["ink"]}" font-size="13" font-weight="600">{y_max_raw:,} ★</text>
|
|
137
|
+
</g>
|
|
138
|
+
</svg>
|
|
139
|
+
'''
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main() -> None:
|
|
143
|
+
if len(sys.argv) != 3:
|
|
144
|
+
raise SystemExit(__doc__)
|
|
145
|
+
repo, prefix = sys.argv[1], sys.argv[2]
|
|
146
|
+
pts = build_series(fetch_star_dates(repo))
|
|
147
|
+
for mode, theme in THEMES.items():
|
|
148
|
+
path = f"{prefix}-{mode}.svg"
|
|
149
|
+
with open(path, "w") as f:
|
|
150
|
+
f.write(render(repo, pts, theme))
|
|
151
|
+
print(f"wrote {path} ({pts[-1][1]} stars, {len(pts)} points)")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
main()
|