claude-code-session-manager 0.26.0 → 0.28.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 (28) hide show
  1. package/.claude-plugin/marketplace.json +21 -0
  2. package/dist/assets/{TiptapBody-BsI_35c5.js → TiptapBody-CJ6GK5CM.js} +1 -1
  3. package/dist/assets/index-DtQ4LzuV.js +3534 -0
  4. package/dist/assets/index-Dwb94Uxm.css +32 -0
  5. package/dist/index.html +2 -2
  6. package/package.json +3 -1
  7. package/plugins/session-manager-dev/.claude-plugin/plugin.json +19 -0
  8. package/plugins/session-manager-dev/skills/develop/SKILL.md +112 -0
  9. package/plugins/session-manager-dev/skills/develop/standards.md +67 -0
  10. package/plugins/session-manager-dev/skills/explain-to-me/SKILL.md +192 -0
  11. package/plugins/session-manager-dev/skills/explain-to-me/assets/style-reference.html +163 -0
  12. package/plugins/session-manager-dev/skills/local-project-health/SKILL.md +58 -0
  13. package/plugins/session-manager-dev/skills/my-feedback/SKILL.md +132 -0
  14. package/plugins/session-manager-dev/skills/optimize-kpi/SKILL.md +289 -0
  15. package/plugins/session-manager-dev/skills/prd/SKILL.md +134 -0
  16. package/plugins/session-manager-dev/skills/process-feedback/SKILL.md +183 -0
  17. package/plugins/session-manager-dev/skills/project-status/SKILL.md +244 -0
  18. package/plugins/session-manager-dev/skills/requesting-code-review/SKILL.md +105 -0
  19. package/plugins/session-manager-dev/skills/requesting-code-review/code-reviewer.md +146 -0
  20. package/plugins/session-manager-dev/skills/security-review/SKILL.md +105 -0
  21. package/src/main/__tests__/scheduler-autofix-select.test.cjs +95 -0
  22. package/src/main/__tests__/scheduler-autopromote.test.cjs +33 -0
  23. package/src/main/hives.cjs +96 -36
  24. package/src/main/scheduler.cjs +135 -11
  25. package/src/main/templates/PRD_AUTHORING.md +316 -0
  26. package/src/preload/api.d.ts +8 -7
  27. package/dist/assets/index-BKkf6gtA.css +0 -32
  28. package/dist/assets/index-C61eFtxs.js +0 -3525
