@firatcand/roster 0.1.0 → 0.4.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.
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # new-agent-instance.sh — adds an instance of a global agent to a project
3
+ # Usage: bash scripts/new-agent-instance.sh <project> <function> <agent-name>
4
+
5
+ set -euo pipefail
6
+
7
+ if [ $# -ne 3 ]; then
8
+ echo "Usage: $0 <project> <function> <agent-name>"
9
+ echo "Example: $0 myproject gtm sdr"
10
+ exit 1
11
+ fi
12
+
13
+ PROJECT="$1"
14
+ FN="$2"
15
+ AGENT="$3"
16
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17
+
18
+ source "$ROOT/scripts/lib/functions.sh"
19
+
20
+ if ! is_valid_function "$FN"; then
21
+ echo "ERROR: '$FN' is not a registered function." >&2
22
+ echo "Registered functions:" >&2
23
+ read_functions | sed 's/^/ - /' >&2
24
+ exit 1
25
+ fi
26
+
27
+ PROJECT_DIR="$ROOT/projects/$PROJECT"
28
+ GLOBAL_AGENT_DIR="$ROOT/$FN/$AGENT"
29
+ INSTANCE_DIR="$GLOBAL_AGENT_DIR/projects/$PROJECT"
30
+ INSTANCE_TEMPLATE="$GLOBAL_AGENT_DIR/projects/_template"
31
+
32
+ if [ ! -d "$PROJECT_DIR" ]; then
33
+ echo "ERROR: Project '$PROJECT' not found at $PROJECT_DIR"
34
+ echo "Create it first: bash scripts/new-project.sh $PROJECT"
35
+ exit 1
36
+ fi
37
+
38
+ if [ ! -d "$GLOBAL_AGENT_DIR" ]; then
39
+ echo "ERROR: Global agent '$FN/$AGENT' not found at $GLOBAL_AGENT_DIR"
40
+ echo "Create it first: bash scripts/new-agent.sh $FN $AGENT"
41
+ exit 1
42
+ fi
43
+
44
+ if [ -d "$INSTANCE_DIR" ]; then
45
+ echo "ERROR: Instance already exists at $INSTANCE_DIR"
46
+ exit 1
47
+ fi
48
+
49
+ if [ ! -d "$INSTANCE_TEMPLATE" ]; then
50
+ echo "ERROR: Agent has no _template instance at $INSTANCE_TEMPLATE"
51
+ echo "(Recreate the agent or create the template manually.)"
52
+ exit 1
53
+ fi
54
+
55
+ CURRENT_MONTH=$(date -u +"%Y-%m")
56
+ TODAY=$(date -u +"%Y-%m-%d")
57
+
58
+ echo "Adding instance of $FN/$AGENT to project $PROJECT"
59
+
60
+ # Copy the template
61
+ cp -R "$INSTANCE_TEMPLATE" "$INSTANCE_DIR"
62
+
63
+ # Portable sed
64
+ SED_INPLACE=(-i)
65
+ if [[ "$(uname)" == "Darwin" ]]; then
66
+ SED_INPLACE=(-i '')
67
+ fi
68
+
69
+ # Patch placeholders in config and asset-references
70
+ sed "${SED_INPLACE[@]}" "s/<project-slug>/$PROJECT/g" "$INSTANCE_DIR/config/default.yaml"
71
+ sed "${SED_INPLACE[@]}" "s/<YYYY-MM-DD>/$TODAY/g" "$INSTANCE_DIR/config/default.yaml"
72
+ sed "${SED_INPLACE[@]}" "s/<Project>/$PROJECT/g" "$INSTANCE_DIR/config/default.yaml"
73
+ sed "${SED_INPLACE[@]}" "s/<project>/$PROJECT/g" "$INSTANCE_DIR/asset-references.md"
74
+
75
+ # Set up current month in log dirs
76
+ mkdir -p "$INSTANCE_DIR/log/runs/$CURRENT_MONTH" "$INSTANCE_DIR/log/feedback/$CURRENT_MONTH"
77
+ touch "$INSTANCE_DIR/log/runs/$CURRENT_MONTH/.gitkeep"
78
+ touch "$INSTANCE_DIR/log/feedback/$CURRENT_MONTH/.gitkeep"
79
+
80
+ # Prompt for tool bindings if the global agent.md has them
81
+ AGENT_MD="$GLOBAL_AGENT_DIR/agent.md"
82
+ INSTANCE_CONFIG="$INSTANCE_DIR/config/default.yaml"
83
+ if [ -f "$AGENT_MD" ] && grep -q '^## Tools and bindings' "$AGENT_MD"; then
84
+ echo ""
85
+ echo "=== Tool bindings for $FN/$AGENT in $PROJECT ==="
86
+ echo "Enter values for each binding. Press Enter (or type 'skip') to leave as TODO."
87
+ bash "$ROOT/scripts/lib/bindings-prompt.sh" "$AGENT_MD" "$INSTANCE_CONFIG" || \
88
+ echo "WARNING: bindings-prompt failed; tools: block not appended. Edit $INSTANCE_CONFIG manually."
89
+ else
90
+ echo ""
91
+ echo "(Agent has no '## Tools and bindings' section in agent.md — skipping binding prompt)"
92
+ fi
93
+
94
+ # Pull list of expected MCPs and skills from agent's .mcp.json (rough — just shows what file exists)
95
+ echo ""
96
+ echo "✓ Instance added: $INSTANCE_DIR"
97
+ echo ""
98
+ echo "Reminders:"
99
+ echo " - Edit $INSTANCE_DIR/config/default.yaml (see $GLOBAL_AGENT_DIR/agent.md § Inputs)"
100
+ echo " - Edit $INSTANCE_DIR/asset-references.md to list which assets this agent uses"
101
+ echo " - Verify project has required guidelines:"
102
+ echo " $PROJECT_DIR/guidelines/voice.md"
103
+ echo " $PROJECT_DIR/guidelines/icps/*.md (at least one)"
104
+ echo " $PROJECT_DIR/guidelines/asset-links.md"
105
+ echo " - Verify agent's MCPs are configured in $GLOBAL_AGENT_DIR/.mcp.json"
106
+ echo ""
107
+ echo "Test from a session:"
108
+ echo " cd $INSTANCE_DIR/"
109
+ echo " claude"
110
+ echo " \"Run $AGENT — dry run, just show me the plan\""
111
+ echo ""
112
+ # Scheduling: register with the native desktop scheduler via `roster schedule install`.
113
+ # See docs/SCHEDULING.md and docs/adr/0001-scheduling-architecture.md for the model.
114
+ echo "Optionally schedule this agent: roster schedule install $FN/$AGENT <plan> --cron \"<expr>\" --tool claude|codex"
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/env bash
2
+ # new-agent.sh — scaffolds a new global agent under a function category
3
+ #
4
+ # Usage:
5
+ # bash scripts/new-agent.sh <function> <agent-name>
6
+ # bash scripts/new-agent.sh --slash-only <function> <agent-name>
7
+ #
8
+ # --slash-only is the recovery flag invoked by chief-of-staff's guided-mode
9
+ # atomic-write transaction when the slash command file fails to land after
10
+ # the agent tree has already been written (see ROS-52). It writes only
11
+ # .claude/commands/<agent>.md, with strict no-clobber, and requires the
12
+ # agent tree to exist.
13
+
14
+ set -euo pipefail
15
+
16
+ print_usage() {
17
+ cat >&2 <<EOF
18
+ Usage:
19
+ $0 <function> <agent-name>
20
+ $0 --slash-only <function> <agent-name>
21
+
22
+ Function: any slug registered in .config/functions.yaml
23
+ Example: $0 gtm content-agent
24
+
25
+ --slash-only: recovery flag. Writes only .claude/commands/<agent-name>.md.
26
+ Requires the agent tree at <function>/<agent-name>/ to already exist.
27
+ Exits non-zero (no clobber) if the slash command file already exists.
28
+ EOF
29
+ }
30
+
31
+ SLASH_ONLY=0
32
+ if [ "${1:-}" = "--slash-only" ]; then
33
+ SLASH_ONLY=1
34
+ shift
35
+ fi
36
+
37
+ if [ $# -ne 2 ]; then
38
+ print_usage
39
+ exit 1
40
+ fi
41
+
42
+ FN="$1"
43
+ NAME="$2"
44
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
45
+ TARGET="$ROOT/$FN/$NAME"
46
+
47
+ source "$ROOT/scripts/lib/functions.sh"
48
+
49
+ # write_slash_command <abs-file-path> <function> <agent-name>
50
+ # Canonical writer for .claude/commands/<agent>.md. Both the full-install
51
+ # path and --slash-only call this so the two paths cannot drift over time
52
+ # (see docs/learnings/2026-Q2/installer-mutations-need-drift-detector-mirror.md).
53
+ write_slash_command() {
54
+ local file="$1" fn="$2" name="$3"
55
+ # ROS-62: quote the description so YAML-special characters in $fn or the
56
+ # placeholder body don't trip the I4 YAML parser when invariants run.
57
+ cat > "$file" <<EOF
58
+ ---
59
+ name: $name
60
+ description: "$fn agent — TODO: fill in description"
61
+ ---
62
+
63
+ # /$name
64
+
65
+ You are operating the \`$fn/$name\` agent. Load \`$fn/$name/agent.md\` and the project's relevant context.
66
+
67
+ The user request is: \$ARGUMENTS
68
+
69
+ ## Routing logic
70
+
71
+ Parse the user request:
72
+
73
+ 1. **If it matches \`run <plan-name> for <project>\` or \`run <plan-name> on <project>\`**:
74
+ - Load \`$fn/$name/plans/<plan-name>.yaml\`. If it doesn't exist, list available plans and ask user to pick.
75
+ - Load \`projects/<project>/CLAUDE.md\` and relevant guidelines.
76
+ - Load \`$fn/$name/projects/<project>/config/default.yaml\`.
77
+ - Validate that all required tool bindings are non-TODO. Abort if not.
78
+ - Execute the plan steps. Substitute \`\${tools.X.Y}\`, \`\${inputs.X}\`, \`\${config.X}\`.
79
+ - Log to \`$fn/$name/projects/<project>/log/runs/<YYYY-MM>/<YYYY-MM-DD-HHMM>.md\`.
80
+ - Surface HITL approvals per the plan's approval_channel.
81
+
82
+ 2. **If only a project is named (no plan)**:
83
+ - List available plans from \`$fn/$name/plans/\` with descriptions. Ask user to pick.
84
+
85
+ 3. **If neither plan nor project is named**:
86
+ - List available projects and plans. Ask user to specify both.
87
+
88
+ 4. **For ad-hoc strategic work**: suggest invoking \`$fn/EXPERT.md\` instead.
89
+
90
+ ## Constraints
91
+
92
+ - Only run plans that exist as files in \`$fn/$name/plans/\`.
93
+ - Don't bypass approval gates.
94
+ - File writes go to the instance's \`log/runs/\` unless the plan explicitly writes elsewhere.
95
+ EOF
96
+ }
97
+
98
+ if ! is_valid_function "$FN"; then
99
+ echo "ERROR: '$FN' is not a registered function." >&2
100
+ echo "Registered functions:" >&2
101
+ read_functions | sed 's/^/ - /' >&2
102
+ echo "" >&2
103
+ echo "To add a new function: bash scripts/create-function.sh $FN" >&2
104
+ exit 1
105
+ fi
106
+
107
+ if ! [[ "$NAME" =~ ^[a-z][a-z0-9-]*$ ]]; then
108
+ echo "ERROR: Agent name must be lowercase, alphanumeric + hyphens." >&2
109
+ exit 1
110
+ fi
111
+
112
+ # --slash-only branch: write only the slash command file and exit.
113
+ # Requires agent tree to exist; refuses to clobber an existing slash command.
114
+ if [ "$SLASH_ONLY" = "1" ]; then
115
+ if [ ! -d "$TARGET" ]; then
116
+ echo "ERROR: agent tree '$FN/$NAME' does not exist at $TARGET." >&2
117
+ echo " --slash-only is a recovery flag — create the agent tree first with:" >&2
118
+ echo " bash scripts/new-agent.sh $FN $NAME" >&2
119
+ exit 1
120
+ fi
121
+ COMMANDS_DIR="$ROOT/.claude/commands"
122
+ SLASH_CMD_FILE="$COMMANDS_DIR/$NAME.md"
123
+ if [ -f "$SLASH_CMD_FILE" ]; then
124
+ echo "ERROR: slash command already exists at .claude/commands/$NAME.md. Refusing to clobber." >&2
125
+ exit 1
126
+ fi
127
+ mkdir -p "$COMMANDS_DIR"
128
+ write_slash_command "$SLASH_CMD_FILE" "$FN" "$NAME"
129
+ echo "Created: .claude/commands/$NAME.md"
130
+ exit 0
131
+ fi
132
+
133
+ if [ -d "$TARGET" ]; then
134
+ echo "ERROR: Agent '$FN/$NAME' already exists at $TARGET" >&2
135
+ exit 1
136
+ fi
137
+
138
+ echo "Creating agent: $FN/$NAME"
139
+
140
+ mkdir -p "$TARGET"/{subagents,playbook,logs,projects/_template,.claude/skills,.claude/plugins}
141
+
142
+ # agent.md stub
143
+ cat > "$TARGET/agent.md" << EOF
144
+ # $NAME
145
+
146
+ ## Purpose
147
+
148
+ <One paragraph: what this agent does, why it exists.>
149
+
150
+ ## Inputs
151
+
152
+ The orchestrator expects:
153
+
154
+ - \`project\`: project slug
155
+ - <other inputs>
156
+
157
+ Read at runtime:
158
+
159
+ - \`agent.md\` (this file)
160
+ - \`projects/<project>/<this-agent>/config/default.yaml\`
161
+ - \`projects/<project>/CLAUDE.md\`
162
+ - \`projects/<project>/guidelines/voice.md\`
163
+ - <other guidelines this agent uses>
164
+ - \`<this-agent>/projects/<project>/playbook/\` — project-scoped lessons
165
+ - \`<this-agent>/playbook/\` — global lessons
166
+
167
+ ## Steps
168
+
169
+ 1. Resolve config and context
170
+ 2. <step>
171
+ 3. <step>
172
+
173
+ ## Subagents
174
+
175
+ - <subagent-name>.md — <one-liner>
176
+
177
+ ## Tools
178
+
179
+ Agent-scoped MCPs at \`<this-agent>/.mcp.json\`:
180
+ - <tool/MCP> — <purpose>
181
+
182
+ Universal MCPs (Slack, Google Drive) inherited from agent-team root.
183
+
184
+ ## Outputs
185
+
186
+ Run file at \`projects/<project>/<this-agent>/log/runs/<YYYY-MM>/<YYYY-MM-DD-HHMM>.md\`. See \`conventions.md\` § "Run file format".
187
+
188
+ ## Approval
189
+
190
+ \`approval_channel: auto\` — in-session if interactive, Slack \`#${FN}\` if cron (resolved via \`SLACK_HITL_CHANNEL_$(echo "$FN" | tr '[:lower:]-' '[:upper:]_')\` in \`.env\`).
191
+
192
+ ## Lessons protocol
193
+
194
+ Log candidate lessons inline in run output under \`## Candidate lessons\`. Don't write to \`playbook/\` directly during runs — that's the dreamer's job.
195
+
196
+ ## Failure modes
197
+
198
+ - <known failure mode>: <handling>
199
+ EOF
200
+
201
+ # README
202
+ cat > "$TARGET/README.md" << EOF
203
+ # $NAME
204
+
205
+ <One-line description.>
206
+
207
+ ## Files
208
+
209
+ - \`agent.md\` — orchestrator contract
210
+ - \`subagents/\` — specialized roles
211
+ - \`playbook/\` — global lessons (one file per lesson)
212
+ - \`logs/\` — agent-level operational logs
213
+ - \`.claude/\` — agent-scoped skills, plugins
214
+ - \`.mcp.json\` — agent-scoped MCPs (CREATE THIS — see template comment)
215
+ - \`projects/\` — per-project instances
216
+
217
+ ## Invocation
218
+
219
+ From a project instance session:
220
+
221
+ \`\`\`bash
222
+ cd $FN/$NAME/projects/<project>/
223
+ claude
224
+ "Run $NAME on <inputs>"
225
+ \`\`\`
226
+
227
+ From cron: see ROS-39 (Phase 2.5 scheduling primitives — wrapper layout + install script land then).
228
+
229
+ ## Configuration
230
+
231
+ Per-project: \`projects/<proj>/$FN/$NAME/config/default.yaml\` (created by \`new-agent-instance.sh\`).
232
+
233
+ ## Outputs
234
+
235
+ \`projects/<proj>/$FN/$NAME/log/runs/<YYYY-MM>/<YYYY-MM-DD-HHMM>.md\`
236
+ EOF
237
+
238
+ # .mcp.json stub
239
+ cat > "$TARGET/.mcp.json" << EOF
240
+ {
241
+ "_comment": "Agent-scoped MCPs for $NAME. Available when working in this agent's tree (including project instances). Add MCPs this agent specifically needs. Universal MCPs (Slack, Google Drive) are inherited from agent-team/.mcp.json.",
242
+ "mcpServers": {}
243
+ }
244
+ EOF
245
+
246
+ # .claude/settings.json
247
+ cat > "$TARGET/.claude/settings.json" << EOF
248
+ {
249
+ "_comment": "Agent-scoped Claude Code settings for $NAME."
250
+ }
251
+ EOF
252
+
253
+ # Subagent template
254
+ cat > "$TARGET/subagents/_template.md" << 'EOF'
255
+ # <Subagent Name>
256
+
257
+ ## Role
258
+ <One paragraph: narrow job, single responsibility.>
259
+
260
+ ## Inputs
261
+ <What the orchestrator passes in.>
262
+
263
+ ## Output
264
+ <Structured output the orchestrator can parse.>
265
+
266
+ ## Tools
267
+ <Named tools this subagent uses.>
268
+
269
+ ## Boundaries
270
+ <What this subagent does NOT do.>
271
+
272
+ ## Quality bar
273
+ <Specific criteria for acceptable output.>
274
+ EOF
275
+
276
+ # Project instance template
277
+ mkdir -p "$TARGET/projects/_template/config"
278
+ mkdir -p "$TARGET/projects/_template/playbook"
279
+ mkdir -p "$TARGET/projects/_template/log/runs"
280
+ mkdir -p "$TARGET/projects/_template/log/feedback"
281
+
282
+ cat > "$TARGET/projects/_template/config/default.yaml" << EOF
283
+ ---
284
+ agent: $NAME
285
+ project: <project-slug>
286
+ created: <YYYY-MM-DD>
287
+ last_modified: <YYYY-MM-DD>
288
+ ---
289
+
290
+ # $NAME Config — <Project>
291
+
292
+ # See $FN/$NAME/agent.md § "Inputs" for required fields.
293
+ # Use prose comments to explain "why" alongside "what".
294
+ EOF
295
+
296
+ cat > "$TARGET/projects/_template/asset-references.md" << EOF
297
+ # Asset references — $NAME / <project>
298
+
299
+ This agent uses these assets from \`projects/<project>/guidelines/asset-links.md\`:
300
+
301
+ - <e.g., specific asset by name>
302
+ EOF
303
+
304
+ touch "$TARGET/playbook/.gitkeep"
305
+ touch "$TARGET/logs/.gitkeep"
306
+ touch "$TARGET/.claude/skills/.gitkeep"
307
+ touch "$TARGET/.claude/plugins/.gitkeep"
308
+ touch "$TARGET/projects/_template/playbook/.gitkeep"
309
+ touch "$TARGET/projects/_template/log/runs/.gitkeep"
310
+ touch "$TARGET/projects/_template/log/feedback/.gitkeep"
311
+
312
+ # === Tool bindings prompt (added by tool-bindings workflow) ===
313
+ # Ask the user whether to define tools now. If yes, collect tool names and scaffold
314
+ # a stub `## Tools and bindings` section in the new agent.md.
315
+
316
+ echo ""
317
+ NEW_AGENT_MD="$ROOT/$FN/$NAME/agent.md"
318
+
319
+ # Skip prompt entirely if non-interactive (no TTY) or AGENT_TEAM_NO_CONFIRM=1
320
+ if [ -t 0 ] && [ "${AGENT_TEAM_NO_CONFIRM:-0}" != "1" ]; then
321
+ read -r -p "Define tools and bindings now? (y/N): " DEFINE_TOOLS
322
+ DEFINE_TOOLS="${DEFINE_TOOLS:-N}"
323
+
324
+ if [[ "$DEFINE_TOOLS" =~ ^[Yy]$ ]]; then
325
+ echo ""
326
+ echo "Which tools is this agent using?"
327
+ echo " Enter comma-separated names (e.g., gmail, drive, attio, heyreach)."
328
+ echo " Or press Enter to skip."
329
+ read -r -p " > " TOOLS_LINE
330
+
331
+ if [ -n "$TOOLS_LINE" ]; then
332
+ # Normalize: split by comma, trim whitespace, lowercase
333
+ TOOL_NAMES=()
334
+ IFS=',' read -ra RAW_TOOLS <<< "$TOOLS_LINE"
335
+ for raw in "${RAW_TOOLS[@]}"; do
336
+ clean="$(echo "$raw" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | tr '[:upper:]' '[:lower:]')"
337
+ if [[ -n "$clean" && "$clean" =~ ^[a-z][a-z0-9_-]*$ ]]; then
338
+ TOOL_NAMES+=("$clean")
339
+ elif [ -n "$clean" ]; then
340
+ echo " WARN: skipping invalid tool name '$clean' (must be lowercase, start with letter, only [a-z0-9_-])" >&2
341
+ fi
342
+ done
343
+
344
+ if [ ${#TOOL_NAMES[@]} -gt 0 ]; then
345
+ {
346
+ echo ""
347
+ echo "## Tools and bindings"
348
+ echo ""
349
+ echo "Per-project tool bindings expected by this agent. Chief-of-staff prompts for these when scaffolding a new agent-instance. Values land in \`projects/<project>/config/default.yaml\` under a \`tools:\` key."
350
+ echo ""
351
+ echo "Fill in each tool's bindings below. Schema: each binding has a \`required\` flag (true/false) and a \`description\`."
352
+ echo ""
353
+ echo '```yaml'
354
+ for tool in "${TOOL_NAMES[@]}"; do
355
+ echo "$tool:"
356
+ echo " # TODO: define bindings"
357
+ echo " # <binding_name>:"
358
+ echo " # required: true"
359
+ echo " # description: \"...\""
360
+ done
361
+ echo '```'
362
+ } >> "$NEW_AGENT_MD"
363
+
364
+ echo ""
365
+ echo "✓ Added '## Tools and bindings' to $NEW_AGENT_MD with stubs for: ${TOOL_NAMES[*]}"
366
+ echo " Edit agent.md to fill in actual bindings before adding instances."
367
+ else
368
+ echo " No valid tool names provided. Skipping section."
369
+ fi
370
+ else
371
+ echo " Empty input. Skipping section."
372
+ fi
373
+ else
374
+ echo "(Skipped tool definition. Add a '## Tools and bindings' section manually later if needed.)"
375
+ fi
376
+ else
377
+ # Non-interactive: skip the prompt entirely. User can add the section manually.
378
+ :
379
+ fi
380
+ # === End tool bindings prompt ===
381
+
382
+ # === Plans directory ===
383
+ PLANS_DIR="$TARGET/plans"
384
+ mkdir -p "$PLANS_DIR"
385
+ touch "$PLANS_DIR/.gitkeep"
386
+ echo "Created: $FN/$NAME/plans/"
387
+
388
+ # === Slash command file ===
389
+ COMMANDS_DIR="$ROOT/.claude/commands"
390
+ mkdir -p "$COMMANDS_DIR"
391
+ SLASH_CMD_FILE="$COMMANDS_DIR/$NAME.md"
392
+
393
+ if [ ! -f "$SLASH_CMD_FILE" ]; then
394
+ write_slash_command "$SLASH_CMD_FILE" "$FN" "$NAME"
395
+ echo "Created: .claude/commands/$NAME.md"
396
+ fi
397
+
398
+ echo ""
399
+ echo "✓ Agent '$FN/$NAME' created at $TARGET"
400
+ echo ""
401
+ echo "Next steps:"
402
+ echo " 1. Fill in $TARGET/agent.md (purpose, inputs, steps, subagents, tools, outputs)"
403
+ echo " 2. Add subagents: cp $TARGET/subagents/_template.md $TARGET/subagents/<name>.md and fill in"
404
+ echo " 3. Add agent-scoped MCPs to $TARGET/.mcp.json if needed (HeyReach, Apollo, etc.)"
405
+ echo " 4. Update $TARGET/README.md with a real description"
406
+ echo " 5. Add at least one plan to $TARGET/plans/ (e.g., $TARGET/plans/<plan-name>.yaml)"
407
+ echo " 6. Edit .claude/commands/$NAME.md to fill in the description"
408
+ echo " 7. Add an instance to a project: bash scripts/new-agent-instance.sh <project> $FN $NAME"
409
+ echo ""
410
+ echo "Reference: see gtm/sdr/ for a complete example."
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # remove-agent-from-project.sh — archives a single agent instance from a project
3
+ # Usage: bash scripts/remove-agent-from-project.sh <project> <function> <agent>
4
+
5
+ set -euo pipefail
6
+
7
+ if [ $# -ne 3 ]; then
8
+ echo "Usage: $0 <project> <function> <agent>"
9
+ exit 1
10
+ fi
11
+
12
+ PROJECT="$1"
13
+ FN="$2"
14
+ AGENT="$3"
15
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
16
+ INSTANCE_DIR="$ROOT/$FN/$AGENT/projects/$PROJECT"
17
+
18
+ if [ ! -d "$INSTANCE_DIR" ]; then
19
+ echo "ERROR: Instance not found at $INSTANCE_DIR"
20
+ exit 1
21
+ fi
22
+
23
+ DATE=$(date +%Y-%m-%d)
24
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
25
+
26
+ # Determine archive suffix
27
+ SUFFIX="$DATE"
28
+ COUNTER=2
29
+ while [ -d "$ROOT/_archive/$FN/$AGENT/projects/$PROJECT-$SUFFIX" ]; do
30
+ SUFFIX="$DATE-$COUNTER"
31
+ COUNTER=$((COUNTER + 1))
32
+ done
33
+
34
+ echo "Removing instance: $FN/$AGENT/projects/$PROJECT (archiving with suffix $SUFFIX)"
35
+
36
+ mkdir -p "$ROOT/_archive/$FN/$AGENT/projects"
37
+ mv "$INSTANCE_DIR" "$ROOT/_archive/$FN/$AGENT/projects/$PROJECT-$SUFFIX"
38
+ echo " Moved: $FN/$AGENT/projects/$PROJECT/ → _archive/$FN/$AGENT/projects/$PROJECT-$SUFFIX/"
39
+
40
+ # Update project CLAUDE.md — remove the line referencing this instance
41
+ PROJECT_CLAUDE="$ROOT/projects/$PROJECT/CLAUDE.md"
42
+ if [ -f "$PROJECT_CLAUDE" ]; then
43
+ # Remove lines that reference the removed instance under "Active agent instances"
44
+ # Match patterns like "- `gtm/sdr/projects/<project>/` — ..."
45
+ SED_INPLACE=(-i)
46
+ if [[ "$(uname)" == "Darwin" ]]; then
47
+ SED_INPLACE=(-i '')
48
+ fi
49
+ sed "${SED_INPLACE[@]}" "/^- \`$FN\/$AGENT\/projects\/$PROJECT\//d" "$PROJECT_CLAUDE"
50
+ echo " Updated: projects/$PROJECT/CLAUDE.md (removed instance line)"
51
+ fi
52
+
53
+ # Operation log
54
+ LOG_DIR="$ROOT/chief-of-staff/logs/$(date +%Y-%m)"
55
+ mkdir -p "$LOG_DIR"
56
+ LOG_FILE="$LOG_DIR/operations-$(date +%Y-%m-%d).md"
57
+ {
58
+ echo ""
59
+ echo "## $TIMESTAMP — remove-agent-from-project"
60
+ echo "Project: $PROJECT, Agent: $FN/$AGENT"
61
+ echo "Archive suffix: $SUFFIX"
62
+ } >> "$LOG_FILE"
63
+
64
+ echo ""
65
+ echo "✓ Instance removed (archived)."
66
+ echo " To restore: move _archive/$FN/$AGENT/projects/$PROJECT-$SUFFIX back to $FN/$AGENT/projects/$PROJECT"
67
+ echo " Operation log: $LOG_FILE"