@firatcand/roster 0.1.0 → 1.0.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/README.md +77 -215
- package/agents/lesson-drafter.md +3 -8
- package/agents/pattern-detector.md +0 -1
- package/bin/roster.js +7233 -1197
- package/data/plan-ceilings.yaml +57 -0
- package/package.json +8 -3
- package/skills/chief-of-staff/SKILL.md +199 -59
- package/skills/dreamer/SKILL.md +8 -7
- package/skills/roster-orchestrator/SKILL.md +53 -25
- package/templates/CLAUDE.project.template.md +1 -1
- package/templates/CONTEXT.template.md +2 -2
- package/templates/gitignore-defaults.txt +2 -0
- package/templates/hooks/banner.sh +47 -0
- package/templates/scaffold/chief-of-staff/README.md +16 -24
- package/templates/scaffold/chief-of-staff/agent.md +22 -32
- package/templates/scaffold/chief-of-staff/plans/audit-agent.yaml +4 -4
- package/templates/scaffold/chief-of-staff/plans/audit-repo.yaml +5 -4
- package/templates/scaffold/chief-of-staff/plans/create-agent.yaml +5 -34
- package/templates/scaffold/config/project.yaml.template +10 -0
- package/templates/scaffold/conventions.md +188 -173
- package/templates/scaffold/dreamer/README.md +2 -2
- package/templates/scaffold/dreamer/agent.md +0 -1
- package/templates/scaffold/dreamer/plans/nightly-reflection.yaml +23 -37
- package/templates/scaffold/dreamer/subagents/lesson-drafter.md +2 -7
- package/templates/scaffold/{projects/_demo/guidelines → guidelines}/asset-links.md +4 -0
- package/templates/scaffold/{projects/_demo/guidelines → guidelines}/brand-book.md +4 -0
- package/templates/scaffold/{projects/_demo/guidelines → guidelines}/messaging.md +4 -0
- package/templates/scaffold/{projects/_demo/guidelines → guidelines}/voice.md +4 -0
- package/templates/scaffold/logs/cron/.gitkeep +1 -0
- package/templates/scaffold/ops/EXPERT.md +5 -5
- package/templates/scaffold/scripts/audit-agent.sh +326 -0
- package/templates/scaffold/scripts/audit-repo.sh +218 -0
- package/templates/scaffold/scripts/create-function.sh +267 -0
- package/templates/scaffold/scripts/lib/README.md +6 -1
- package/templates/scaffold/scripts/lib/bindings-prompt.sh +53 -0
- package/templates/scaffold/scripts/lib/functions.sh +17 -5
- package/templates/scaffold/scripts/new-agent.sh +416 -0
- package/templates/scaffold/scripts/rename-agent.sh +91 -0
- package/templates/scaffold/scripts/save-state.sh +32 -0
- package/agents/critic.md +0 -74
- package/agents/enricher.md +0 -56
- package/agents/promotion-arbiter.md +0 -71
- package/agents/prospector.md +0 -51
- package/agents/writer.md +0 -58
- package/skills/sdr/SKILL.md +0 -147
- package/templates/scaffold/chief-of-staff/plans/add-agent-to-project.yaml +0 -45
- package/templates/scaffold/chief-of-staff/plans/archive-project.yaml +0 -51
- package/templates/scaffold/chief-of-staff/plans/audit-project.yaml +0 -34
- package/templates/scaffold/chief-of-staff/plans/create-project.yaml +0 -65
- package/templates/scaffold/chief-of-staff/plans/remove-agent-from-project.yaml +0 -50
- package/templates/scaffold/chief-of-staff/plans/rename-project.yaml +0 -62
- package/templates/scaffold/chief-of-staff/plans/unarchive-project.yaml +0 -41
- package/templates/scaffold/dreamer/subagents/promotion-arbiter.md +0 -64
- package/templates/scaffold/gtm/sdr/.claude/settings.json +0 -3
- package/templates/scaffold/gtm/sdr/.mcp.json +0 -21
- package/templates/scaffold/gtm/sdr/README.md +0 -46
- package/templates/scaffold/gtm/sdr/agent.md +0 -136
- package/templates/scaffold/gtm/sdr/plans/cold-outreach.yaml +0 -92
- package/templates/scaffold/gtm/sdr/projects/_demo/asset-references.md +0 -7
- package/templates/scaffold/gtm/sdr/projects/_demo/config/default.yaml +0 -69
- package/templates/scaffold/gtm/sdr/projects/_demo/log/feedback/.gitkeep +0 -0
- package/templates/scaffold/gtm/sdr/projects/_demo/log/runs/.gitkeep +0 -0
- package/templates/scaffold/gtm/sdr/projects/_demo/playbook/.gitkeep +0 -0
- package/templates/scaffold/gtm/sdr/subagents/critic.md +0 -67
- package/templates/scaffold/gtm/sdr/subagents/enricher.md +0 -49
- package/templates/scaffold/gtm/sdr/subagents/prospector.md +0 -44
- package/templates/scaffold/gtm/sdr/subagents/writer.md +0 -51
- package/templates/scaffold/projects/_demo/CLAUDE.md +0 -35
- package/templates/scaffold/projects/_demo/README.md +0 -16
- package/templates/scaffold/projects/_demo/assets/.gitkeep +0 -0
- package/templates/scaffold/projects/_demo/config/default.yaml +0 -28
- package/templates/scaffold/projects/_demo/state.md +0 -11
- package/templates/scaffold/scripts/new-project.sh +0 -125
- /package/templates/scaffold/gtm/{sdr/playbook/.gitkeep → .gitkeep} +0 -0
- /package/templates/scaffold/{projects/_demo/guidelines → guidelines}/icps/_persona-template.md +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# audit-repo.sh — full workspace audit; runs agent audits and repo-level checks
|
|
3
|
+
# Usage: bash scripts/audit-repo.sh
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
|
|
9
|
+
source "$ROOT/scripts/lib/functions.sh"
|
|
10
|
+
|
|
11
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
12
|
+
RUN_TIME=$(date +%Y-%m-%d-%H%M)
|
|
13
|
+
LOG_DIR="$ROOT/chief-of-staff/logs/$(date +%Y-%m)"
|
|
14
|
+
mkdir -p "$LOG_DIR"
|
|
15
|
+
REPORT="$LOG_DIR/audit-repo-$RUN_TIME.md"
|
|
16
|
+
|
|
17
|
+
REPO_FAILURES=()
|
|
18
|
+
REPO_WARNINGS=()
|
|
19
|
+
REPO_PASSED=()
|
|
20
|
+
|
|
21
|
+
# === Repo-level checks ===
|
|
22
|
+
|
|
23
|
+
# Universal .mcp.json
|
|
24
|
+
if [ ! -f "$ROOT/.mcp.json" ]; then
|
|
25
|
+
REPO_WARNINGS+=("[.mcp.json] universal MCP config missing")
|
|
26
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
27
|
+
if ! python3 -c "import json; json.load(open('$ROOT/.mcp.json'))" 2>/dev/null; then
|
|
28
|
+
REPO_FAILURES+=("[.mcp.json] invalid JSON")
|
|
29
|
+
else
|
|
30
|
+
REPO_PASSED+=("[.mcp.json] valid")
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Universal .claude/settings.json
|
|
35
|
+
if [ ! -f "$ROOT/.claude/settings.json" ]; then
|
|
36
|
+
REPO_WARNINGS+=("[.claude/settings.json] universal settings missing")
|
|
37
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
38
|
+
if ! python3 -c "import json; json.load(open('$ROOT/.claude/settings.json'))" 2>/dev/null; then
|
|
39
|
+
REPO_FAILURES+=("[.claude/settings.json] invalid JSON")
|
|
40
|
+
else
|
|
41
|
+
REPO_PASSED+=("[.claude/settings.json] valid")
|
|
42
|
+
fi
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Required root files
|
|
46
|
+
for f in CLAUDE.md conventions.md README.md; do
|
|
47
|
+
if [ ! -f "$ROOT/$f" ]; then
|
|
48
|
+
REPO_FAILURES+=("[$f] missing at repo root")
|
|
49
|
+
else
|
|
50
|
+
REPO_PASSED+=("[$f] present")
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
# config/project.yaml — workspace identity (v1 shape)
|
|
55
|
+
if [ ! -f "$ROOT/config/project.yaml" ]; then
|
|
56
|
+
REPO_FAILURES+=("[config/project.yaml] missing — workspace identity required")
|
|
57
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
58
|
+
if ! python3 -c "import yaml; yaml.safe_load(open('$ROOT/config/project.yaml'))" 2>/dev/null; then
|
|
59
|
+
REPO_FAILURES+=("[config/project.yaml] YAML parse error")
|
|
60
|
+
else
|
|
61
|
+
REPO_PASSED+=("[config/project.yaml] valid")
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# guidelines/ — cross-agent substrate
|
|
66
|
+
if [ ! -d "$ROOT/guidelines" ]; then
|
|
67
|
+
REPO_FAILURES+=("[guidelines/] missing — workspace substrate dir required")
|
|
68
|
+
else
|
|
69
|
+
REPO_PASSED+=("[guidelines/] present")
|
|
70
|
+
for f in voice.md messaging.md brand-book.md asset-links.md; do
|
|
71
|
+
if [ ! -f "$ROOT/guidelines/$f" ]; then
|
|
72
|
+
REPO_WARNINGS+=("[guidelines/$f] missing — required substrate file")
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
if [ ! -d "$ROOT/guidelines/icps" ] || [ -z "$(find "$ROOT/guidelines/icps" -maxdepth 1 -name '*.md' -type f 2>/dev/null | head -1)" ]; then
|
|
76
|
+
REPO_WARNINGS+=("[guidelines/icps/] empty or missing — at least one persona file required")
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Build a regex of registered functions for matching grandparent dirs.
|
|
81
|
+
REGISTERED_FNS_PIPE=$(read_functions 2>/dev/null | tr '\n' '|' | sed 's/|$//')
|
|
82
|
+
|
|
83
|
+
# Registered function should have a folder
|
|
84
|
+
while IFS= read -r fn; do
|
|
85
|
+
[ -z "$fn" ] && continue
|
|
86
|
+
if [ ! -d "$ROOT/$fn" ]; then
|
|
87
|
+
REPO_FAILURES+=("[$fn] registered in .config/functions.yaml but folder does not exist")
|
|
88
|
+
REPO_FAILURES+=(" → Suggested fix: mkdir $fn && cp <stub README>, OR remove from .config/functions.yaml")
|
|
89
|
+
fi
|
|
90
|
+
done < <(read_functions 2>/dev/null || true)
|
|
91
|
+
|
|
92
|
+
# has_expert: true → EXPERT.md must exist
|
|
93
|
+
while IFS=$'\t' read -r slug has_expert; do
|
|
94
|
+
[ -z "$slug" ] && continue
|
|
95
|
+
if [ "$has_expert" = "true" ] && [ ! -f "$ROOT/$slug/EXPERT.md" ]; then
|
|
96
|
+
REPO_WARNINGS+=("[$slug/EXPERT.md] registry says has_expert=true but file missing")
|
|
97
|
+
fi
|
|
98
|
+
done < <(read_functions_with_metadata 2>/dev/null || true)
|
|
99
|
+
|
|
100
|
+
# HITL channel env vars: every function should have SLACK_HITL_CHANNEL_<FN> in .env.example
|
|
101
|
+
ENV_EXAMPLE="$ROOT/.env.example"
|
|
102
|
+
if [ -f "$ENV_EXAMPLE" ]; then
|
|
103
|
+
while IFS= read -r fn; do
|
|
104
|
+
[ -z "$fn" ] && continue
|
|
105
|
+
var="SLACK_HITL_CHANNEL_$(echo "$fn" | tr '[:lower:]-' '[:upper:]_')"
|
|
106
|
+
if ! grep -q "^${var}=" "$ENV_EXAMPLE"; then
|
|
107
|
+
REPO_WARNINGS+=("[.env.example] missing $var (function '$fn' has no HITL channel env var)")
|
|
108
|
+
fi
|
|
109
|
+
done < <(read_functions 2>/dev/null || true)
|
|
110
|
+
if ! grep -q "^SLACK_HITL_CHANNEL_ADMIN=" "$ENV_EXAMPLE"; then
|
|
111
|
+
REPO_WARNINGS+=("[.env.example] missing SLACK_HITL_CHANNEL_ADMIN (used by dreamer + chief-of-staff)")
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Function-shaped top-level dirs not in the registry
|
|
116
|
+
KNOWN_NON_FUNCTIONS="dreamer chief-of-staff scripts logs _archive"
|
|
117
|
+
for dir in "$ROOT"/*/; do
|
|
118
|
+
basename=$(basename "$dir")
|
|
119
|
+
[[ "$basename" == .* ]] && continue
|
|
120
|
+
echo "$KNOWN_NON_FUNCTIONS" | grep -qw "$basename" && continue
|
|
121
|
+
if [ -n "$REGISTERED_FNS_PIPE" ] && echo "$basename" | grep -qE "^($REGISTERED_FNS_PIPE)\$"; then
|
|
122
|
+
continue
|
|
123
|
+
fi
|
|
124
|
+
if find "$dir" -maxdepth 2 -name 'agent.md' -type f 2>/dev/null | head -1 | grep -q .; then
|
|
125
|
+
REPO_WARNINGS+=("[$basename/] looks function-shaped but not registered in .config/functions.yaml")
|
|
126
|
+
REPO_WARNINGS+=(" → Suggested fix: add to registry via 'bash scripts/create-function.sh $basename' (or remove if intended)")
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
# === Run audit-agent for each agent ===
|
|
131
|
+
AGENTS=()
|
|
132
|
+
while IFS= read -r fn; do
|
|
133
|
+
[ -z "$fn" ] && continue
|
|
134
|
+
if [ -d "$ROOT/$fn" ]; then
|
|
135
|
+
while IFS= read -r path; do
|
|
136
|
+
AGENTS+=("$fn:$(basename "$path")")
|
|
137
|
+
done < <(find "$ROOT/$fn" -maxdepth 1 -mindepth 1 -type d 2>/dev/null || true)
|
|
138
|
+
fi
|
|
139
|
+
done < <(read_functions 2>/dev/null || true)
|
|
140
|
+
|
|
141
|
+
AGENT_RESULTS=()
|
|
142
|
+
for entry in "${AGENTS[@]:-}"; do
|
|
143
|
+
FN="${entry%%:*}"
|
|
144
|
+
AGENT="${entry##*:}"
|
|
145
|
+
RESULT=$(bash "$ROOT/scripts/audit-agent.sh" "$FN" "$AGENT" 2>&1 | head -5 || echo " (audit failed)")
|
|
146
|
+
AGENT_RESULTS+=("### $FN/$AGENT")
|
|
147
|
+
AGENT_RESULTS+=("\`\`\`")
|
|
148
|
+
while IFS= read -r line; do
|
|
149
|
+
AGENT_RESULTS+=("$line")
|
|
150
|
+
done <<< "$RESULT"
|
|
151
|
+
AGENT_RESULTS+=("\`\`\`")
|
|
152
|
+
AGENT_RESULTS+=("")
|
|
153
|
+
done
|
|
154
|
+
|
|
155
|
+
# === Status ===
|
|
156
|
+
if [ ${#REPO_FAILURES[@]} -gt 0 ]; then
|
|
157
|
+
STATUS="fail"
|
|
158
|
+
elif [ ${#REPO_WARNINGS[@]} -gt 0 ]; then
|
|
159
|
+
STATUS="warn"
|
|
160
|
+
else
|
|
161
|
+
STATUS="pass"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Write report
|
|
165
|
+
{
|
|
166
|
+
echo "---"
|
|
167
|
+
echo "operation: audit-repo"
|
|
168
|
+
echo "ran: $TIMESTAMP"
|
|
169
|
+
echo "status: $STATUS"
|
|
170
|
+
echo "---"
|
|
171
|
+
echo ""
|
|
172
|
+
echo "# Repo Audit"
|
|
173
|
+
echo ""
|
|
174
|
+
echo "## Summary"
|
|
175
|
+
echo "- Agents audited: ${#AGENTS[@]}"
|
|
176
|
+
echo "- Repo-level passed: ${#REPO_PASSED[@]}"
|
|
177
|
+
echo "- Repo-level warnings: ${#REPO_WARNINGS[@]}"
|
|
178
|
+
echo "- Repo-level failures: ${#REPO_FAILURES[@]}"
|
|
179
|
+
echo ""
|
|
180
|
+
if [ ${#REPO_FAILURES[@]} -gt 0 ]; then
|
|
181
|
+
echo "## Repo-level Failures"
|
|
182
|
+
for line in "${REPO_FAILURES[@]}"; do
|
|
183
|
+
echo "- $line"
|
|
184
|
+
done
|
|
185
|
+
echo ""
|
|
186
|
+
fi
|
|
187
|
+
if [ ${#REPO_WARNINGS[@]} -gt 0 ]; then
|
|
188
|
+
echo "## Repo-level Warnings"
|
|
189
|
+
for line in "${REPO_WARNINGS[@]}"; do
|
|
190
|
+
echo "- $line"
|
|
191
|
+
done
|
|
192
|
+
echo ""
|
|
193
|
+
fi
|
|
194
|
+
if [ ${#REPO_PASSED[@]} -gt 0 ]; then
|
|
195
|
+
echo "## Repo-level Passed"
|
|
196
|
+
for line in "${REPO_PASSED[@]}"; do
|
|
197
|
+
echo "- $line"
|
|
198
|
+
done
|
|
199
|
+
echo ""
|
|
200
|
+
fi
|
|
201
|
+
echo "## Agent audits (summaries)"
|
|
202
|
+
echo ""
|
|
203
|
+
for line in "${AGENT_RESULTS[@]:-}"; do
|
|
204
|
+
echo "$line"
|
|
205
|
+
done
|
|
206
|
+
echo ""
|
|
207
|
+
echo "Individual audit reports are in $LOG_DIR/"
|
|
208
|
+
} > "$REPORT"
|
|
209
|
+
|
|
210
|
+
# Print summary
|
|
211
|
+
echo "Repo audit: $STATUS"
|
|
212
|
+
echo " Agents: ${#AGENTS[@]}"
|
|
213
|
+
echo " Repo-level: ${#REPO_PASSED[@]} passed, ${#REPO_WARNINGS[@]} warnings, ${#REPO_FAILURES[@]} failures"
|
|
214
|
+
[ ${#REPO_FAILURES[@]} -gt 0 ] && {
|
|
215
|
+
echo "Repo failures:"
|
|
216
|
+
for line in "${REPO_FAILURES[@]}"; do echo " $line"; done
|
|
217
|
+
}
|
|
218
|
+
echo "Full report: $REPORT"
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# create-function.sh — register a new function category and scaffold its folder
|
|
3
|
+
# Usage: bash scripts/create-function.sh <slug> [--description "..."] [--with-expert] [--no-confirm]
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
CONFIG="$ROOT/.config/functions.yaml"
|
|
9
|
+
|
|
10
|
+
source "$ROOT/scripts/lib/functions.sh"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<EOF
|
|
14
|
+
Usage: $0 <slug> [--description "..."] [--with-expert] [--no-confirm]
|
|
15
|
+
|
|
16
|
+
<slug> required, lowercase kebab-case, ^[a-z][a-z0-9-]*\$
|
|
17
|
+
--description one-line description (prompted if omitted in TTY)
|
|
18
|
+
--with-expert scaffold EXPERT.md stub and set has_expert: true
|
|
19
|
+
--no-confirm skip the interactive proliferation prompt
|
|
20
|
+
|
|
21
|
+
Environment:
|
|
22
|
+
AGENT_TEAM_NO_CONFIRM=1 equivalent to --no-confirm
|
|
23
|
+
EOF
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if [ $# -lt 1 ]; then
|
|
27
|
+
usage >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
SLUG=""
|
|
32
|
+
DESCRIPTION=""
|
|
33
|
+
WITH_EXPERT="" # "" | "true" | "false"; "" means unresolved (will prompt)
|
|
34
|
+
DESC_PROVIDED=0
|
|
35
|
+
NO_CONFIRM=0
|
|
36
|
+
|
|
37
|
+
while [ $# -gt 0 ]; do
|
|
38
|
+
case "$1" in
|
|
39
|
+
--description)
|
|
40
|
+
[ $# -lt 2 ] && { echo "ERROR: --description requires a value" >&2; exit 1; }
|
|
41
|
+
DESCRIPTION="$2"
|
|
42
|
+
DESC_PROVIDED=1
|
|
43
|
+
shift 2
|
|
44
|
+
;;
|
|
45
|
+
--description=*)
|
|
46
|
+
DESCRIPTION="${1#--description=}"
|
|
47
|
+
DESC_PROVIDED=1
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
--with-expert)
|
|
51
|
+
WITH_EXPERT="true"
|
|
52
|
+
shift
|
|
53
|
+
;;
|
|
54
|
+
--no-confirm)
|
|
55
|
+
NO_CONFIRM=1
|
|
56
|
+
shift
|
|
57
|
+
;;
|
|
58
|
+
-h|--help)
|
|
59
|
+
usage
|
|
60
|
+
exit 0
|
|
61
|
+
;;
|
|
62
|
+
--*)
|
|
63
|
+
echo "ERROR: unknown flag '$1'" >&2
|
|
64
|
+
usage >&2
|
|
65
|
+
exit 1
|
|
66
|
+
;;
|
|
67
|
+
*)
|
|
68
|
+
if [ -z "$SLUG" ]; then
|
|
69
|
+
SLUG="$1"
|
|
70
|
+
shift
|
|
71
|
+
else
|
|
72
|
+
echo "ERROR: unexpected positional arg '$1'" >&2
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
if [ "${AGENT_TEAM_NO_CONFIRM:-0}" = "1" ]; then
|
|
80
|
+
NO_CONFIRM=1
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ -z "$SLUG" ]; then
|
|
84
|
+
echo "ERROR: <slug> is required" >&2
|
|
85
|
+
usage >&2
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# 1. Validate slug format
|
|
90
|
+
if ! [[ "$SLUG" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
|
91
|
+
echo "ERROR: slug '$SLUG' is invalid. Must be lowercase, start with a letter, and only contain a-z, 0-9, and hyphens." >&2
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# 2. Check config exists and is parseable
|
|
96
|
+
if [ ! -f "$CONFIG" ]; then
|
|
97
|
+
echo "ERROR: $CONFIG not found or unreadable" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
if ! read_functions >/dev/null 2>&1; then
|
|
101
|
+
echo "ERROR: failed to read $CONFIG (malformed YAML or unreadable)" >&2
|
|
102
|
+
read_functions >/dev/null # re-run to surface stderr
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# 3. Check slug not already in registry
|
|
107
|
+
if is_valid_function "$SLUG"; then
|
|
108
|
+
echo "ERROR: function '$SLUG' is already registered in $CONFIG" >&2
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# 4. Check folder doesn't exist
|
|
113
|
+
TARGET="$ROOT/$SLUG"
|
|
114
|
+
if [ -e "$TARGET" ]; then
|
|
115
|
+
echo "ERROR: '$TARGET' already exists on disk" >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# 5. Resolve description
|
|
120
|
+
is_tty() { [ -t 0 ] && [ -t 1 ]; }
|
|
121
|
+
|
|
122
|
+
if [ $DESC_PROVIDED -eq 0 ]; then
|
|
123
|
+
if is_tty; then
|
|
124
|
+
printf "Description (one line, e.g. 'Research — discovery, market analysis'): " >&2
|
|
125
|
+
IFS= read -r DESCRIPTION
|
|
126
|
+
fi
|
|
127
|
+
if [ -z "$DESCRIPTION" ]; then
|
|
128
|
+
echo "ERROR: --description not provided and not running interactively" >&2
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Strip trailing whitespace
|
|
134
|
+
DESCRIPTION="${DESCRIPTION%"${DESCRIPTION##*[![:space:]]}"}"
|
|
135
|
+
if [ -z "$DESCRIPTION" ]; then
|
|
136
|
+
echo "ERROR: description is empty" >&2
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# 6. Resolve has_expert
|
|
141
|
+
if [ -z "$WITH_EXPERT" ]; then
|
|
142
|
+
if is_tty; then
|
|
143
|
+
printf "Does this function need an expert? (y/N): " >&2
|
|
144
|
+
IFS= read -r ANS
|
|
145
|
+
case "$ANS" in
|
|
146
|
+
y|Y|yes|YES) WITH_EXPERT="true" ;;
|
|
147
|
+
*) WITH_EXPERT="false" ;;
|
|
148
|
+
esac
|
|
149
|
+
else
|
|
150
|
+
WITH_EXPERT="false"
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# 7. Soft proliferation reminder
|
|
155
|
+
if [ "$NO_CONFIRM" -eq 0 ] && is_tty; then
|
|
156
|
+
cat >&2 <<EOF
|
|
157
|
+
About to create function: $SLUG
|
|
158
|
+
Reminder: function categories should map to how you mentally divide work.
|
|
159
|
+
Will you have at least 2-3 agents in this function within ~90 days?
|
|
160
|
+
If not, the agent likely fits an existing function. Press Ctrl-C to abort,
|
|
161
|
+
or any key to proceed.
|
|
162
|
+
EOF
|
|
163
|
+
read -r -n 1 _ || true
|
|
164
|
+
echo "" >&2
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# === Mutations begin here ===
|
|
168
|
+
# Track what we created so we can roll back on failure.
|
|
169
|
+
CREATED_PATHS=()
|
|
170
|
+
CONFIG_BACKUP="$(mktemp -t functions-yaml-backup.XXXXXX)" || {
|
|
171
|
+
echo "ERROR: could not create backup tempfile" >&2
|
|
172
|
+
exit 1
|
|
173
|
+
}
|
|
174
|
+
cp "$CONFIG" "$CONFIG_BACKUP"
|
|
175
|
+
|
|
176
|
+
rollback() {
|
|
177
|
+
echo "Rolling back partial changes..." >&2
|
|
178
|
+
for p in "${CREATED_PATHS[@]:-}"; do
|
|
179
|
+
[ -e "$p" ] && rm -rf "$p"
|
|
180
|
+
done
|
|
181
|
+
cp "$CONFIG_BACKUP" "$CONFIG"
|
|
182
|
+
}
|
|
183
|
+
trap 'rollback; rm -f "$CONFIG_BACKUP"' ERR
|
|
184
|
+
|
|
185
|
+
# 8. Append entry to YAML
|
|
186
|
+
{
|
|
187
|
+
printf ' - slug: %s\n' "$SLUG"
|
|
188
|
+
printf ' description: %s\n' "$DESCRIPTION"
|
|
189
|
+
printf ' has_expert: %s\n' "$WITH_EXPERT"
|
|
190
|
+
} >> "$CONFIG"
|
|
191
|
+
|
|
192
|
+
# 9. Create folder
|
|
193
|
+
mkdir -p "$TARGET"
|
|
194
|
+
CREATED_PATHS+=("$TARGET")
|
|
195
|
+
|
|
196
|
+
# 10. Stub README.md
|
|
197
|
+
README_PATH="$TARGET/README.md"
|
|
198
|
+
{
|
|
199
|
+
printf '# Global %s agents\n\n' "$SLUG"
|
|
200
|
+
printf '%s\n\n' "$DESCRIPTION"
|
|
201
|
+
printf 'No agents yet. Add via:\n\n'
|
|
202
|
+
printf '```bash\n'
|
|
203
|
+
printf 'bash scripts/new-agent.sh %s <agent-name>\n' "$SLUG"
|
|
204
|
+
printf '```\n\n'
|
|
205
|
+
printf "When you add the first agent here, see \`gtm/sdr/\` as the canonical example of an agent's directory layout.\n"
|
|
206
|
+
} > "$README_PATH"
|
|
207
|
+
|
|
208
|
+
# 11. Optional EXPERT.md stub
|
|
209
|
+
if [ "$WITH_EXPERT" = "true" ]; then
|
|
210
|
+
EXPERT_PATH="$TARGET/EXPERT.md"
|
|
211
|
+
cat > "$EXPERT_PATH" <<EOF
|
|
212
|
+
# $SLUG Expert
|
|
213
|
+
|
|
214
|
+
<!--
|
|
215
|
+
This is a stub. Fill in the expert system prompt for this function.
|
|
216
|
+
|
|
217
|
+
Experts shape SUBSTRATE (project guidelines), not artifacts. They critique
|
|
218
|
+
and generate guideline files in \`guidelines/\` at the workspace root.
|
|
219
|
+
|
|
220
|
+
Required sections (see other EXPERT.md files for examples once they exist):
|
|
221
|
+
- Identity (1 paragraph)
|
|
222
|
+
- Scope (guide / critique / generate guidelines)
|
|
223
|
+
- Skill routing (table)
|
|
224
|
+
- Practitioner panel (optional, only if it adds value)
|
|
225
|
+
- Read-first protocol
|
|
226
|
+
- Output rules (what writes where, what doesn't)
|
|
227
|
+
- Stage filter (early-stage constraints)
|
|
228
|
+
|
|
229
|
+
Keep it concise. Lean on skills rather than restating their content here.
|
|
230
|
+
-->
|
|
231
|
+
|
|
232
|
+
# Stub: replace with the function's expert system prompt.
|
|
233
|
+
EOF
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
# 12. Append to operation log
|
|
237
|
+
LOG_DIR="$ROOT/chief-of-staff/logs/$(date +%Y-%m)"
|
|
238
|
+
mkdir -p "$LOG_DIR"
|
|
239
|
+
LOG_FILE="$LOG_DIR/operations-$(date +%Y-%m-%d).md"
|
|
240
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
241
|
+
{
|
|
242
|
+
echo ""
|
|
243
|
+
echo "## $TIMESTAMP — create-function: $SLUG"
|
|
244
|
+
echo "Description: $DESCRIPTION"
|
|
245
|
+
echo "has_expert: $WITH_EXPERT"
|
|
246
|
+
} >> "$LOG_FILE"
|
|
247
|
+
|
|
248
|
+
# Success — disarm rollback trap
|
|
249
|
+
trap - ERR
|
|
250
|
+
rm -f "$CONFIG_BACKUP"
|
|
251
|
+
|
|
252
|
+
echo ""
|
|
253
|
+
echo "✓ Function '$SLUG' created."
|
|
254
|
+
echo " Folder: $TARGET/"
|
|
255
|
+
echo " README: $README_PATH"
|
|
256
|
+
[ "$WITH_EXPERT" = "true" ] && echo " EXPERT.md: $TARGET/EXPERT.md (stub)"
|
|
257
|
+
echo " Registry: $CONFIG (entry appended)"
|
|
258
|
+
echo " Log: $LOG_FILE"
|
|
259
|
+
echo ""
|
|
260
|
+
SLUG_ENV=$(echo "$SLUG" | tr '[:lower:]-' '[:upper:]_')
|
|
261
|
+
|
|
262
|
+
echo "Reminders for the new function:"
|
|
263
|
+
echo " 1. HITL routing — agents in this function will route to #${SLUG}"
|
|
264
|
+
echo " - Create the Slack channel #${SLUG}"
|
|
265
|
+
echo " - Add to .env: SLACK_HITL_CHANNEL_${SLUG_ENV}=#${SLUG}"
|
|
266
|
+
[ "$WITH_EXPERT" = "true" ] && echo " 2. Fill in $TARGET/EXPERT.md with the expert system prompt"
|
|
267
|
+
echo " $([ "$WITH_EXPERT" = "true" ] && echo 3 || echo 2). Add the first agent: bash scripts/new-agent.sh $SLUG <agent-name>"
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# Shared script libraries
|
|
2
2
|
|
|
3
|
-
Helper functions for use across scripts.
|
|
3
|
+
Helper functions for use across scripts.
|
|
4
4
|
|
|
5
5
|
Conventions:
|
|
6
6
|
- Bash: `<n>.sh`, sourced via `source "$(dirname $0)/lib/<n>.sh"`
|
|
7
7
|
- Python: `<n>.py` if needed (use `pip install --break-system-packages`)
|
|
8
8
|
- Keep functions narrow
|
|
9
9
|
|
|
10
|
+
## Current inhabitants
|
|
11
|
+
|
|
12
|
+
- `functions.sh` — read/validate the function registry at `.config/functions.yaml`. Pure bash + falls back to `python3` + `pyyaml` when available for safer YAML parsing.
|
|
13
|
+
- `bindings-prompt.sh` — **disabled in v1.0**. Phase 2 will rebuild this around the env-merge loader (config.yaml `tools:` metadata + `/.env` values). Until then, invocation aborts with manual-configuration instructions (edit agent.md `## Tools and bindings` → mirror the YAML block into `<agent>/config.yaml` → add env-var values to `/.env`). The file remains in the tarball + executable so smoke continues to assert presence; the runtime behavior is the guard message.
|
|
14
|
+
|
|
10
15
|
Example future additions:
|
|
11
16
|
- `lib/lesson.sh` — read/write lesson files, validate schema
|
|
12
17
|
- `lib/run.sh` — append to run files, format frontmatter
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bindings-prompt.sh — DISABLED in v1.0 (ROS-78).
|
|
3
|
+
#
|
|
4
|
+
# Pre-v1.0 behavior: read `## Tools and bindings` from agent.md (legacy
|
|
5
|
+
# two-level `tool: key: {required, description}` schema), prompt the user
|
|
6
|
+
# for binding values, and append a `tools:` block to a project-instance
|
|
7
|
+
# config at `projects/<inst>/config/default.yaml`.
|
|
8
|
+
#
|
|
9
|
+
# Both inputs change in v1.0:
|
|
10
|
+
# 1. The shipped `## Tools and bindings` block in agent.md is now a flat
|
|
11
|
+
# schema (`tool: { env_var, required, description }` per
|
|
12
|
+
# conventions.md). The legacy parser silently emits an empty
|
|
13
|
+
# `tools.<tool>:` block when it encounters scalar values where it
|
|
14
|
+
# expects a mapping.
|
|
15
|
+
# 2. The instance-config target is gone — agent config lives at
|
|
16
|
+
# `<function>/<agent>/config.yaml`, and env-var values belong in
|
|
17
|
+
# workspace `/.env` (overridable in `<agent>/.env`).
|
|
18
|
+
#
|
|
19
|
+
# The proper v1.0 flow (read agent.md flat schema → write metadata to
|
|
20
|
+
# config.yaml `tools:` block → prompt for required env-var values and
|
|
21
|
+
# append to `/.env`) requires the env-merge loader that ships in Phase 2.
|
|
22
|
+
# Rather than ship a script whose advertised behavior is broken against
|
|
23
|
+
# the new schema, this script aborts with manual-configuration instructions
|
|
24
|
+
# (edit agent.md schema → mirror to config.yaml → fill /.env).
|
|
25
|
+
#
|
|
26
|
+
# Tracking: re-enable as part of the Phase 2 cli-plumbing reshape
|
|
27
|
+
# (env-merge loader + doctor checks 13-15).
|
|
28
|
+
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
cat >&2 <<'MSG'
|
|
32
|
+
bindings-prompt.sh is disabled in Roster v1.0.
|
|
33
|
+
|
|
34
|
+
The legacy two-level schema parser this script uses is incompatible with
|
|
35
|
+
the flat ## Tools and bindings schema documented in conventions.md
|
|
36
|
+
("Tool bindings" section). The Phase 2 reshape (env-merge loader +
|
|
37
|
+
config.yaml/.env split) will rebuild this flow.
|
|
38
|
+
|
|
39
|
+
Until Phase 2 lands, configure tool bindings by hand:
|
|
40
|
+
|
|
41
|
+
1. Open <function>/<agent>/agent.md and confirm the ## Tools and
|
|
42
|
+
bindings YAML block lists each tool with env_var, required, and
|
|
43
|
+
description fields (see conventions.md § "Tool bindings").
|
|
44
|
+
2. Mirror that block as the tools: mapping in
|
|
45
|
+
<function>/<agent>/config.yaml.
|
|
46
|
+
3. Add the corresponding env var values to workspace /.env (or, for
|
|
47
|
+
agent-scoped overrides, <function>/<agent>/.env).
|
|
48
|
+
|
|
49
|
+
(The chief-of-staff guided create-agent flow shipped in v0.4 is also on
|
|
50
|
+
the pre-v1 shape; it will be updated alongside the env-merge loader.)
|
|
51
|
+
MSG
|
|
52
|
+
|
|
53
|
+
exit 2
|
|
@@ -20,7 +20,8 @@ read_functions() {
|
|
|
20
20
|
fi
|
|
21
21
|
if _have_pyyaml; then
|
|
22
22
|
python3 -c "
|
|
23
|
-
import yaml, sys
|
|
23
|
+
import yaml, re, sys
|
|
24
|
+
SLUG_RE = re.compile(r'^[a-z][a-z0-9-]*\$')
|
|
24
25
|
try:
|
|
25
26
|
with open('$config') as f:
|
|
26
27
|
data = yaml.safe_load(f) or {}
|
|
@@ -29,12 +30,23 @@ except yaml.YAMLError as e:
|
|
|
29
30
|
sys.exit(1)
|
|
30
31
|
for fn in data.get('functions', []):
|
|
31
32
|
slug = fn.get('slug', '')
|
|
32
|
-
if slug:
|
|
33
|
-
|
|
33
|
+
if not slug:
|
|
34
|
+
continue
|
|
35
|
+
if not SLUG_RE.match(slug):
|
|
36
|
+
sys.stderr.write(\"ERROR: malformed slug '\" + slug + \"' in $config — must match ^[a-z][a-z0-9-]*\$\n\")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
print(slug)
|
|
34
39
|
" || return 1
|
|
35
40
|
else
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
local slug
|
|
42
|
+
while IFS= read -r slug; do
|
|
43
|
+
if ! [[ "$slug" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
|
44
|
+
echo "ERROR: malformed slug '$slug' in $config — must match ^[a-z][a-z0-9-]*\$" >&2
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
printf '%s\n' "$slug"
|
|
48
|
+
done < <(grep -E '^[[:space:]]*-[[:space:]]*slug:[[:space:]]*' "$config" \
|
|
49
|
+
| sed -E 's/^[[:space:]]*-[[:space:]]*slug:[[:space:]]*//; s/[[:space:]]*$//')
|
|
38
50
|
fi
|
|
39
51
|
}
|
|
40
52
|
|