@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.
Files changed (75) hide show
  1. package/README.md +77 -215
  2. package/agents/lesson-drafter.md +3 -8
  3. package/agents/pattern-detector.md +0 -1
  4. package/bin/roster.js +7233 -1197
  5. package/data/plan-ceilings.yaml +57 -0
  6. package/package.json +8 -3
  7. package/skills/chief-of-staff/SKILL.md +199 -59
  8. package/skills/dreamer/SKILL.md +8 -7
  9. package/skills/roster-orchestrator/SKILL.md +53 -25
  10. package/templates/CLAUDE.project.template.md +1 -1
  11. package/templates/CONTEXT.template.md +2 -2
  12. package/templates/gitignore-defaults.txt +2 -0
  13. package/templates/hooks/banner.sh +47 -0
  14. package/templates/scaffold/chief-of-staff/README.md +16 -24
  15. package/templates/scaffold/chief-of-staff/agent.md +22 -32
  16. package/templates/scaffold/chief-of-staff/plans/audit-agent.yaml +4 -4
  17. package/templates/scaffold/chief-of-staff/plans/audit-repo.yaml +5 -4
  18. package/templates/scaffold/chief-of-staff/plans/create-agent.yaml +5 -34
  19. package/templates/scaffold/config/project.yaml.template +10 -0
  20. package/templates/scaffold/conventions.md +188 -173
  21. package/templates/scaffold/dreamer/README.md +2 -2
  22. package/templates/scaffold/dreamer/agent.md +0 -1
  23. package/templates/scaffold/dreamer/plans/nightly-reflection.yaml +23 -37
  24. package/templates/scaffold/dreamer/subagents/lesson-drafter.md +2 -7
  25. package/templates/scaffold/{projects/_demo/guidelines → guidelines}/asset-links.md +4 -0
  26. package/templates/scaffold/{projects/_demo/guidelines → guidelines}/brand-book.md +4 -0
  27. package/templates/scaffold/{projects/_demo/guidelines → guidelines}/messaging.md +4 -0
  28. package/templates/scaffold/{projects/_demo/guidelines → guidelines}/voice.md +4 -0
  29. package/templates/scaffold/logs/cron/.gitkeep +1 -0
  30. package/templates/scaffold/ops/EXPERT.md +5 -5
  31. package/templates/scaffold/scripts/audit-agent.sh +326 -0
  32. package/templates/scaffold/scripts/audit-repo.sh +218 -0
  33. package/templates/scaffold/scripts/create-function.sh +267 -0
  34. package/templates/scaffold/scripts/lib/README.md +6 -1
  35. package/templates/scaffold/scripts/lib/bindings-prompt.sh +53 -0
  36. package/templates/scaffold/scripts/lib/functions.sh +17 -5
  37. package/templates/scaffold/scripts/new-agent.sh +416 -0
  38. package/templates/scaffold/scripts/rename-agent.sh +91 -0
  39. package/templates/scaffold/scripts/save-state.sh +32 -0
  40. package/agents/critic.md +0 -74
  41. package/agents/enricher.md +0 -56
  42. package/agents/promotion-arbiter.md +0 -71
  43. package/agents/prospector.md +0 -51
  44. package/agents/writer.md +0 -58
  45. package/skills/sdr/SKILL.md +0 -147
  46. package/templates/scaffold/chief-of-staff/plans/add-agent-to-project.yaml +0 -45
  47. package/templates/scaffold/chief-of-staff/plans/archive-project.yaml +0 -51
  48. package/templates/scaffold/chief-of-staff/plans/audit-project.yaml +0 -34
  49. package/templates/scaffold/chief-of-staff/plans/create-project.yaml +0 -65
  50. package/templates/scaffold/chief-of-staff/plans/remove-agent-from-project.yaml +0 -50
  51. package/templates/scaffold/chief-of-staff/plans/rename-project.yaml +0 -62
  52. package/templates/scaffold/chief-of-staff/plans/unarchive-project.yaml +0 -41
  53. package/templates/scaffold/dreamer/subagents/promotion-arbiter.md +0 -64
  54. package/templates/scaffold/gtm/sdr/.claude/settings.json +0 -3
  55. package/templates/scaffold/gtm/sdr/.mcp.json +0 -21
  56. package/templates/scaffold/gtm/sdr/README.md +0 -46
  57. package/templates/scaffold/gtm/sdr/agent.md +0 -136
  58. package/templates/scaffold/gtm/sdr/plans/cold-outreach.yaml +0 -92
  59. package/templates/scaffold/gtm/sdr/projects/_demo/asset-references.md +0 -7
  60. package/templates/scaffold/gtm/sdr/projects/_demo/config/default.yaml +0 -69
  61. package/templates/scaffold/gtm/sdr/projects/_demo/log/feedback/.gitkeep +0 -0
  62. package/templates/scaffold/gtm/sdr/projects/_demo/log/runs/.gitkeep +0 -0
  63. package/templates/scaffold/gtm/sdr/projects/_demo/playbook/.gitkeep +0 -0
  64. package/templates/scaffold/gtm/sdr/subagents/critic.md +0 -67
  65. package/templates/scaffold/gtm/sdr/subagents/enricher.md +0 -49
  66. package/templates/scaffold/gtm/sdr/subagents/prospector.md +0 -44
  67. package/templates/scaffold/gtm/sdr/subagents/writer.md +0 -51
  68. package/templates/scaffold/projects/_demo/CLAUDE.md +0 -35
  69. package/templates/scaffold/projects/_demo/README.md +0 -16
  70. package/templates/scaffold/projects/_demo/assets/.gitkeep +0 -0
  71. package/templates/scaffold/projects/_demo/config/default.yaml +0 -28
  72. package/templates/scaffold/projects/_demo/state.md +0 -11
  73. package/templates/scaffold/scripts/new-project.sh +0 -125
  74. /package/templates/scaffold/gtm/{sdr/playbook/.gitkeep → .gitkeep} +0 -0
  75. /package/templates/scaffold/{projects/_demo/guidelines → guidelines}/icps/_persona-template.md +0 -0
