gru-ai 0.1.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 (143) hide show
  1. package/.claude/skills/brainstorm/SKILL.md +340 -0
  2. package/.claude/skills/code-review-excellence/SKILL.md +198 -0
  3. package/.claude/skills/directive/SKILL.md +121 -0
  4. package/.claude/skills/directive/docs/pipeline/00-delegation-and-triage.md +181 -0
  5. package/.claude/skills/directive/docs/pipeline/01-checkpoint.md +34 -0
  6. package/.claude/skills/directive/docs/pipeline/02-read-directive.md +38 -0
  7. package/.claude/skills/directive/docs/pipeline/03-read-context.md +15 -0
  8. package/.claude/skills/directive/docs/pipeline/04-challenge.md +38 -0
  9. package/.claude/skills/directive/docs/pipeline/05-planning.md +64 -0
  10. package/.claude/skills/directive/docs/pipeline/06-technical-audit.md +88 -0
  11. package/.claude/skills/directive/docs/pipeline/07-plan-approval.md +145 -0
  12. package/.claude/skills/directive/docs/pipeline/07b-project-brainstorm.md +85 -0
  13. package/.claude/skills/directive/docs/pipeline/08-worktree-and-state.md +50 -0
  14. package/.claude/skills/directive/docs/pipeline/09-execute-projects.md +709 -0
  15. package/.claude/skills/directive/docs/pipeline/10-wrapup.md +242 -0
  16. package/.claude/skills/directive/docs/pipeline/11-completion-gate.md +75 -0
  17. package/.claude/skills/directive/docs/reference/rules/casting-rules.md +78 -0
  18. package/.claude/skills/directive/docs/reference/rules/failure-handling.md +20 -0
  19. package/.claude/skills/directive/docs/reference/rules/phase-definitions.md +42 -0
  20. package/.claude/skills/directive/docs/reference/rules/scope-and-dod.md +30 -0
  21. package/.claude/skills/directive/docs/reference/schemas/audit-output.md +44 -0
  22. package/.claude/skills/directive/docs/reference/schemas/brainstorm-output.md +52 -0
  23. package/.claude/skills/directive/docs/reference/schemas/challenger-output.md +13 -0
  24. package/.claude/skills/directive/docs/reference/schemas/checkpoint.md +18 -0
  25. package/.claude/skills/directive/docs/reference/schemas/current-json.md +5 -0
  26. package/.claude/skills/directive/docs/reference/schemas/directive-json.md +143 -0
  27. package/.claude/skills/directive/docs/reference/schemas/investigation-output.md +37 -0
  28. package/.claude/skills/directive/docs/reference/schemas/plan-schema.md +103 -0
  29. package/.claude/skills/directive/docs/reference/templates/architect-prompt.md +66 -0
  30. package/.claude/skills/directive/docs/reference/templates/auditor-prompt.md +53 -0
  31. package/.claude/skills/directive/docs/reference/templates/brainstorm-prompt.md +68 -0
  32. package/.claude/skills/directive/docs/reference/templates/challenger-prompt.md +35 -0
  33. package/.claude/skills/directive/docs/reference/templates/digest.md +134 -0
  34. package/.claude/skills/directive/docs/reference/templates/investigator-prompt.md +51 -0
  35. package/.claude/skills/directive/docs/reference/templates/planner-prompt.md +130 -0
  36. package/.claude/skills/frontend-design/SKILL.md +42 -0
  37. package/.claude/skills/gruai-agents/SKILL.md +161 -0
  38. package/.claude/skills/gruai-config/SKILL.md +61 -0
  39. package/.claude/skills/healthcheck/SKILL.md +216 -0
  40. package/.claude/skills/report/SKILL.md +380 -0
  41. package/.claude/skills/scout/SKILL.md +452 -0
  42. package/.claude/skills/seo-audit/SKILL.md +107 -0
  43. package/.claude/skills/walkthrough/SKILL.md +274 -0
  44. package/.claude/skills/webapp-testing/SKILL.md +96 -0
  45. package/LICENSE +21 -0
  46. package/README.md +206 -0
  47. package/cli/templates/CLAUDE.md.template +57 -0
  48. package/cli/templates/agent-roles/backend.md +47 -0
  49. package/cli/templates/agent-roles/cmo.md +52 -0
  50. package/cli/templates/agent-roles/content.md +48 -0
  51. package/cli/templates/agent-roles/coo.md +66 -0
  52. package/cli/templates/agent-roles/cpo.md +52 -0
  53. package/cli/templates/agent-roles/cto.md +63 -0
  54. package/cli/templates/agent-roles/data.md +46 -0
  55. package/cli/templates/agent-roles/design.md +46 -0
  56. package/cli/templates/agent-roles/frontend.md +47 -0
  57. package/cli/templates/agent-roles/fullstack.md +47 -0
  58. package/cli/templates/agent-roles/qa.md +46 -0
  59. package/cli/templates/backlog.json.template +3 -0
  60. package/cli/templates/directive.json.template +9 -0
  61. package/cli/templates/directive.md.template +23 -0
  62. package/cli/templates/goals-index.md +21 -0
  63. package/cli/templates/gruai.config.json.template +12 -0
  64. package/cli/templates/lessons.md +16 -0
  65. package/cli/templates/vision.md +35 -0
  66. package/cli/templates/welcome-directive/directive.json +9 -0
  67. package/cli/templates/welcome-directive/directive.md +53 -0
  68. package/dist/assets/GamePage-C5XQQOQH.js +49 -0
  69. package/dist/assets/README.md +17 -0
  70. package/dist/assets/characters/char_0.png +0 -0
  71. package/dist/assets/characters/char_1.png +0 -0
  72. package/dist/assets/characters/char_10.png +0 -0
  73. package/dist/assets/characters/char_11.png +0 -0
  74. package/dist/assets/characters/char_2.png +0 -0
  75. package/dist/assets/characters/char_3.png +0 -0
  76. package/dist/assets/characters/char_4.png +0 -0
  77. package/dist/assets/characters/char_5.png +0 -0
  78. package/dist/assets/characters/char_6.png +0 -0
  79. package/dist/assets/characters/char_7.png +0 -0
  80. package/dist/assets/characters/char_8.png +0 -0
  81. package/dist/assets/characters/char_9.png +0 -0
  82. package/dist/assets/index-CnTPDqpP.js +12 -0
  83. package/dist/assets/index-gR5q7ikB.css +1 -0
  84. package/dist/assets/office/furniture.png +0 -0
  85. package/dist/assets/office/room-builder.png +0 -0
  86. package/dist/index.html +16 -0
  87. package/dist-server/scripts/intelligence-trends.d.ts +100 -0
  88. package/dist-server/scripts/intelligence-trends.js +365 -0
  89. package/dist-server/server/actions/cleanup.d.ts +4 -0
  90. package/dist-server/server/actions/cleanup.js +30 -0
  91. package/dist-server/server/actions/send-input.d.ts +6 -0
  92. package/dist-server/server/actions/send-input.js +147 -0
  93. package/dist-server/server/actions/terminal.d.ts +4 -0
  94. package/dist-server/server/actions/terminal.js +427 -0
  95. package/dist-server/server/config.d.ts +9 -0
  96. package/dist-server/server/config.js +217 -0
  97. package/dist-server/server/db.d.ts +7 -0
  98. package/dist-server/server/db.js +79 -0
  99. package/dist-server/server/hooks/event-receiver.d.ts +11 -0
  100. package/dist-server/server/hooks/event-receiver.js +36 -0
  101. package/dist-server/server/index.d.ts +1 -0
  102. package/dist-server/server/index.js +552 -0
  103. package/dist-server/server/notifications/macos.d.ts +5 -0
  104. package/dist-server/server/notifications/macos.js +22 -0
  105. package/dist-server/server/notifications/notifier.d.ts +17 -0
  106. package/dist-server/server/notifications/notifier.js +110 -0
  107. package/dist-server/server/parsers/process-discovery.d.ts +39 -0
  108. package/dist-server/server/parsers/process-discovery.js +776 -0
  109. package/dist-server/server/parsers/session-scanner.d.ts +56 -0
  110. package/dist-server/server/parsers/session-scanner.js +390 -0
  111. package/dist-server/server/parsers/session-state.d.ts +68 -0
  112. package/dist-server/server/parsers/session-state.js +696 -0
  113. package/dist-server/server/parsers/session-state.test.d.ts +1 -0
  114. package/dist-server/server/parsers/session-state.test.js +950 -0
  115. package/dist-server/server/parsers/task-parser.d.ts +10 -0
  116. package/dist-server/server/parsers/task-parser.js +97 -0
  117. package/dist-server/server/parsers/team-parser.d.ts +3 -0
  118. package/dist-server/server/parsers/team-parser.js +67 -0
  119. package/dist-server/server/platform/__tests__/claude-code.test.d.ts +1 -0
  120. package/dist-server/server/platform/__tests__/claude-code.test.js +311 -0
  121. package/dist-server/server/platform/claude-code.d.ts +34 -0
  122. package/dist-server/server/platform/claude-code.js +94 -0
  123. package/dist-server/server/platform/index.d.ts +5 -0
  124. package/dist-server/server/platform/index.js +1 -0
  125. package/dist-server/server/platform/types.d.ts +190 -0
  126. package/dist-server/server/platform/types.js +9 -0
  127. package/dist-server/server/state/aggregator.d.ts +42 -0
  128. package/dist-server/server/state/aggregator.js +1080 -0
  129. package/dist-server/server/state/work-item-types.d.ts +555 -0
  130. package/dist-server/server/state/work-item-types.js +168 -0
  131. package/dist-server/server/types.d.ts +237 -0
  132. package/dist-server/server/types.js +1 -0
  133. package/dist-server/server/watchers/claude-watcher.d.ts +17 -0
  134. package/dist-server/server/watchers/claude-watcher.js +130 -0
  135. package/dist-server/server/watchers/context-watcher.d.ts +22 -0
  136. package/dist-server/server/watchers/context-watcher.js +125 -0
  137. package/dist-server/server/watchers/directive-watcher.d.ts +46 -0
  138. package/dist-server/server/watchers/directive-watcher.js +497 -0
  139. package/dist-server/server/watchers/session-watcher.d.ts +18 -0
  140. package/dist-server/server/watchers/session-watcher.js +126 -0
  141. package/dist-server/server/watchers/state-watcher.d.ts +36 -0
  142. package/dist-server/server/watchers/state-watcher.js +369 -0
  143. package/package.json +68 -0