@@ -0,0 +1,146 @@
1
+ # Code Review Agent
2
+
3
+ You are reviewing code changes for production readiness.
4
+
5
+ **Your task:**
6
+ 1. Review {WHAT_WAS_IMPLEMENTED}
7
+ 2. Compare against {PLAN_OR_REQUIREMENTS}
8
+ 3. Check code quality, architecture, testing
9
+ 4. Categorize issues by severity
10
+ 5. Assess production readiness
11
+
12
+ ## What Was Implemented
13
+
14
+ {DESCRIPTION}
15
+
16
+ ## Requirements/Plan
17
+
18
+ {PLAN_REFERENCE}
19
+
20
+ ## Git Range to Review
21
+
22
+ **Base:** {BASE_SHA}
23
+ **Head:** {HEAD_SHA}
24
+
25
+ ```bash
26
+ git diff --stat {BASE_SHA}..{HEAD_SHA}
27
+ git diff {BASE_SHA}..{HEAD_SHA}
28
+ ```
29
+
30
+ ## Review Checklist
31
+
32
+ **Code Quality:**
33
+ - Clean separation of concerns?
34
+ - Proper error handling?
35
+ - Type safety (if applicable)?
36
+ - DRY principle followed?
37
+ - Edge cases handled?
38
+
39
+ **Architecture:**
40
+ - Sound design decisions?
41
+ - Scalability considerations?
42
+ - Performance implications?
43
+ - Security concerns?
44
+
45
+ **Testing:**
46
+ - Tests actually test logic (not mocks)?
47
+ - Edge cases covered?
48
+ - Integration tests where needed?
49
+ - All tests passing?
50
+
51
+ **Requirements:**
52
+ - All plan requirements met?
53
+ - Implementation matches spec?
54
+ - No scope creep?
55
+ - Breaking changes documented?
56
+
57
+ **Production Readiness:**
58
+ - Migration strategy (if schema changes)?
59
+ - Backward compatibility considered?
60
+ - Documentation complete?
61
+ - No obvious bugs?
62
+
63
+ ## Output Format
64
+
65
+ ### Strengths
66
+ [What's well done? Be specific.]
67
+
68
+ ### Issues
69
+
70
+ #### Critical (Must Fix)
71
+ [Bugs, security issues, data loss risks, broken functionality]
72
+
73
+ #### Important (Should Fix)
74
+ [Architecture problems, missing features, poor error handling, test gaps]
75
+
76
+ #### Minor (Nice to Have)
77
+ [Code style, optimization opportunities, documentation improvements]
78
+
79
+ **For each issue:**
80
+ - File:line reference
81
+ - What's wrong
82
+ - Why it matters
83
+ - How to fix (if not obvious)
84
+
85
+ ### Recommendations
86
+ [Improvements for code quality, architecture, or process]
87
+
88
+ ### Assessment
89
+
90
+ **Ready to merge?** [Yes/No/With fixes]
91
+
92
+ **Reasoning:** [Technical assessment in 1-2 sentences]
93
+
94
+ ## Critical Rules
95
+
96
+ **DO:**
97
+ - Categorize by actual severity (not everything is Critical)
98
+ - Be specific (file:line, not vague)
99
+ - Explain WHY issues matter
100
+ - Acknowledge strengths
101
+ - Give clear verdict
102
+
103
+ **DON'T:**
104
+ - Say "looks good" without checking
105
+ - Mark nitpicks as Critical
106
+ - Give feedback on code you didn't review
107
+ - Be vague ("improve error handling")
108
+ - Avoid giving a clear verdict
109
+
110
+ ## Example Output
111
+
112
+ ```
113
+ ### Strengths
114
+ - Clean database schema with proper migrations (db.ts:15-42)
115
+ - Comprehensive test coverage (18 tests, all edge cases)
116
+ - Good error handling with fallbacks (summarizer.ts:85-92)
117
+
118
+ ### Issues
119
+
120
+ #### Important
121
+ 1. **Missing help text in CLI wrapper**
122
+ - File: index-conversations:1-31
123
+ - Issue: No --help flag, users won't discover --concurrency
124
+ - Fix: Add --help case with usage examples
125
+
126
+ 2. **Date validation missing**
127
+ - File: search.ts:25-27
128
+ - Issue: Invalid dates silently return no results
129
+ - Fix: Validate ISO format, throw error with example
130
+
131
+ #### Minor
132
+ 1. **Progress indicators**
133
+ - File: indexer.ts:130
134
+ - Issue: No "X of Y" counter for long operations
135
+ - Impact: Users don't know how long to wait
136
+
137
+ ### Recommendations
138
+ - Add progress reporting for user experience
139
+ - Consider config file for excluded projects (portability)
140
+
141
+ ### Assessment
142
+
143
+ **Ready to merge: With fixes**
144
+
145
+ **Reasoning:** Core implementation is solid with good architecture and tests. Important issues (help text, date validation) are easily fixed and don't affect core functionality.
146
+ ```
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: security-review
3
+ description: >-
4
+ Check for common security vulnerabilities before shipping code — focus on what
5
+ actually gets exploited (OWASP Top 10: injection, auth, secrets, access control),
6
+ not compliance theater. Use before committing code that handles user input, auth,
7
+ or data storage; when adding API endpoints or external integrations; when
8
+ reviewing dependencies; or when asked to do a security review. Keywords: security,
9
+ vulnerability, OWASP, injection, auth, secrets, review.
10
+ ---
11
+
12
+ # Security Review
13
+
14
+ ## Overview
15
+
16
+ Check for common security vulnerabilities before shipping code. Focus on what actually gets exploited, not compliance theater.
17
+
18
+ ## When to Use
19
+
20
+ - Before committing code that handles user input, auth, or data storage
21
+ - When adding new API endpoints or external integrations
22
+ - When reviewing dependencies or updating packages
23
+ - When asked to do a security review
24
+
25
+ ## OWASP Top 10 Checklist
26
+
27
+ ### Injection (SQL, NoSQL, OS Command, LDAP)
28
+ - All user input parameterized or escaped before use in queries
29
+ - No string concatenation in SQL/NoSQL queries
30
+ - No `eval()`, `exec()`, `shell=True`, or `child_process.exec()` with user input
31
+ - ORM/query builder used instead of raw queries where possible
32
+
33
+ ### Broken Authentication
34
+ - Passwords hashed with bcrypt/argon2 (never MD5/SHA1)
35
+ - Session tokens are random, long, and expire
36
+ - MFA available for sensitive operations
37
+ - No credentials in URLs, logs, or error messages
38
+
39
+ ### Sensitive Data Exposure
40
+ - No secrets in code, config files, or git history (.env, API keys, tokens)
41
+ - Data encrypted at rest (AES-256) and in transit (TLS)
42
+ - Sensitive fields excluded from logs and API responses
43
+ - PII handled according to data classification
44
+
45
+ ### XSS (Cross-Site Scripting)
46
+ - All output HTML-encoded by default (React JSX handles this)
47
+ - No `dangerouslySetInnerHTML` or `v-html` without sanitization
48
+ - Content-Security-Policy headers set
49
+ - User input never inserted into `<script>` tags or event handlers
50
+
51
+ ### Broken Access Control
52
+ - Every API endpoint checks authentication AND authorization
53
+ - No direct object references without ownership verification
54
+ - CORS configured to specific allowed origins (not `*`)
55
+ - Rate limiting on auth and sensitive endpoints
56
+
57
+ ### Security Misconfiguration
58
+ - No default credentials or debug modes in production
59
+ - Error messages don't leak stack traces or internal details
60
+ - HTTP security headers set (HSTS, X-Frame-Options, X-Content-Type-Options)
61
+ - Unnecessary features and endpoints disabled
62
+
63
+ ### CSRF (Cross-Site Request Forgery)
64
+ - State-changing requests use CSRF tokens or SameSite cookies
65
+ - POST/PUT/DELETE endpoints reject requests without valid tokens
66
+
67
+ ## Dependency Check
68
+
69
+ ```bash
70
+ # Node.js
71
+ npm audit
72
+ # Check for known vulnerabilities
73
+ npx audit-ci --moderate
74
+
75
+ # Python
76
+ pip-audit
77
+ safety check
78
+ ```
79
+
80
+ Flag: outdated packages with known CVEs, unmaintained dependencies, packages with suspiciously few downloads or recent ownership transfers.
81
+
82
+ ## Secrets Scan
83
+
84
+ Look for accidentally committed secrets:
85
+ - API keys, tokens, passwords in source files
86
+ - `.env` files not in `.gitignore`
87
+ - Hardcoded connection strings
88
+ - Private keys or certificates
89
+
90
+ ## Input Validation Rules
91
+
92
+ - Validate at system boundaries (API endpoints, form handlers, file uploads)
93
+ - Reject unexpected input types and sizes
94
+ - Whitelist over blacklist
95
+ - Validate on the server, never trust client-only validation
96
+
97
+ ## When Reporting Issues
98
+
99
+ Classify findings:
100
+ - **Critical**: Exploitable now, data at risk (injection, auth bypass, exposed secrets)
101
+ - **High**: Exploitable with effort (XSS, CSRF, broken access control)
102
+ - **Medium**: Defense-in-depth gap (missing headers, weak config)
103
+ - **Low**: Best practice deviation (could become a problem later)
104
+
105
+ Always provide the fix, not just the finding.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * scheduler-autofix-select.test.cjs — unit tests for selectAutoFixTargets.
3
+ *
4
+ * Run: timeout 120 node --test src/main/__tests__/scheduler-autofix-select.test.cjs
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { test } = require('node:test');
10
+ const assert = require('node:assert/strict');
11
+ const { selectAutoFixTargets } = require('../scheduler.cjs');
12
+
13
+ const noSiblingOnDisk = () => false;
14
+
15
+ function makeJob(overrides = {}) {
16
+ return {
17
+ slug: '05-my-feature',
18
+ status: 'needs_review',
19
+ runId: '2026-06-16T10-00-00-000Z',
20
+ parallelGroup: 5,
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ test('selects a fresh needs_review job', () => {
26
+ const jobs = [makeJob()];
27
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
28
+ assert.strictEqual(result.length, 1);
29
+ assert.strictEqual(result[0].slug, '05-my-feature');
30
+ });
31
+
32
+ test('excludes job with autoFixAttempted: true', () => {
33
+ const jobs = [makeJob({ autoFixAttempted: true })];
34
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
35
+ assert.strictEqual(result.length, 0);
36
+ });
37
+
38
+ test('excludes a fix-plan slug (05-fix-foo)', () => {
39
+ const jobs = [makeJob({ slug: '05-fix-foo' })];
40
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
41
+ assert.strictEqual(result.length, 0);
42
+ });
43
+
44
+ test('excludes a failed job', () => {
45
+ const jobs = [makeJob({ status: 'failed' })];
46
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
47
+ assert.strictEqual(result.length, 0);
48
+ });
49
+
50
+ test('excludes a completed job', () => {
51
+ const jobs = [makeJob({ status: 'completed' })];
52
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
53
+ assert.strictEqual(result.length, 0);
54
+ });
55
+
56
+ test('excludes a job missing runId', () => {
57
+ const jobs = [makeJob({ runId: null })];
58
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
59
+ assert.strictEqual(result.length, 0);
60
+ });
61
+
62
+ test('excludes when fix sibling exists on disk', () => {
63
+ const jobs = [makeJob()];
64
+ // fixSlug = '05-fix-my-feature'
65
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: (s) => s === '05-fix-my-feature' });
66
+ assert.strictEqual(result.length, 0);
67
+ });
68
+
69
+ test('excludes when fix sibling already in the queue', () => {
70
+ const sibling = { slug: '05-fix-my-feature', status: 'pending', runId: null };
71
+ const jobs = [makeJob(), sibling];
72
+ const result = selectAutoFixTargets(jobs, { fixSlugExists: noSiblingOnDisk });
73
+ assert.strictEqual(result.length, 0);
74
+ });
75
+
76
+ test('fixSlug uses padded parallelGroup and strips leading digits from slug', () => {
77
+ const jobs = [makeJob({ slug: '07-some-task', parallelGroup: 7 })];
78
+ // Expected fixSlug: '07-fix-some-task'
79
+ const seen = [];
80
+ selectAutoFixTargets(jobs, {
81
+ fixSlugExists: (s) => { seen.push(s); return false; },
82
+ });
83
+ assert.strictEqual(seen[0], '07-fix-some-task');
84
+ });
85
+
86
+ test('defaults parallelGroup to 99 when absent', () => {
87
+ const jobs = [makeJob({ slug: '05-my-feature', parallelGroup: undefined })];
88
+ const seen = [];
89
+ selectAutoFixTargets(jobs, {
90
+ fixSlugExists: (s) => { seen.push(s); return false; },
91
+ });
92
+ assert.strictEqual(seen[0], '99-fix-my-feature');
93
+ });
94
+
95
+ console.log('scheduler-autofix-select tests: PASS');
@@ -0,0 +1,33 @@
1
+ /**
2
+ * scheduler-autopromote.test.cjs — unit tests for isPromotableOriginal.
3
+ *
4
+ * Run: timeout 120 node --test src/main/__tests__/scheduler-autopromote.test.cjs
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { test } = require('node:test');
10
+ const assert = require('node:assert/strict');
11
+ const { isPromotableOriginal } = require('../scheduler.cjs');
12
+
13
+ test('isPromotableOriginal: failed → true', () => {
14
+ assert.strictEqual(isPromotableOriginal('failed'), true);
15
+ });
16
+
17
+ test('isPromotableOriginal: needs_review → true', () => {
18
+ assert.strictEqual(isPromotableOriginal('needs_review'), true);
19
+ });
20
+
21
+ test('isPromotableOriginal: completed → false', () => {
22
+ assert.strictEqual(isPromotableOriginal('completed'), false);
23
+ });
24
+
25
+ test('isPromotableOriginal: running → false', () => {
26
+ assert.strictEqual(isPromotableOriginal('running'), false);
27
+ });
28
+
29
+ test('isPromotableOriginal: pending → false', () => {
30
+ assert.strictEqual(isPromotableOriginal('pending'), false);
31
+ });
32
+
33
+ console.log('scheduler-autopromote tests: PASS');
@@ -1,29 +1,28 @@
1
1
  /**
2
- * hives.cjs — pre-baked subagent swarm templates ("Hives").
2
+ * hives.cjs — subagent recipe templates ("Hives").
3
3
  *
4
- * A Hive is a named collection of subagent roles + an optional default plan,
5
- * launchable as one unit. Concept ported from ClaudeCodeUnleashed; our shape
6
- * is `{ slug, name, description, roles: [{ label, prompt }], defaultPlan? }`
7
- * (renderer launches by configuring Orchestrator with those roles).
4
+ * A Recipe is a named sequence of steps, each referencing an agent by name
5
+ * from `~/.claude/agents/<agentName>.md`. Shape:
6
+ * { slug, name, description, brief?, steps: [{ agentName, note? }] }
7
+ *
8
+ * Legacy hives (old shape: `roles: [{ label, prompt }]`) are migrated on
9
+ * first read: each role is materialized as an agent .md and replaced by a
10
+ * step referencing it. Migration is idempotent and permanent (re-saved).
8
11
  *
9
12
  * Storage: `~/.claude/session-manager/hives/<slug>.json`
10
13
  * - slug must match SLUG_RE: /^[a-z0-9-_]{1,64}$/
11
- * - up to 32 roles per hive
14
+ * - up to 32 steps per recipe
12
15
  * - per-field byte caps mirrored in the inline zod schemas below
13
16
  *
14
- * IPC namespace:
15
- * - hives:list -> { hives: Hive[], error: string | null }
16
- * - hives:get -> { hive: Hive | null, error: string | null }
17
+ * IPC namespace (channel names kept for backwards compat):
18
+ * - hives:list -> { hives: Recipe[], error: string | null }
19
+ * - hives:get -> { hive: Recipe | null, error: string | null }
17
20
  * - hives:save -> { ok: boolean, error: string | null }
18
21
  * - hives:delete -> { ok: boolean, error: string | null }
19
22
  *
20
- * All mutations go through config.cjs::writeJson (atomic tmp+rename) and
21
- * config.cjs::validatePath (allowedRoots = home dir). Never raw fs.writeFile.
22
- *
23
- * Default hives (Code review / Build feature / Bug hunt) ship in the renderer
24
- * (src/renderer/lib/defaultHives.ts) and are NOT writable to disk — they exist
25
- * only as in-memory starter examples so a fresh install has content. The IPC
26
- * layer only sees user-saved hives.
23
+ * All mutations go through config.cjs::writeJson / writeTextAtomic (atomic
24
+ * tmp+rename) and config.cjs::validatePath (allowedRoots = home dir).
25
+ * Never raw fs.writeFile.
27
26
  */
28
27
 
29
28
  'use strict';
@@ -37,34 +36,35 @@ const config = require('./config.cjs');
37
36
 
38
37
  // ──────────────────────────────────────────── caps
39
38
  const SLUG_RE = /^[a-z0-9-_]{1,64}$/;
39
+ const AGENT_NAME_RE = /^[a-z0-9-_]{1,64}$/;
40
40
  const MAX_NAME_LEN = 128;
41
41
  const MAX_DESC_LEN = 2048;
42
- const MAX_LABEL_LEN = 128;
43
- const MAX_PROMPT_LEN = 16 * 1024;
44
- const MAX_PLAN_LEN = 8 * 1024;
45
- const MAX_ROLES = 32;
42
+ const MAX_BRIEF_LEN = 8 * 1024;
43
+ const MAX_NOTE_LEN = 2048;
44
+ const MAX_STEPS = 32;
46
45
 
47
46
  // ──────────────────────────────────────────── inline zod schemas
48
- const hiveRoleSchema = z.object({
49
- label: z.string().min(1).max(MAX_LABEL_LEN),
50
- prompt: z.string().min(1).max(MAX_PROMPT_LEN),
47
+ const recipeStepSchema = z.object({
48
+ agentName: z.string().regex(AGENT_NAME_RE),
49
+ note: z.string().max(MAX_NOTE_LEN).optional(),
51
50
  }).strict();
52
51
 
53
- const hiveSchema = z.object({
52
+ const recipeSchema = z.object({
54
53
  slug: z.string().regex(SLUG_RE),
55
54
  name: z.string().min(1).max(MAX_NAME_LEN),
56
55
  description: z.string().max(MAX_DESC_LEN).default(''),
57
- roles: z.array(hiveRoleSchema).min(1).max(MAX_ROLES),
58
- defaultPlan: z.string().max(MAX_PLAN_LEN).optional(),
56
+ brief: z.string().max(MAX_BRIEF_LEN).optional(),
57
+ steps: z.array(recipeStepSchema).min(1).max(MAX_STEPS),
59
58
  }).strict();
60
59
 
61
60
  const slugPayload = z.object({
62
61
  slug: z.string().regex(SLUG_RE),
63
62
  }).strict();
64
63
 
64
+ // Field name "hive" kept for IPC channel backwards compatibility.
65
65
  const savePayload = z.object({
66
66
  slug: z.string().regex(SLUG_RE),
67
- hive: hiveSchema,
67
+ hive: recipeSchema,
68
68
  }).strict();
69
69
 
70
70
  // ──────────────────────────────────────────── paths
@@ -72,6 +72,10 @@ function rootDir() {
72
72
  return path.join(os.homedir(), '.claude', 'session-manager', 'hives');
73
73
  }
74
74
 
75
+ function agentsDir() {
76
+ return path.join(os.homedir(), '.claude', 'agents');
77
+ }
78
+
75
79
  function hivePath(slug) {
76
80
  if (!SLUG_RE.test(slug)) {
77
81
  throw new Error(`invalid hive slug (must match ${SLUG_RE.source})`);
@@ -83,6 +87,58 @@ async function ensureRoot() {
83
87
  await fsp.mkdir(rootDir(), { recursive: true });
84
88
  }
85
89
 
90
+ // ──────────────────────────────────────────── helpers
91
+ function kebabCase(s) {
92
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64) || 'agent';
93
+ }
94
+
95
+ // ──────────────────────────────────────────── legacy migration
96
+ async function migrateLegacyHive(slug, raw) {
97
+ const adir = agentsDir();
98
+ await fsp.mkdir(adir, { recursive: true });
99
+
100
+ const usedNames = new Set();
101
+ const steps = [];
102
+
103
+ for (const role of (raw.roles ?? [])) {
104
+ let base = kebabCase(role.label ?? 'agent');
105
+ let agentName = base;
106
+ let suffix = 2;
107
+ while (usedNames.has(agentName)) {
108
+ agentName = `${base.slice(0, 62)}-${suffix++}`;
109
+ }
110
+ usedNames.add(agentName);
111
+
112
+ const agentFilePath = path.join(adir, `${agentName}.md`);
113
+ // validatePath ensures we stay within allowedRoots (home dir).
114
+ const validPath = config.validatePath(agentFilePath);
115
+
116
+ let exists = false;
117
+ try { await fsp.stat(validPath); exists = true; } catch { /* ENOENT = not yet created */ }
118
+
119
+ if (!exists) {
120
+ const descLine = (role.prompt ?? '').replace(/[\r\n]+/g, ' ').slice(0, 200);
121
+ const content = `---\nname: ${role.label}\ndescription: ${descLine}\n---\n\n${role.prompt ?? ''}\n`;
122
+ await config.writeTextAtomic(validPath, content);
123
+ }
124
+
125
+ steps.push({ agentName });
126
+ }
127
+
128
+ const recipe = {
129
+ slug,
130
+ name: raw.name,
131
+ description: raw.description ?? '',
132
+ steps,
133
+ ...(raw.defaultPlan ? { brief: raw.defaultPlan } : {}),
134
+ };
135
+
136
+ // Persist migrated shape permanently so subsequent reads skip migration.
137
+ await saveHive(slug, recipe);
138
+
139
+ return { hive: recipe, error: null };
140
+ }
141
+
86
142
  // ──────────────────────────────────────────── core ops
87
143
  async function listHives() {
88
144
  try {
@@ -118,13 +174,20 @@ async function readHive(slug) {
118
174
  const r = await config.readJson(abs);
119
175
  if (!r || !r.exists) return { hive: null, error: null };
120
176
  if (r.parseError) return { hive: null, error: `parse error: ${r.parseError}` };
177
+
178
+ const raw = r.data;
179
+
180
+ // Legacy migration: old shape has `roles` array instead of `steps`.
181
+ if (raw && Array.isArray(raw.roles) && !Array.isArray(raw.steps)) {
182
+ return migrateLegacyHive(slug, raw);
183
+ }
184
+
121
185
  // Re-validate stored shape so a hand-edited file can't smuggle in bad data.
122
- const parsed = hiveSchema.safeParse(r.data);
186
+ const parsed = recipeSchema.safeParse(raw);
123
187
  if (!parsed.success) {
124
- return { hive: null, error: `invalid hive on disk: ${parsed.error.issues[0]?.message ?? 'unknown'}` };
188
+ return { hive: null, error: `invalid recipe on disk: ${parsed.error.issues[0]?.message ?? 'unknown'}` };
125
189
  }
126
- // Trust the file's own slug only if it matches the filename; otherwise force
127
- // them to agree (filename wins — that's the storage key).
190
+ // Trust the file's own slug only if it matches the filename; filename wins.
128
191
  const hive = { ...parsed.data, slug };
129
192
  return { hive, error: null };
130
193
  }
@@ -139,8 +202,7 @@ async function getHive(slug) {
139
202
 
140
203
  async function saveHive(slug, hive) {
141
204
  try {
142
- // Body slug must match path slug. Reject mismatches loudly so the renderer
143
- // can't accidentally overwrite the wrong file by tampering with `slug`.
205
+ // Body slug must match path slug.
144
206
  if (hive.slug !== slug) {
145
207
  return { ok: false, error: `slug mismatch: payload slug "${hive.slug}" != path "${slug}"` };
146
208
  }
@@ -148,10 +210,9 @@ async function saveHive(slug, hive) {
148
210
  const abs = hivePath(slug);
149
211
  const out = {
150
212
  ...hive,
151
- // Strip undefined for clean JSON on disk.
152
213
  description: hive.description ?? '',
153
214
  };
154
- if (out.defaultPlan === undefined || out.defaultPlan === '') delete out.defaultPlan;
215
+ if (out.brief === undefined || out.brief === '') delete out.brief;
155
216
  const result = await config.writeJson(abs, out);
156
217
  if (!result || !result.ok) {
157
218
  return { ok: false, error: result?.error ?? 'write failed' };
@@ -178,7 +239,6 @@ async function deleteHive(slug) {
178
239
  await fsp.unlink(real);
179
240
  } catch (e) {
180
241
  if (e.code === 'ENOENT') {
181
- // Treat missing as success — idempotent delete, same as fs unlink ENOENT.
182
242
  return { ok: true, error: null };
183
243
  }
184
244
  return { ok: false, error: e.message };