@@ -9,7 +9,7 @@ Cross-domain pattern detection matters. A lesson observed in Twitter automation
9
9
  ## Files
10
10
 
11
11
  - `agent.md` — orchestrator contract
12
- - `subagents/` — pattern-detector, lesson-drafter, promotion-arbiter
12
+ - `subagents/` — pattern-detector, lesson-drafter
13
13
  - `playbook/` — the dreamer's own lessons (lessons about how to learn)
14
14
  - `logs/` — its own runs
15
15
  - `state.md` — last processed cutoff
@@ -17,7 +17,7 @@ Cross-domain pattern detection matters. A lesson observed in Twitter automation
17
17
 
18
18
  ## Invocation
19
19
 
20
- Nightly via cron (`scripts/cron/wrappers/dreamer-nightly.sh`).
20
+ Nightly via the native desktop scheduler. Register with `roster schedule install` — each fire spawns a fresh CLI session in the workspace, loads `CONTEXT.md`, invokes the `roster-orchestrator` skill, and dispatches the dreamer in isolated subagent context. See `conventions.md` § Schedules for the model.
21
21
 
22
22
  On-demand from a session: "Run the dreamer on the last week's outreach runs across all projects."
23
23
 
@@ -45,7 +45,6 @@ Typically scheduled nightly via cron or `/schedule`. When invoked without a plan
45
45
 
46
46
  - `pattern-detector.md` — finds patterns across runs+feedback
47
47
  - `lesson-drafter.md` — drafts a single lesson in schema format
48
- - `promotion-arbiter.md` — decides project vs global scope for validated lessons
49
48
 
50
49
  ## Tools and bindings
51
50
 
@@ -1,11 +1,9 @@
1
1
  plan: nightly-reflection
2
2
  description: |
3
- Reads runs and feedback across all agents and projects since the last
4
- cutoff, detects patterns, drafts lesson candidates, surfaces them for HITL
5
- approval via Slack #admin, and writes approved lessons to the relevant
6
- playbook directories. Promotes project-scoped lessons to global when the
7
- pattern is observed in 2+ projects. Updates dreamer/state.md with the new
8
- cutoff and a summary.
3
+ Reads runs and feedback across all agents since the last cutoff, detects
4
+ patterns, drafts lesson candidates, surfaces them for HITL approval via
5
+ Slack #admin, and writes approved lessons to each agent's playbook.
6
+ Updates dreamer/state.md with the new cutoff and a summary.
9
7
 
10
8
  inputs:
11
9
  mode:
@@ -14,7 +12,7 @@ inputs:
14
12
  description: nightly | weekly | on-demand. Selects how aggressively to scan and how to weight evidence.
15
13
  scope:
16
14
  required: false
17
- description: Limit to one project (slug) or one agent (function/agent) — useful for on-demand runs.
15
+ description: Limit to one agent (function/agent) — useful for on-demand runs.
18
16
  since:
19
17
  required: false
20
18
  description: ISO timestamp cutoff. If omitted, uses last_processed_through from dreamer/state.md.
@@ -24,7 +22,6 @@ outputs:
24
22
  candidates_drafted: integer
25
23
  candidates_approved: integer
26
24
  lessons_written: integer
27
- lessons_promoted: integer
28
25
  conflicts_surfaced: integer
