@automagik/genie 4.260324.9 → 4.260324.10

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260324.9",
13
+ "version": "4.260324.10",
14
14
  "source": "./plugins/genie",
15
15
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
16
16
  }
package/dist/genie.js CHANGED
@@ -839,7 +839,7 @@ ${gitLog}`:"","","Pick up where you left off. Read the wish file for full contex
839
839
  WHERE actor_type = ${actor.actorType}
840
840
  AND actor_id = ${actor.actorId}
841
841
  AND channel = ${channel}
842
- `).count>0}async function listTasksForActor(actor,filters={}){let sql=await getConnection(),repo=filters.repoPath??getRepoPath(),conditions=["t.repo_path = $1"],values2=[repo],paramIdx=2;if(conditions.push(`ta.actor_type = $${paramIdx++}`),values2.push(actor.actorType),conditions.push(`ta.actor_id = $${paramIdx++}`),values2.push(actor.actorId),filters.stage)conditions.push(`t.stage = $${paramIdx++}`),values2.push(filters.stage);if(filters.status)conditions.push(`t.status = $${paramIdx++}`),values2.push(filters.status);if(filters.priority)conditions.push(`t.priority = $${paramIdx++}`),values2.push(filters.priority);let limit=filters.limit??100,offset=filters.offset??0;values2.push(limit,offset);let query=`SELECT DISTINCT t.* FROM tasks t JOIN task_actors ta ON ta.task_id = t.id WHERE ${conditions.join(" AND ")} ORDER BY t.created_at DESC LIMIT $${paramIdx++} OFFSET $${paramIdx++}`;return(await sql.unsafe(query,values2)).map(mapTask)}var init_task_service=__esm(()=>{init_db()});import{writeFileSync as writeFileSync7}from"fs";import{join as join33}from"path";function scaffoldAgentFiles(targetDir){writeFileSync7(join33(targetDir,"SOUL.md"),SOUL_TEMPLATE),writeFileSync7(join33(targetDir,"HEARTBEAT.md"),HEARTBEAT_TEMPLATE),writeFileSync7(join33(targetDir,"AGENTS.md"),AGENTS_TEMPLATE)}var SOUL_TEMPLATE=`# Soul
842
+ `).count>0}async function listTasksForActor(actor,filters={}){let sql=await getConnection(),conditions=[],values2=[],paramIdx=1;if(filters.allProjects);else if(filters.projectName)conditions.push(`t.project_id = (SELECT id FROM projects WHERE name = $${paramIdx++})`),values2.push(filters.projectName);else conditions.push(`t.repo_path = $${paramIdx++}`),values2.push(filters.repoPath??getRepoPath());if(conditions.push(`ta.actor_type = $${paramIdx++}`),values2.push(actor.actorType),conditions.push(`ta.actor_id = $${paramIdx++}`),values2.push(actor.actorId),filters.stage)conditions.push(`t.stage = $${paramIdx++}`),values2.push(filters.stage);if(filters.status)conditions.push(`t.status = $${paramIdx++}`),values2.push(filters.status);if(filters.priority)conditions.push(`t.priority = $${paramIdx++}`),values2.push(filters.priority);let limit=filters.limit??100,offset=filters.offset??0;values2.push(limit,offset);let query=`SELECT DISTINCT t.* FROM tasks t JOIN task_actors ta ON ta.task_id = t.id WHERE ${conditions.join(" AND ")} ORDER BY t.created_at DESC LIMIT $${paramIdx++} OFFSET $${paramIdx++}`;return(await sql.unsafe(query,values2)).map(mapTask)}var init_task_service=__esm(()=>{init_db()});import{writeFileSync as writeFileSync7}from"fs";import{join as join33}from"path";function scaffoldAgentFiles(targetDir){writeFileSync7(join33(targetDir,"SOUL.md"),SOUL_TEMPLATE),writeFileSync7(join33(targetDir,"HEARTBEAT.md"),HEARTBEAT_TEMPLATE),writeFileSync7(join33(targetDir,"AGENTS.md"),AGENTS_TEMPLATE)}var SOUL_TEMPLATE=`# Soul
843
843
 
844
844
  You are an AI assistant. Define your role, personality, and approach here.
845
845
 
@@ -2,7 +2,7 @@
2
2
  "id": "genie",
3
3
  "name": "Genie",
4
4
  "description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
5
- "version": "4.260324.9",
5
+ "version": "4.260324.10",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260324.9",
3
+ "version": "4.260324.10",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260324.9",
3
+ "version": "4.260324.10",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260324.9",
3
+ "version": "4.260324.10",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -1020,4 +1020,82 @@ describe('Projects', () => {
1020
1020
  expect(t.projectId).toBe(proj!.id);
1021
1021
  }
1022
1022
  });
1023
+
1024
+ it('should round-trip: create virtual project → create task with explicit projectId → list by projectName', async () => {
1025
+ const projName = `test-virtual-roundtrip-${Date.now()}`;
1026
+ const taskRepo = `${REPO}-roundtrip`;
1027
+
1028
+ // 1. Create virtual project (no repoPath) — mimics handleTaskCreate --project flow
1029
+ const project = await createProject({ name: projName });
1030
+ expect(project.id).toMatch(/^proj-/);
1031
+ expect(project.repoPath).toBeNull();
1032
+
1033
+ // 2. Create task with explicit projectId — mimics createTask(input, repoPath, projectId)
1034
+ const task = await createTask({ title: 'Round-trip test task' }, taskRepo, project.id);
1035
+ expect(task.projectId).toBe(project.id);
1036
+ expect(task.repoPath).toBe(taskRepo);
1037
+
1038
+ // 3. List by projectName — mimics genie task list --project <name>
1039
+ const tasks = await listTasks({ projectName: projName });
1040
+ expect(tasks.length).toBeGreaterThanOrEqual(1);
1041
+ const found = tasks.find((t) => t.id === task.id);
1042
+ expect(found).not.toBeUndefined();
1043
+ expect(found!.projectId).toBe(project.id);
1044
+
1045
+ // 4. Verify getProjectByName returns same project
1046
+ const lookedUp = await getProjectByName(projName);
1047
+ expect(lookedUp).not.toBeNull();
1048
+ expect(lookedUp!.id).toBe(project.id);
1049
+
1050
+ // Cleanup
1051
+ await sql`DELETE FROM tasks WHERE repo_path = ${taskRepo}`;
1052
+ await sql`DELETE FROM projects WHERE name = ${projName}`;
1053
+ });
1054
+
1055
+ it('should round-trip with listTasksForActor and --project filter', async () => {
1056
+ const projName = `test-virtual-actor-${Date.now()}`;
1057
+ const taskRepo = `${REPO}-actor-proj`;
1058
+
1059
+ // Create virtual project and task
1060
+ const project = await createProject({ name: projName });
1061
+ const task = await createTask({ title: 'Actor project task' }, taskRepo, project.id);
1062
+ await assignTask(task.id, actor, 'assignee', {}, taskRepo);
1063
+
1064
+ // List via listTasksForActor with projectName filter
1065
+ const tasks = await listTasksForActor(actor, { projectName: projName });
1066
+ expect(tasks.length).toBeGreaterThanOrEqual(1);
1067
+ const found = tasks.find((t) => t.id === task.id);
1068
+ expect(found).not.toBeUndefined();
1069
+ expect(found!.projectId).toBe(project.id);
1070
+
1071
+ // Cleanup
1072
+ await sql`DELETE FROM task_actors WHERE task_id = ${task.id}`;
1073
+ await sql`DELETE FROM tasks WHERE repo_path = ${taskRepo}`;
1074
+ await sql`DELETE FROM projects WHERE name = ${projName}`;
1075
+ });
1076
+
1077
+ it('should find auto-created projects by name in subsequent commands', async () => {
1078
+ const autoRepo = `${REPO}-auto-find`;
1079
+
1080
+ // ensureProject auto-creates with basename
1081
+ const projId = await ensureProject(autoRepo);
1082
+
1083
+ // The auto-created project should be findable by its name (basename of path)
1084
+ const parts = autoRepo.split('/');
1085
+ const expectedName = parts[parts.length - 1];
1086
+ const found = await getProjectByName(expectedName);
1087
+ expect(found).not.toBeNull();
1088
+ expect(found!.id).toBe(projId);
1089
+
1090
+ // Create task and list by project name
1091
+ const task = await createTask({ title: 'Auto-find task' }, autoRepo);
1092
+ expect(task.projectId).toBe(projId);
1093
+
1094
+ const tasks = await listTasks({ projectName: expectedName });
1095
+ expect(tasks.some((t) => t.id === task.id)).toBe(true);
1096
+
1097
+ // Cleanup
1098
+ await sql`DELETE FROM tasks WHERE repo_path = ${autoRepo}`;
1099
+ await sql`DELETE FROM projects WHERE repo_path = ${autoRepo}`;
1100
+ });
1023
1101
  });
@@ -1489,11 +1489,21 @@ export async function deletePreference(actor: Actor, channel: string): Promise<b
1489
1489
  /** List tasks assigned to a specific actor. */
1490
1490
  export async function listTasksForActor(actor: Actor, filters: TaskFilters = {}): Promise<TaskRow[]> {
1491
1491
  const sql = await getConnection();
1492
- const repo = filters.repoPath ?? getRepoPath();
1493
1492
 
1494
- const conditions: string[] = ['t.repo_path = $1'];
1495
- const values: unknown[] = [repo];
1496
- let paramIdx = 2;
1493
+ const conditions: string[] = [];
1494
+ const values: unknown[] = [];
1495
+ let paramIdx = 1;
1496
+
1497
+ // Scope conditions (project/repo/all) — mirrors buildScopeConditions with t. prefix
1498
+ if (filters.allProjects) {
1499
+ // No repo scoping — show all projects
1500
+ } else if (filters.projectName) {
1501
+ conditions.push(`t.project_id = (SELECT id FROM projects WHERE name = $${paramIdx++})`);
1502
+ values.push(filters.projectName);
1503
+ } else {
1504
+ conditions.push(`t.repo_path = $${paramIdx++}`);
1505
+ values.push(filters.repoPath ?? getRepoPath());
1506
+ }
1497
1507
 
1498
1508
  conditions.push(`ta.actor_type = $${paramIdx++}`);
1499
1509
  values.push(actor.actorType);