@@ -0,0 +1,217 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ const CONFIG_DIR = path.join(os.homedir(), '.conductor');
5
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
6
+ function resolveHome(filePath) {
7
+ if (filePath.startsWith('~/') || filePath === '~') {
8
+ return path.join(os.homedir(), filePath.slice(1));
9
+ }
10
+ return filePath;
11
+ }
12
+ /**
13
+ * Decode a ~/.claude/projects/ directory name back to a real filesystem path.
14
+ *
15
+ * Directory names encode paths by replacing `/` with `-`, e.g.:
16
+ * `-Users-yangyang-Repos-gruai` -> `/Users/yangyang/Repos/gruai`
17
+ *
18
+ * The challenge: path components can contain hyphens (e.g. `gruai`).
19
+ * We solve this by trying all possible hyphen->slash splits and checking which
20
+ * decoded path actually exists on disk.
21
+ */
22
+ function decodeProjectDirName(dirName) {
23
+ // Must start with `-` (represents leading `/`)
24
+ if (!dirName.startsWith('-'))
25
+ return null;
26
+ const stripped = dirName.slice(1); // remove leading `-`
27
+ const parts = stripped.split('-');
28
+ // Use dynamic programming / recursive backtracking to find valid path
29
+ // Try combining adjacent parts with hyphens to form directory names
30
+ const result = findValidPath(parts, 0, '/');
31
+ return result;
32
+ }
33
+ /**
34
+ * Recursively try combining parts to find a path that exists on disk.
35
+ * parts: array of hyphen-split segments
36
+ * index: current position in parts
37
+ * currentPath: path built so far
38
+ */
39
+ function findValidPath(parts, index, currentPath) {
40
+ if (index >= parts.length) {
41
+ // We've consumed all parts. Check if the full path exists
42
+ return fs.existsSync(currentPath) ? currentPath : null;
43
+ }
44
+ // Try progressively longer component names (joining with hyphens)
45
+ for (let end = index + 1; end <= parts.length; end++) {
46
+ const component = parts.slice(index, end).join('-');
47
+ const candidatePath = path.join(currentPath, component);
48
+ // For intermediate components, check if this directory exists before recursing
49
+ if (end < parts.length) {
50
+ if (fs.existsSync(candidatePath)) {
51
+ const result = findValidPath(parts, end, candidatePath);
52
+ if (result)
53
+ return result;
54
+ }
55
+ }
56
+ else {
57
+ // Last component: check if the full path exists
58
+ if (fs.existsSync(candidatePath)) {
59
+ return candidatePath;
60
+ }
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Derive a human-friendly project name from a filesystem path.
67
+ * Uses the last directory component, title-cased.
68
+ * e.g. `/Users/yangyang/Repos/gruai` -> `Agent Conductor`
69
+ */
70
+ function deriveProjectName(projectPath) {
71
+ const basename = path.basename(projectPath);
72
+ return basename
73
+ .split('-')
74
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
75
+ .join(' ');
76
+ }
77
+ /**
78
+ * Discover conductor-enabled projects from ~/.claude/projects/.
79
+ * Scans subdirectories, decodes their names to real paths, and checks
80
+ * for `.context/` to identify conductor-enabled repos.
81
+ */
82
+ export function discoverProjects(claudeHome) {
83
+ const projectsDir = path.join(claudeHome, 'projects');
84
+ if (!fs.existsSync(projectsDir)) {
85
+ return [];
86
+ }
87
+ const discovered = [];
88
+ let entries;
89
+ try {
90
+ entries = fs.readdirSync(projectsDir).filter((name) => {
91
+ if (name.startsWith('.'))
92
+ return false;
93
+ try {
94
+ return fs.statSync(path.join(projectsDir, name)).isDirectory();
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ });
100
+ }
101
+ catch {
102
+ return [];
103
+ }
104
+ for (const entry of entries) {
105
+ const decodedPath = decodeProjectDirName(entry);
106
+ if (!decodedPath)
107
+ continue;
108
+ // Only include projects that have .context/ (conductor-enabled)
109
+ const contextDir = path.join(decodedPath, '.context');
110
+ if (!fs.existsSync(contextDir))
111
+ continue;
112
+ discovered.push({
113
+ name: deriveProjectName(decodedPath),
114
+ path: decodedPath,
115
+ source: 'discovered',
116
+ });
117
+ }
118
+ console.log(`[config] Discovered ${discovered.length} conductor-enabled project(s) from ${projectsDir}`);
119
+ for (const p of discovered) {
120
+ console.log(`[config] - ${p.name}: ${p.path}`);
121
+ }
122
+ return discovered;
123
+ }
124
+ function defaultConfig() {
125
+ return {
126
+ projects: [],
127
+ claudeHome: '~/.claude',
128
+ server: {
129
+ port: 4444,
130
+ },
131
+ notifications: {
132
+ macOS: true,
133
+ browser: true,
134
+ },
135
+ };
136
+ }
137
+ export function loadConfig() {
138
+ try {
139
+ let configProjects = [];
140
+ let claudeHome = '~/.claude';
141
+ let serverPort = 4444;
142
+ let notifications = { macOS: true, browser: true };
143
+ if (fs.existsSync(CONFIG_PATH)) {
144
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
145
+ const parsed = JSON.parse(raw);
146
+ configProjects = (parsed.projects ?? []).map((p) => ({
147
+ ...p,
148
+ source: 'config',
149
+ }));
150
+ claudeHome = parsed.claudeHome ?? claudeHome;
151
+ serverPort = parsed.server?.port ?? serverPort;
152
+ notifications = {
153
+ macOS: parsed.notifications?.macOS ?? notifications.macOS,
154
+ browser: parsed.notifications?.browser ?? notifications.browser,
155
+ };
156
+ }
157
+ else {
158
+ // Create default config file
159
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
160
+ const defaults = defaultConfig();
161
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(defaults, null, 2), 'utf-8');
162
+ console.log(`[config] Created default config at ${CONFIG_PATH}`);
163
+ }
164
+ const resolvedClaudeHome = resolveHome(claudeHome);
165
+ // Discover projects from ~/.claude/projects/
166
+ const discoveredProjects = discoverProjects(resolvedClaudeHome);
167
+ // Merge: discovered projects are primary, config.json projects fill gaps
168
+ // Deduplicate by resolved path
169
+ const seenPaths = new Set();
170
+ const mergedProjects = [];
171
+ // Add discovered projects first (they take priority)
172
+ for (const dp of discoveredProjects) {
173
+ const resolved = path.resolve(dp.path);
174
+ if (!seenPaths.has(resolved)) {
175
+ seenPaths.add(resolved);
176
+ mergedProjects.push(dp);
177
+ }
178
+ }
179
+ // Add config.json projects that weren't already discovered
180
+ for (const cp of configProjects) {
181
+ const resolved = path.resolve(resolveHome(cp.path));
182
+ if (!seenPaths.has(resolved)) {
183
+ seenPaths.add(resolved);
184
+ mergedProjects.push({
185
+ ...cp,
186
+ path: resolveHome(cp.path),
187
+ });
188
+ }
189
+ else {
190
+ // Config entry exists for a discovered project — use config's name if provided
191
+ const existing = mergedProjects.find((p) => path.resolve(p.path) === resolved);
192
+ if (existing && cp.name) {
193
+ existing.name = cp.name;
194
+ }
195
+ }
196
+ }
197
+ const config = {
198
+ projects: mergedProjects,
199
+ claudeHome: resolvedClaudeHome,
200
+ server: { port: serverPort },
201
+ notifications,
202
+ };
203
+ return config;
204
+ }
205
+ catch (err) {
206
+ console.error(`[config] Error loading config from ${CONFIG_PATH}, using defaults:`, err);
207
+ const defaults = defaultConfig();
208
+ return {
209
+ ...defaults,
210
+ claudeHome: resolveHome(defaults.claudeHome),
211
+ };
212
+ }
213
+ }
214
+ export function saveConfig(config) {
215
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
216
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
217
+ }
@@ -0,0 +1,7 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { HookEvent } from './types.js';
3
+ export declare function getDb(): Database.Database;
4
+ export declare function insertEvent(event: HookEvent): void;
5
+ export declare function getRecentEvents(limit?: number): HookEvent[];
6
+ export declare function getEventsBySession(sessionId: string, limit?: number): HookEvent[];
7
+ export declare function closeDb(): void;
@@ -0,0 +1,79 @@
1
+ import Database from 'better-sqlite3';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import fs from 'node:fs';
5
+ const DB_DIR = path.join(os.homedir(), '.conductor');
6
+ const DB_PATH = path.join(DB_DIR, 'conductor.db');
7
+ let db = null;
8
+ export function getDb() {
9
+ if (db)
10
+ return db;
11
+ fs.mkdirSync(DB_DIR, { recursive: true });
12
+ db = new Database(DB_PATH);
13
+ db.pragma('journal_mode = WAL');
14
+ db.pragma('synchronous = NORMAL');
15
+ db.exec(`
16
+ CREATE TABLE IF NOT EXISTS events (
17
+ id TEXT PRIMARY KEY,
18
+ type TEXT NOT NULL,
19
+ session_id TEXT NOT NULL,
20
+ timestamp TEXT NOT NULL,
21
+ message TEXT NOT NULL DEFAULT '',
22
+ project TEXT,
23
+ metadata_json TEXT
24
+ )
25
+ `);
26
+ // Index for querying by session and time
27
+ db.exec(`
28
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
29
+ `);
30
+ db.exec(`
31
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp DESC);
32
+ `);
33
+ console.log(`[db] SQLite database initialized at ${DB_PATH}`);
34
+ return db;
35
+ }
36
+ export function insertEvent(event) {
37
+ const database = getDb();
38
+ const stmt = database.prepare(`
39
+ INSERT OR REPLACE INTO events (id, type, session_id, timestamp, message, project, metadata_json)
40
+ VALUES (?, ?, ?, ?, ?, ?, ?)
41
+ `);
42
+ stmt.run(event.id, event.type, event.sessionId, event.timestamp, event.message, event.project ?? null, event.metadata ? JSON.stringify(event.metadata) : null);
43
+ }
44
+ export function getRecentEvents(limit = 100) {
45
+ const database = getDb();
46
+ const rows = database
47
+ .prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)
48
+ .all(limit);
49
+ return rows.map((row) => ({
50
+ id: row.id,
51
+ type: row.type,
52
+ sessionId: row.session_id,
53
+ timestamp: row.timestamp,
54
+ message: row.message,
55
+ project: row.project ?? undefined,
56
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
57
+ }));
58
+ }
59
+ export function getEventsBySession(sessionId, limit = 50) {
60
+ const database = getDb();
61
+ const rows = database
62
+ .prepare(`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?`)
63
+ .all(sessionId, limit);
64
+ return rows.map((row) => ({
65
+ id: row.id,
66
+ type: row.type,
67
+ sessionId: row.session_id,
68
+ timestamp: row.timestamp,
69
+ message: row.message,
70
+ project: row.project ?? undefined,
71
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
72
+ }));
73
+ }
74
+ export function closeDb() {
75
+ if (db) {
76
+ db.close();
77
+ db = null;
78
+ }
79
+ }
@@ -0,0 +1,11 @@
1
+ import type { HookEvent } from '../types.js';
2
+ interface RawEventBody {
3
+ type?: string;
4
+ sessionId?: string;
5
+ timestamp?: string;
6
+ message?: string;
7
+ project?: string;
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ export declare function processEvent(body: RawEventBody): HookEvent;
11
+ export {};
@@ -0,0 +1,36 @@
1
+ import crypto from 'node:crypto';
2
+ import { insertEvent } from '../db.js';
3
+ export function processEvent(body) {
4
+ const event = {
5
+ id: crypto.randomUUID(),
6
+ type: body.type ?? 'unknown',
7
+ sessionId: body.sessionId ?? 'unknown',
8
+ timestamp: body.timestamp ?? new Date().toISOString(),
9
+ message: body.message ?? formatDefaultMessage(body.type),
10
+ project: body.project,
11
+ metadata: body.metadata,
12
+ };
13
+ // Persist to SQLite
14
+ insertEvent(event);
15
+ return event;
16
+ }
17
+ function formatDefaultMessage(type) {
18
+ switch (type) {
19
+ case 'stop':
20
+ return 'Agent session stopped';
21
+ case 'task_completed':
22
+ return 'Task completed';
23
+ case 'teammate_idle':
24
+ return 'Teammate is idle';
25
+ case 'permission_prompt':
26
+ return 'Agent needs permission';
27
+ case 'idle_prompt':
28
+ return 'Agent needs input';
29
+ case 'elicitation_dialog':
30
+ return 'Agent needs input';
31
+ case 'error':
32
+ return 'Agent encountered an error';
33
+ default:
34
+ return `Event: ${type ?? 'unknown'}`;
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ export {};