29
26
 
30
27
  steps:
@@ -35,9 +32,9 @@ steps:
35
32
 
36
33
  - id: identify_material
37
34
  description: |
38
- Walk every agent's <function>/<agent>/projects/<project>/log/runs/<YYYY-MM>/
39
- and log/feedback/<YYYY-MM>/ for files newer than the cutoff. Match runs
40
- to feedback by filename. Apply ${inputs.scope} filter if provided.
35
+ Walk every agent's <function>/<agent>/log/runs/<YYYY-MM>/ and
36
+ log/feedback/<YYYY-MM>/ for files newer than the cutoff. Match runs to
37
+ feedback by filename. Apply ${inputs.scope} filter if provided.
41
38
 
42
39
  - id: detect_patterns
43
40
  subagent: pattern-detector
@@ -62,52 +59,41 @@ steps:
62
59
  description: |
63
60
  For each candidate that meets threshold or extends an existing lesson,
64
61
  draft a lesson in schema format with frontmatter (id, source: dreamer,
65
- scope: project|global, status: candidate, agent, created). Place drafts
66
- in dreamer/pending/.
62
+ status: candidate, agent, created). Place drafts in
63
+ <function>/<agent>/pending/.
67
64
  args:
68
65
  input_from: accumulate_evidence
69
66
 
70
- - id: arbitrate_promotion
71
- subagent: promotion-arbiter
72
- description: |
73
- For any project lesson validated in 2+ projects, decide whether to
74
- promote to global scope. Returns project vs global designation per
75
- candidate.
76
- args:
77
- input_from: draft_lessons
78
-
79
67
  - id: hitl_routing
80
68
  description: |
81
- Post all candidates and promotions to Slack #admin (channel from
69
+ Post all candidates to Slack #admin (channel from
82
70
  SLACK_HITL_CHANNEL_ADMIN env var) as threaded messages, one per
83
71
  candidate. Format: "Candidate lesson <id>: <title>. Approve / Reject /
84
72
  Defer." TTL 7 days. If Slack unavailable, queue candidates locally in
85
- dreamer/pending/ and retry next run.
73
+ <function>/<agent>/pending/ and retry next run.
86
74
  approval: slack
87
75
 
88
76
  - id: apply_approvals
89
77
  description: |
90
- On approval, write the lesson to the correct location:
91
- - Project-scoped: <function>/<agent>/projects/<project>/playbook/L-...md
92
- with scope: project
93
- - Promoted to global: <function>/<agent>/playbook/L-...md with
94
- scope: global AND mark project lesson promoted_to_global: true
95
- - Retired: update existing lesson's status: retired with reason
96
- All dreamer-written lessons get source: dreamer in frontmatter.
97
- Respect human-written lessons (source: human) — never modify or
98
- supersede without explicit HITL approval.
78
+ On approval, move the candidate file from <function>/<agent>/pending/
79
+ to <function>/<agent>/playbook/L-...md. There is no scope decision;
80
+ v1 has a single playbook per agent. All dreamer-written lessons get
81
+ source: dreamer in frontmatter. Respect human-written lessons
82
+ (source: human) never modify or supersede without explicit HITL
83
+ approval. Retired candidates update an existing lesson's status:
84
+ retired with reason.
99
85
 
100
86
  - id: update_state
101
87
  description: |
102
88
  Write timestamp + summary to dreamer/state.md:
103
89
  last_processed_through: <ISO timestamp>
104
- last_run_summary: drafted N, approved M, written K, promoted P
90
+ last_run_summary: drafted N, approved M, written K
105
91
 
106
92
  - id: write_run_log
107
93
  description: |
108
94
  Write run details to dreamer/logs/<YYYY-MM>/<YYYY-MM-DD-HHMM>.md
109
- including: material processed (counts by project and agent), patterns
110
- detected, lesson candidates drafted (Slack thread links), promotion
111
- candidates, approvals applied, conflicts surfaced.
95
+ including: material processed (counts by agent), patterns detected,
96
+ lesson candidates drafted (Slack thread links), approvals applied,
97
+ conflicts surfaced.
112
98
 
113
99
  approval_channel: slack
@@ -9,20 +9,17 @@ Take a candidate pattern and draft a lesson file in the schema defined in `conve
9
9
  - `pattern` (object): output from pattern-detector
10
10
  - `existing_lesson` (object, optional): if extending an existing lesson, the current version
11
11
  - `agent` (string): which agent
12
- - `project` (string): which project sourced it
13
12
 
14
13
  ## Output
15
14
 
16
15
  ```yaml
17
16
  suggested_filename: L-2026-04-26-001.md
18
- suggested_path: <function>/<agent>/projects/<project>/playbook/ # or <function>/<agent>/playbook/
17
+ suggested_path: <function>/<agent>/playbook/
19
18
  status: candidate
20
19
  lesson_markdown: |
21
20
  ---
22
21
  id: L-2026-04-26-001
23
22
  source: dreamer
24
- scope: project # or global
25
- project: _demo # or "—" if scope=global
26
23
  agent: sdr
27
24
  ...full frontmatter per conventions...
28
25
  ---
@@ -31,7 +28,6 @@ lesson_markdown: |
31
28
 
32
29
  ## Pattern observed
33
30
  ## Recommendation
34
- ## Why this might be project-specific
35
31
  ## Retirement criteria
36
32
  ```
37
33
 
@@ -43,9 +39,8 @@ None.
43
39
 
44
40
  - Use the exact schema in `conventions.md`. Don't invent fields.
45
41
  - Always set `source: dreamer`.
46
- - Default to `scope: project` unless explicitly handling a promotion case.
47
42
  - Cite evidence in body, not just frontmatter.
48
- - Body has 4 sections: pattern, recommendation, scope reasoning, retirement criteria. That's it.
43
+ - Body has 3 sections: pattern, recommendation, retirement criteria. That's it.
49
44
 
50
45
  ## Quality bar
51
46
 
@@ -1,3 +1,7 @@
1
+ > **Example content — replace with your project's actuals.**
2
+ > This file ships filled with an illustrative brand ("Acme Corp") so you can
3
+ > see the expected shape. Overwrite freely; no agent will warn if you do.
4
+
1
5
  # Asset Links — Acme Corp
2
6
 
3
7
  ## Brand
@@ -1,3 +1,7 @@
1
+ > **Example content — replace with your project's actuals.**
2
+ > This file ships filled with an illustrative brand ("Acme Corp") so you can
3
+ > see the expected shape. Overwrite freely; no agent will warn if you do.
4
+
1
5
  # Brand Book — Acme Corp
2
6
 
3
7
  ## Logo
@@ -1,3 +1,7 @@
1
+ > **Example content — replace with your project's actuals.**
2
+ > This file ships filled with an illustrative brand ("Acme Corp") so you can
3
+ > see the expected shape. Overwrite freely; no agent will warn if you do.
4
+
1
5
  # Messaging — Acme Corp
2
6
 
3
7
  ## Headline value props
@@ -1,3 +1,7 @@
1
+ > **Example content — replace with your project's actuals.**
2
+ > This file ships filled with an illustrative brand ("Acme Corp") so you can
3
+ > see the expected shape. Overwrite freely; no agent will warn if you do.
4
+
1
5
  # Voice — Acme Corp
2
6
 
3
7
  ## Adjectives describing the brand voice
@@ -0,0 +1 @@
1
+ # Cron log directory
@@ -11,7 +11,7 @@ Ops advisor for an early-stage solo founder running an agent team. Cover automat
11
11
 
12
12
  ## Scope
13
13
 
14
- - **Critique**: Audit `scripts/cron/crontab`, `scripts/cron/wrappers/*`, agent `config/default.yaml` files, `.env` patterns, and ops-related project guidelines when they exist. State the principle being violated. Score risk: data loss > silent failure > cost > polish.
14
+ - **Critique**: Audit `roster/<function>/schedules.yaml`, `.roster/schedule-specs/`, agent `config/default.yaml` files, `.env` patterns, and ops-related project guidelines when they exist. State the principle being violated. Score risk: data loss > silent failure > cost > polish.
15
15
  - **Generate guidelines**: Produce or refine ops-related guideline files when a project demands them — `projects/<project>/guidelines/ops-runbook.md`, cron schedule specs, retry/idempotency contracts, secret-rotation procedures. Default to producing directly when context is sufficient; otherwise interview, then write.
16
16
  - **Guide**: Scheduling decisions, secrets management, deployment patterns, observability strategy, failure-mode reasoning. Strategic output — files only when the task asks for substrate.
17
17
 
@@ -22,16 +22,16 @@ You do **NOT** produce tactical artifacts (specific cron wrapper shell scripts,
22
22
  On invocation, read in this order:
23
23
 
24
24
  1. `projects/<project>/CLAUDE.md` — project identity and what runs against it
25
- 2. `scripts/cron/crontab` and `scripts/cron/wrappers/` — current automation surface
25
+ 2. `roster/<function>/schedules.yaml` and `.roster/schedule-specs/` — current automation surface (Phase 2.5 native-scheduler model; see `conventions.md` § Schedules and [ADR-0001](../../docs/adr/0001-scheduling-architecture.md))
26
26
  3. The relevant agent's `agent.md` and `config/default.yaml` — tool bindings, schedules, caps
27
27
  4. `projects/<project>/state.md` — current focus
28
- 5. `logs/cron/*` for recent failures, if a reliability question
28
+ 5. `logs/cron/*` for recent `roster schedule install --tool codex --via cron` failures, if a reliability question
29
29
 
30
30
  Identify gaps. Ask only about gaps. Don't re-ask what's already in substrate. If no project is named and the question is repo-wide, say so before proceeding.
31
31
 
32
32
  ## What you cover
33
33
 
34
- - Scheduling (cron, Claude Code `/schedule`, GitHub Actions scheduled workflows)
34
+ - Scheduling (native desktop scheduler via `roster schedule install`, the `roster schedule install --tool codex --via cron` crontab path, GitHub Actions scheduled workflows)
35
35
  - Secrets management (`.env`, env-var conventions, rotation, SOPS or similar when justified)
36
36
  - Deployment patterns (script-based, GitHub Actions, manual checklists)
37
37
  - Observability (`logs/cron/`, structured logging, alerting thresholds, "did it run" verification)
@@ -62,7 +62,7 @@ When a task spans skills (e.g., "design the cron + monitoring + alert chain for
62
62
  ## Behavior rules
63
63
 
64
64
  - **Idempotency first.** Every operation must be safe to re-run. If it isn't, name the guard.
65
- - **Observability is non-negotiable.** If you can't tell whether it ran, it didn't. Logs land in `logs/cron/<job>-<YYYY-MM-DD>.log` per `conventions.md`.
65
+ - **Observability is non-negotiable.** If you can't tell whether it ran, it didn't. For `--via cron` installs (Codex-only — `roster schedule install --tool codex --via cron`), stdout/stderr lands in `logs/cron/<job>.log` per `conventions.md` § Schedules. For UI-handoff installs (Claude Scheduled Tasks, Codex Automations), the scheduler owns the log surface — check the host app's run history.
66
66
  - **Cost-aware.** Name the cost of every recommendation — dollars, time, on-call burden.
67
67
  - **Name failure modes.** Don't ship a recommendation without stating what happens when it fails.
68
68
  - **Stay in your lane.** Reusable substrate and patterns. One-off incident response and per-run remediations are agent work, not expert work.
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env bash
2
+ # audit-agent.sh — checks agent structure completeness, reports issues with suggested fixes
3
+ # Usage: bash scripts/audit-agent.sh <function> <agent>
4
+
5
+ set -euo pipefail
6
+
7
+ if [ $# -ne 2 ]; then
8
+ echo "Usage: $0 <function> <agent>"
9
+ exit 1
10
+ fi
11
+
12
+ FN="$1"
13
+ AGENT="$2"
14
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
15
+ AGENT_DIR="$ROOT/$FN/$AGENT"
16
+
17
+ source "$ROOT/scripts/lib/functions.sh"
18
+
19
+ if ! is_valid_function "$FN"; then
20
+ echo "ERROR: '$FN' is not a registered function." >&2
21
+ echo "Registered functions:" >&2
22
+ read_functions | sed 's/^/ - /' >&2
23
+ exit 1
24
+ fi
25
+
26
+ if [ ! -d "$AGENT_DIR" ]; then
27
+ echo "ERROR: Agent '$FN/$AGENT' not found at $AGENT_DIR"
28
+ exit 1
29
+ fi
30
+
31
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
32
+ RUN_TIME=$(date +%Y-%m-%d-%H%M)
33
+ LOG_DIR="$ROOT/chief-of-staff/logs/$(date +%Y-%m)"
34
+ mkdir -p "$LOG_DIR"
35
+ REPORT="$LOG_DIR/audit-$FN-$AGENT-$RUN_TIME.md"
36
+
37
+ FAILURES=()
38
+ WARNINGS=()
39
+ PASSED=()
40
+
41
+ # === agent.md required sections ===
42
+ if [ ! -f "$AGENT_DIR/agent.md" ]; then
43
+ FAILURES+=("[$FN/$AGENT/agent.md] missing")
44
+ else
45
+ REQUIRED_SECTIONS=("## Purpose" "## Inputs" "## Plans" "## Subagents" "## Outputs" "## Approval" "## Lessons protocol")
46
+ MISSING=()
47
+ for section in "${REQUIRED_SECTIONS[@]}"; do
48
+ if ! grep -qF "$section" "$AGENT_DIR/agent.md"; then
49
+ MISSING+=("$section")
50
+ fi
51
+ done
52
+ if [ ${#MISSING[@]} -gt 0 ]; then
53
+ FAILURES+=("[$FN/$AGENT/agent.md] missing required sections: ${MISSING[*]}")
54
+ FAILURES+=(" → Suggested fix: add the missing sections per conventions.md § 'Agent contract'")
55
+ else
56
+ PASSED+=("[$FN/$AGENT/agent.md] all required sections present")
57
+ fi
58
+
59
+ # ## Tools and bindings is required ONLY for agents that use external tools.
60
+ # Missing → warning, not failure (agents reading only workspace guidelines
61
+ # don't need it). The chief-of-staff create-agent guided flow adds the
62
+ # section when the user names tools.
63
+ if ! grep -qF "## Tools and bindings" "$AGENT_DIR/agent.md"; then
64
+ WARNINGS+=("[$FN/$AGENT/agent.md] '## Tools and bindings' not declared — fine for tool-less agents; required if this agent calls external APIs")
65
+ fi
66
+
67
+ # Steps section should NOT be present anymore — workflows live in plans/
68
+ if grep -qE '^## Steps' "$AGENT_DIR/agent.md"; then
69
+ WARNINGS+=("[$FN/$AGENT/agent.md] still has a '## Steps' section — workflow logic should live in plans/<plan>.yaml, not agent.md")
70
+ WARNINGS+=(" → Suggested fix: extract workflow steps into a plan file under $FN/$AGENT/plans/ and remove the section from agent.md")
71
+ fi
72
+ fi
73
+
74
+ # === plans/ directory ===
75
+ PLANS_DIR="$AGENT_DIR/plans"
76
+ if [ ! -d "$PLANS_DIR" ]; then
77
+ WARNINGS+=("[$FN/$AGENT/plans/] missing — agent has no plans declared")
78
+ WARNINGS+=(" → Suggested fix: mkdir $PLANS_DIR && add at least one .yaml plan file")
79
+ else
80
+ PLAN_COUNT=$(find "$PLANS_DIR" -maxdepth 1 -name '*.yaml' -type f 2>/dev/null | wc -l | tr -d ' ')
81
+ if [ "$PLAN_COUNT" -eq 0 ]; then
82
+ WARNINGS+=("[$FN/$AGENT/plans/] empty — agent has no plans declared")
83
+ WARNINGS+=(" → Suggested fix: add at least one .yaml plan to $PLANS_DIR")
84
+ else
85
+ PASSED+=("[$FN/$AGENT/plans/] $PLAN_COUNT plan(s)")
86
+ for plan in "$PLANS_DIR"/*.yaml; do
87
+ [ -f "$plan" ] || continue
88
+ REL="${plan#$ROOT/}"
89
+ if command -v python3 >/dev/null 2>&1; then
90
+ if ! python3 -c "import yaml; yaml.safe_load(open('$plan'))" 2>/dev/null; then
91
+ FAILURES+=("[$REL] YAML parse error")
92
+ else
93
+ PASSED+=("[$REL] valid YAML")
94
+ fi
95
+ fi
96
+ done
97
+ fi
98
+ fi
99
+
100
+ # === Slash command file ===
101
+ SLASH_CMD="$ROOT/.claude/commands/$AGENT.md"
102
+ if [ ! -f "$SLASH_CMD" ]; then
103
+ WARNINGS+=("[.claude/commands/$AGENT.md] missing — slash command not registered")
104
+ WARNINGS+=(" → Suggested fix: scaffold via 'bash scripts/new-agent.sh' template, or copy from another agent's slash command file")
105
+ else
106
+ PASSED+=("[.claude/commands/$AGENT.md] present")
107
+ fi
108
+
109
+ # README
110
+ if [ ! -f "$AGENT_DIR/README.md" ]; then
111
+ FAILURES+=("[$FN/$AGENT/README.md] missing")
112
+ else
113
+ PASSED+=("[$FN/$AGENT/README.md] present")
114
+ fi
115
+
116
+ # config.yaml — agent config (guideline refs + tool bindings).
117
+ # Schema check: single-document mapping with agent=$FN/$AGENT, plans_dir,
118
+ # guideline_refs (mapping), tools (mapping). Drift here breaks the runtime
119
+ # loader (Phase 2) and `chief-of-staff create-agent` reuse.
120
+ if [ ! -f "$AGENT_DIR/config.yaml" ]; then
121
+ FAILURES+=("[$FN/$AGENT/config.yaml] missing")
122
+ FAILURES+=(" → Suggested fix: add config.yaml at agent root with at least 'agent: $FN/$AGENT' and 'plans_dir: ./plans/'")
123
+ elif command -v python3 >/dev/null 2>&1; then
124
+ CFG_RC=0
125
+ CFG_MSG="$(AGENT_EXPECT="$FN/$AGENT" CFG_PATH="$AGENT_DIR/config.yaml" python3 - <<'PYEOF' 2>&1
126
+ import os, sys
127
+ try:
128
+ import yaml
129
+ except ImportError:
130
+ sys.stderr.write("pyyaml-missing")
131
+ sys.exit(2)
132
+ expect = os.environ["AGENT_EXPECT"]
133
+ path = os.environ["CFG_PATH"]
134
+ with open(path) as f:
135
+ try:
136
+ doc = yaml.safe_load(f)
137
+ except yaml.YAMLError as e:
138
+ sys.stderr.write(f"yaml-parse-error: {e}")
139
+ sys.exit(1)
140
+ if not isinstance(doc, dict):
141
+ sys.stderr.write("not-a-mapping")
142
+ sys.exit(1)
143
+ errs = []
144
+ agent = doc.get("agent")
145
+ if agent != expect:
146
+ errs.append(f"agent field is {agent!r}, expected {expect!r}")
147
+ if "plans_dir" not in doc:
148
+ errs.append("missing plans_dir")
149
+ gr = doc.get("guideline_refs")
150
+ if gr is not None and not isinstance(gr, dict):
151
+ errs.append("guideline_refs is not a mapping")
152
+ tools = doc.get("tools")
153
+ if tools is not None and not isinstance(tools, dict):
154
+ errs.append("tools is not a mapping")
155
+ if errs:
156
+ sys.stderr.write("; ".join(errs))
157
+ sys.exit(1)
158
+ PYEOF
159
+ )" || CFG_RC=$?
160
+ if [ $CFG_RC -eq 0 ]; then
161
+ PASSED+=("[$FN/$AGENT/config.yaml] valid (schema)")
162
+ elif [ $CFG_RC -eq 2 ]; then
163
+ PASSED+=("[$FN/$AGENT/config.yaml] present (schema not validated, pyyaml missing)")
164
+ else
165
+ FAILURES+=("[$FN/$AGENT/config.yaml] $CFG_MSG")
166
+ FAILURES+=(" → Suggested fix: open the file and ensure it is a single YAML mapping with 'agent: $FN/$AGENT', 'plans_dir', a 'guideline_refs:' mapping, and a 'tools:' mapping (may be empty)")
167
+ fi
168
+ else
169
+ PASSED+=("[$FN/$AGENT/config.yaml] present (schema not validated, python3 missing)")
170
+ fi
171
+
172
+ # .mcp.json valid JSON
173
+ if [ ! -f "$AGENT_DIR/.mcp.json" ]; then
174
+ WARNINGS+=("[$FN/$AGENT/.mcp.json] missing (no agent-scoped MCPs configured)")
175
+ else
176
+ if command -v python3 >/dev/null 2>&1; then
177
+ if ! python3 -c "import json; json.load(open('$AGENT_DIR/.mcp.json'))" 2>/dev/null; then
178
+ FAILURES+=("[$FN/$AGENT/.mcp.json] invalid JSON")
179
+ FAILURES+=(" → Suggested fix: validate with: python3 -c 'import json; json.load(open(\"$AGENT_DIR/.mcp.json\"))'")
180
+ else
181
+ PASSED+=("[$FN/$AGENT/.mcp.json] valid JSON")
182
+ fi
183
+ else
184
+ PASSED+=("[$FN/$AGENT/.mcp.json] present (JSON not validated, python3 missing)")
185
+ fi
186
+ fi
187
+
188
+ # .claude/settings.json
189
+ if [ ! -f "$AGENT_DIR/.claude/settings.json" ]; then
190
+ WARNINGS+=("[$FN/$AGENT/.claude/settings.json] missing")
191
+ else
192
+ if command -v python3 >/dev/null 2>&1; then
193
+ if ! python3 -c "import json; json.load(open('$AGENT_DIR/.claude/settings.json'))" 2>/dev/null; then
194
+ FAILURES+=("[$FN/$AGENT/.claude/settings.json] invalid JSON")
195
+ else
196
+ PASSED+=("[$FN/$AGENT/.claude/settings.json] valid JSON")
197
+ fi
198
+ else
199
+ PASSED+=("[$FN/$AGENT/.claude/settings.json] present")
200
+ fi
201
+ fi
202
+
203
+ # subagents/ exists
204
+ if [ ! -d "$AGENT_DIR/subagents" ]; then
205
+ WARNINGS+=("[$FN/$AGENT/subagents/] missing (may be intentional for very simple agents)")
206
+ else
207
+ # Check each subagent has required sections
208
+ for sub in "$AGENT_DIR/subagents"/*.md; do
209
+ [ -f "$sub" ] || continue
210
+ BASENAME=$(basename "$sub")
211
+ [ "$BASENAME" = "_template.md" ] && continue
212
+ REL="${sub#$ROOT/}"
213
+ SUB_REQUIRED=("## Role" "## Inputs" "## Output" "## Tools" "## Boundaries" "## Quality bar")
214
+ SUB_MISSING=()
215
+ for section in "${SUB_REQUIRED[@]}"; do
216
+ if ! grep -qF "$section" "$sub"; then
217
+ SUB_MISSING+=("$section")
218
+ fi
219
+ done
220
+ if [ ${#SUB_MISSING[@]} -gt 0 ]; then
221
+ WARNINGS+=("[$REL] missing sections: ${SUB_MISSING[*]}")
222
+ else
223
+ PASSED+=("[$REL] all subagent sections present")
224
+ fi
225
+ done
226
+ fi
227
+
228
+ # playbook/ exists
229
+ if [ ! -d "$AGENT_DIR/playbook" ]; then
230
+ WARNINGS+=("[$FN/$AGENT/playbook/] missing")
231
+ else
232
+ PASSED+=("[$FN/$AGENT/playbook/] present")
233
+ fi
234
+
235
+ # Flat-shape directories
236
+ for d in logs/runs logs/feedback pending; do
237
+ if [ ! -d "$AGENT_DIR/$d" ]; then
238
+ WARNINGS+=("[$FN/$AGENT/$d/] missing")
239
+ fi
240
+ done
241
+
242
+ # asset-references.md at agent root
243
+ if [ ! -f "$AGENT_DIR/asset-references.md" ]; then
244
+ WARNINGS+=("[$FN/$AGENT/asset-references.md] missing")
245
+ fi
246
+
247
+ # === Status ===
248
+ if [ ${#FAILURES[@]} -gt 0 ]; then
249
+ STATUS="fail"
250
+ elif [ ${#WARNINGS[@]} -gt 0 ]; then
251
+ STATUS="warn"
252
+ else
253
+ STATUS="pass"
254
+ fi
255
+
256
+ count_items() {
257
+ local arr=("$@")
258
+ local n=0
259
+ for item in "${arr[@]}"; do
260
+ if ! [[ "$item" =~ ^[[:space:]]*→ ]]; then
261
+ n=$((n+1))
262
+ fi
263
+ done
264
+ echo $n
265
+ }
266
+
267
+ N_FAIL=0
268
+ for item in "${FAILURES[@]:-}"; do
269
+ [ -z "$item" ] && continue
270
+ [[ "$item" =~ ^[[:space:]]+→ ]] && continue
271
+ N_FAIL=$((N_FAIL + 1))
272
+ done
273
+ N_WARN=0
274
+ for item in "${WARNINGS[@]:-}"; do
275
+ [ -z "$item" ] && continue
276
+ [[ "$item" =~ ^[[:space:]]+→ ]] && continue
277
+ N_WARN=$((N_WARN + 1))
278
+ done
279
+ N_PASS=${#PASSED[@]}
280
+
281
+ # Write report
282
+ {
283
+ echo "---"
284
+ echo "operation: audit-agent"
285
+ echo "function: $FN"
286
+ echo "agent: $AGENT"
287
+ echo "ran: $TIMESTAMP"
288
+ echo "status: $STATUS"
289
+ echo "---"
290
+ echo ""
291
+ echo "# Audit: $FN/$AGENT"
292
+ echo ""
293
+ echo "## Summary"
294
+ echo "- $N_PASS passed"
295
+ echo "- $N_WARN warnings"
296
+ echo "- $N_FAIL failures"
297
+ echo ""
298
+ if [ $N_FAIL -gt 0 ]; then
299
+ echo "## Failures"
300
+ for line in "${FAILURES[@]}"; do
301
+ echo "- $line"
302
+ done
303
+ echo ""
304
+ fi
305
+ if [ $N_WARN -gt 0 ]; then
306
+ echo "## Warnings"
307
+ for line in "${WARNINGS[@]}"; do
308
+ echo "- $line"
309
+ done
310
+ echo ""
311
+ fi
312
+ if [ $N_PASS -gt 0 ]; then
313
+ echo "## Passed"
314
+ for line in "${PASSED[@]}"; do
315
+ echo "- $line"
316
+ done
317
+ fi
318
+ } > "$REPORT"
319
+
320
+ echo "Audit: $FN/$AGENT — $STATUS"
321
+ echo " Passed: $N_PASS, Warnings: $N_WARN, Failures: $N_FAIL"
322
+ [ $N_FAIL -gt 0 ] && {
323
+ echo "Failures:"
324
+ for line in "${FAILURES[@]}"; do echo " $line"; done
325
+ }
326
+ echo "Full report: $REPORT"