@artyfacts/claude 1.1.2 → 1.2.1
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.
- package/dist/chunk-EROV5RIA.mjs +741 -0
- package/dist/cli.js +192 -7
- package/dist/cli.mjs +18 -6
- package/dist/index.d.mts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +181 -2
- package/dist/index.mjs +7 -1
- package/package.json +1 -1
- package/src/cli.ts +23 -6
- package/src/context.ts +260 -0
- package/src/executor.ts +44 -2
- package/src/index.ts +17 -0
package/dist/index.js
CHANGED
|
@@ -32,7 +32,10 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ArtyfactsListener: () => ArtyfactsListener,
|
|
34
34
|
ClaudeExecutor: () => ClaudeExecutor,
|
|
35
|
+
ContextFetcher: () => ContextFetcher,
|
|
36
|
+
buildPromptWithContext: () => buildPromptWithContext,
|
|
35
37
|
clearCredentials: () => clearCredentials,
|
|
38
|
+
createContextFetcher: () => createContextFetcher,
|
|
36
39
|
createExecutor: () => createExecutor,
|
|
37
40
|
createListener: () => createListener,
|
|
38
41
|
getCredentials: () => getCredentials,
|
|
@@ -233,6 +236,153 @@ async function getCredentials(options) {
|
|
|
233
236
|
|
|
234
237
|
// src/executor.ts
|
|
235
238
|
var import_child_process = require("child_process");
|
|
239
|
+
|
|
240
|
+
// src/context.ts
|
|
241
|
+
var ContextFetcher = class {
|
|
242
|
+
config;
|
|
243
|
+
constructor(config) {
|
|
244
|
+
this.config = config;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Fetch full context for a task
|
|
248
|
+
*/
|
|
249
|
+
async fetchTaskContext(taskId) {
|
|
250
|
+
const response = await fetch(
|
|
251
|
+
`${this.config.baseUrl}/tasks/${taskId}/context`,
|
|
252
|
+
{
|
|
253
|
+
headers: {
|
|
254
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
255
|
+
"Accept": "application/json"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
261
|
+
throw new Error(`Failed to fetch task context: ${response.status} - ${errorText}`);
|
|
262
|
+
}
|
|
263
|
+
const data = await response.json();
|
|
264
|
+
return data;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
function buildPromptWithContext(context) {
|
|
268
|
+
const parts = [];
|
|
269
|
+
parts.push(`You are an AI agent working within the Artyfacts task management system.
|
|
270
|
+
|
|
271
|
+
Your job is to complete the assigned task. You have full context about the organization, project, and related work.
|
|
272
|
+
|
|
273
|
+
Guidelines:
|
|
274
|
+
- Be thorough but concise
|
|
275
|
+
- If the task requires code, provide working code
|
|
276
|
+
- If the task requires analysis, provide structured findings
|
|
277
|
+
- If the task requires a decision, explain your reasoning
|
|
278
|
+
- If you cannot complete the task, explain why
|
|
279
|
+
|
|
280
|
+
Format your response as follows:
|
|
281
|
+
1. First, provide your main output (the task deliverable)
|
|
282
|
+
2. End with a brief summary line starting with "SUMMARY:"`);
|
|
283
|
+
parts.push("");
|
|
284
|
+
parts.push("---");
|
|
285
|
+
parts.push("");
|
|
286
|
+
parts.push("## Organization Context");
|
|
287
|
+
parts.push(`**${context.organization.name}**`);
|
|
288
|
+
if (context.organization.context) {
|
|
289
|
+
parts.push("");
|
|
290
|
+
parts.push(formatOrgContext(context.organization.context));
|
|
291
|
+
}
|
|
292
|
+
parts.push("");
|
|
293
|
+
if (context.project) {
|
|
294
|
+
parts.push(`## Project: ${context.project.name}`);
|
|
295
|
+
if (context.project.description) {
|
|
296
|
+
parts.push(context.project.description);
|
|
297
|
+
}
|
|
298
|
+
parts.push("");
|
|
299
|
+
}
|
|
300
|
+
parts.push(`## Artifact: ${context.artifact.title}`);
|
|
301
|
+
if (context.artifact.summary) {
|
|
302
|
+
parts.push(context.artifact.summary);
|
|
303
|
+
}
|
|
304
|
+
if (context.artifact.description) {
|
|
305
|
+
parts.push("");
|
|
306
|
+
parts.push(context.artifact.description);
|
|
307
|
+
}
|
|
308
|
+
parts.push("");
|
|
309
|
+
const relatedSections = context.artifact.sections.filter(
|
|
310
|
+
(s) => s.id !== context.task.id
|
|
311
|
+
);
|
|
312
|
+
if (relatedSections.length > 0) {
|
|
313
|
+
parts.push("### Related Sections:");
|
|
314
|
+
for (const section of relatedSections) {
|
|
315
|
+
const preview = section.content ? section.content.substring(0, 200) + (section.content.length > 200 ? "..." : "") : "No content";
|
|
316
|
+
const statusBadge = section.task_status ? ` [${section.task_status}]` : "";
|
|
317
|
+
parts.push(`- **${section.heading}**${statusBadge}: ${preview}`);
|
|
318
|
+
}
|
|
319
|
+
parts.push("");
|
|
320
|
+
}
|
|
321
|
+
parts.push("---");
|
|
322
|
+
parts.push("");
|
|
323
|
+
parts.push(`## Your Task: ${context.task.heading}`);
|
|
324
|
+
if (context.task.priority) {
|
|
325
|
+
const priorityLabels = ["\u{1F534} High", "\u{1F7E1} Medium", "\u{1F7E2} Low"];
|
|
326
|
+
parts.push(`**Priority:** ${priorityLabels[context.task.priority - 1] || "Medium"}`);
|
|
327
|
+
}
|
|
328
|
+
parts.push("");
|
|
329
|
+
parts.push("### Description");
|
|
330
|
+
parts.push(context.task.content || "No additional description provided.");
|
|
331
|
+
parts.push("");
|
|
332
|
+
if (context.task.expected_output) {
|
|
333
|
+
parts.push("### Expected Output");
|
|
334
|
+
if (context.task.expected_output.format) {
|
|
335
|
+
parts.push(`**Format:** ${context.task.expected_output.format}`);
|
|
336
|
+
}
|
|
337
|
+
if (context.task.expected_output.requirements && context.task.expected_output.requirements.length > 0) {
|
|
338
|
+
parts.push("**Requirements:**");
|
|
339
|
+
for (const req of context.task.expected_output.requirements) {
|
|
340
|
+
parts.push(`- ${req}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
parts.push("");
|
|
344
|
+
}
|
|
345
|
+
parts.push("---");
|
|
346
|
+
parts.push("");
|
|
347
|
+
parts.push("Complete this task and provide your output below.");
|
|
348
|
+
return parts.join("\n");
|
|
349
|
+
}
|
|
350
|
+
function formatOrgContext(context) {
|
|
351
|
+
const trimmed = context.trim();
|
|
352
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
353
|
+
try {
|
|
354
|
+
const parsed = JSON.parse(trimmed);
|
|
355
|
+
return formatContextObject(parsed);
|
|
356
|
+
} catch {
|
|
357
|
+
return context;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return context;
|
|
361
|
+
}
|
|
362
|
+
function formatContextObject(obj, indent = "") {
|
|
363
|
+
if (typeof obj !== "object" || obj === null) {
|
|
364
|
+
return String(obj);
|
|
365
|
+
}
|
|
366
|
+
if (Array.isArray(obj)) {
|
|
367
|
+
return obj.map((item) => `${indent}- ${formatContextObject(item, indent + " ")}`).join("\n");
|
|
368
|
+
}
|
|
369
|
+
const lines = [];
|
|
370
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
371
|
+
const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
372
|
+
if (typeof value === "object" && value !== null) {
|
|
373
|
+
lines.push(`${indent}**${label}:**`);
|
|
374
|
+
lines.push(formatContextObject(value, indent + " "));
|
|
375
|
+
} else {
|
|
376
|
+
lines.push(`${indent}- **${label}:** ${value}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return lines.join("\n");
|
|
380
|
+
}
|
|
381
|
+
function createContextFetcher(config) {
|
|
382
|
+
return new ContextFetcher(config);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/executor.ts
|
|
236
386
|
var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
|
|
237
387
|
var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
|
|
238
388
|
|
|
@@ -253,25 +403,51 @@ Format your response as follows:
|
|
|
253
403
|
2. End with a brief summary line starting with "SUMMARY:"`;
|
|
254
404
|
var ClaudeExecutor = class {
|
|
255
405
|
config;
|
|
406
|
+
contextFetcher = null;
|
|
256
407
|
constructor(config = {}) {
|
|
257
408
|
this.config = {
|
|
258
409
|
...config,
|
|
259
410
|
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
260
411
|
claudePath: config.claudePath || "claude"
|
|
261
412
|
};
|
|
413
|
+
if (config.baseUrl && config.apiKey) {
|
|
414
|
+
this.contextFetcher = createContextFetcher({
|
|
415
|
+
baseUrl: config.baseUrl,
|
|
416
|
+
apiKey: config.apiKey
|
|
417
|
+
});
|
|
418
|
+
}
|
|
262
419
|
}
|
|
263
420
|
/**
|
|
264
421
|
* Execute a task using Claude Code CLI
|
|
422
|
+
*
|
|
423
|
+
* If full context is available (baseUrl + apiKey configured), fetches
|
|
424
|
+
* organization, project, artifact, and related sections for a rich prompt.
|
|
265
425
|
*/
|
|
266
426
|
async execute(task) {
|
|
267
427
|
try {
|
|
268
|
-
|
|
428
|
+
let prompt;
|
|
429
|
+
let fullContext = null;
|
|
430
|
+
const useFullContext = this.config.useFullContext !== false && this.contextFetcher;
|
|
431
|
+
if (useFullContext) {
|
|
432
|
+
try {
|
|
433
|
+
fullContext = await this.contextFetcher.fetchTaskContext(task.taskId);
|
|
434
|
+
prompt = buildPromptWithContext(fullContext);
|
|
435
|
+
console.log(" \u{1F4DA} Using full context (org, project, artifact, related sections)");
|
|
436
|
+
} catch (contextError) {
|
|
437
|
+
console.warn(" \u26A0\uFE0F Could not fetch full context, using minimal prompt");
|
|
438
|
+
console.warn(` ${contextError instanceof Error ? contextError.message : contextError}`);
|
|
439
|
+
prompt = this.buildTaskPrompt(task);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
prompt = this.buildTaskPrompt(task);
|
|
443
|
+
}
|
|
269
444
|
const output = await this.runClaude(prompt);
|
|
270
445
|
const { content, summary } = this.parseResponse(output, task.heading);
|
|
271
446
|
return {
|
|
272
447
|
success: true,
|
|
273
448
|
output: content,
|
|
274
|
-
summary
|
|
449
|
+
summary,
|
|
450
|
+
promptUsed: prompt
|
|
275
451
|
};
|
|
276
452
|
} catch (error) {
|
|
277
453
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -601,7 +777,10 @@ function createListener(config) {
|
|
|
601
777
|
0 && (module.exports = {
|
|
602
778
|
ArtyfactsListener,
|
|
603
779
|
ClaudeExecutor,
|
|
780
|
+
ContextFetcher,
|
|
781
|
+
buildPromptWithContext,
|
|
604
782
|
clearCredentials,
|
|
783
|
+
createContextFetcher,
|
|
605
784
|
createExecutor,
|
|
606
785
|
createListener,
|
|
607
786
|
getCredentials,
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ArtyfactsListener,
|
|
3
3
|
ClaudeExecutor,
|
|
4
|
+
ContextFetcher,
|
|
5
|
+
buildPromptWithContext,
|
|
4
6
|
clearCredentials,
|
|
7
|
+
createContextFetcher,
|
|
5
8
|
createExecutor,
|
|
6
9
|
createListener,
|
|
7
10
|
getCredentials,
|
|
@@ -9,11 +12,14 @@ import {
|
|
|
9
12
|
promptForApiKey,
|
|
10
13
|
runDeviceAuth,
|
|
11
14
|
saveCredentials
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EROV5RIA.mjs";
|
|
13
16
|
export {
|
|
14
17
|
ArtyfactsListener,
|
|
15
18
|
ClaudeExecutor,
|
|
19
|
+
ContextFetcher,
|
|
20
|
+
buildPromptWithContext,
|
|
16
21
|
clearCredentials,
|
|
22
|
+
createContextFetcher,
|
|
17
23
|
createExecutor,
|
|
18
24
|
createListener,
|
|
19
25
|
getCredentials,
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -196,6 +196,15 @@ async function checkAndClaimTasks(
|
|
|
196
196
|
continue;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// Get the claimed task data (includes UUID which is globally unique)
|
|
200
|
+
const claimedTask = await claimResponse.json().catch(() => ({ id: task.id })) as {
|
|
201
|
+
id: string;
|
|
202
|
+
section_id: string;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Use UUID for subsequent calls (globally unique, avoids org filter issues)
|
|
206
|
+
const taskUuid = claimedTask.id || task.id;
|
|
207
|
+
|
|
199
208
|
// Successfully claimed - now execute
|
|
200
209
|
activeTasks.add(task.section_id);
|
|
201
210
|
console.log(' ✓ Claimed!');
|
|
@@ -209,8 +218,9 @@ async function checkAndClaimTasks(
|
|
|
209
218
|
console.log(' → Executing with Claude...');
|
|
210
219
|
|
|
211
220
|
try {
|
|
221
|
+
// Use UUID for context/complete (more reliable than section_id)
|
|
212
222
|
const result = await executor.execute({
|
|
213
|
-
taskId:
|
|
223
|
+
taskId: taskUuid, // Use UUID instead of section_id
|
|
214
224
|
heading: task.heading,
|
|
215
225
|
content: task.content,
|
|
216
226
|
artifactId: task.artifact_id,
|
|
@@ -222,7 +232,7 @@ async function checkAndClaimTasks(
|
|
|
222
232
|
await completeTask({
|
|
223
233
|
baseUrl,
|
|
224
234
|
apiKey,
|
|
225
|
-
taskId:
|
|
235
|
+
taskId: taskUuid, // Use UUID instead of section_id
|
|
226
236
|
output: result.output,
|
|
227
237
|
summary: result.summary,
|
|
228
238
|
});
|
|
@@ -232,7 +242,7 @@ async function checkAndClaimTasks(
|
|
|
232
242
|
await blockTask({
|
|
233
243
|
baseUrl,
|
|
234
244
|
apiKey,
|
|
235
|
-
taskId:
|
|
245
|
+
taskId: taskUuid, // Use UUID instead of section_id
|
|
236
246
|
reason: result.error || 'Execution failed',
|
|
237
247
|
});
|
|
238
248
|
}
|
|
@@ -269,10 +279,14 @@ async function runAgent(options: {
|
|
|
269
279
|
process.exit(1);
|
|
270
280
|
}
|
|
271
281
|
|
|
272
|
-
// Create executor (uses Claude Code CLI)
|
|
282
|
+
// Create executor (uses Claude Code CLI + Artyfacts context API)
|
|
273
283
|
let executor: ClaudeExecutor | null = null;
|
|
274
284
|
if (!options.dryRun) {
|
|
275
|
-
executor = createExecutor(
|
|
285
|
+
executor = createExecutor({
|
|
286
|
+
// Pass API credentials so executor can fetch full context
|
|
287
|
+
baseUrl: options.baseUrl,
|
|
288
|
+
apiKey: credentials.apiKey,
|
|
289
|
+
});
|
|
276
290
|
|
|
277
291
|
// Check if Claude Code is installed
|
|
278
292
|
const installed = await executor.isInstalled();
|
|
@@ -292,6 +306,7 @@ async function runAgent(options: {
|
|
|
292
306
|
process.exit(1);
|
|
293
307
|
}
|
|
294
308
|
console.log('✅ Claude Code connected');
|
|
309
|
+
console.log('📚 Context fetching enabled (will fetch org/project/artifact context)');
|
|
295
310
|
}
|
|
296
311
|
|
|
297
312
|
// Create listener
|
|
@@ -309,7 +324,9 @@ async function runAgent(options: {
|
|
|
309
324
|
console.log('👂 Listening for tasks...\n');
|
|
310
325
|
|
|
311
326
|
// Check for existing claimable tasks on connect
|
|
312
|
-
|
|
327
|
+
if (!options.dryRun) {
|
|
328
|
+
checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor!, options.dryRun);
|
|
329
|
+
}
|
|
313
330
|
break;
|
|
314
331
|
case 'reconnecting':
|
|
315
332
|
console.log('🔄 Reconnecting...');
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context fetcher for Artyfacts tasks
|
|
3
|
+
*
|
|
4
|
+
* Fetches full context including organization, project, artifact, and related sections.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export interface OrganizationContext {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
context?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProjectContext {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SectionContext {
|
|
24
|
+
id: string;
|
|
25
|
+
heading: string;
|
|
26
|
+
content?: string;
|
|
27
|
+
section_type?: string;
|
|
28
|
+
task_status?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ArtifactContext {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
summary?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
artifact_type?: string;
|
|
37
|
+
sections: SectionContext[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface TaskFullContext {
|
|
41
|
+
task: {
|
|
42
|
+
id: string;
|
|
43
|
+
heading: string;
|
|
44
|
+
content: string;
|
|
45
|
+
expected_output?: {
|
|
46
|
+
format?: string;
|
|
47
|
+
requirements?: string[];
|
|
48
|
+
};
|
|
49
|
+
priority?: number;
|
|
50
|
+
};
|
|
51
|
+
artifact: ArtifactContext;
|
|
52
|
+
project?: ProjectContext;
|
|
53
|
+
organization: OrganizationContext;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ContextFetcherConfig {
|
|
57
|
+
baseUrl: string;
|
|
58
|
+
apiKey: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Context Fetcher
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
export class ContextFetcher {
|
|
66
|
+
private config: ContextFetcherConfig;
|
|
67
|
+
|
|
68
|
+
constructor(config: ContextFetcherConfig) {
|
|
69
|
+
this.config = config;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fetch full context for a task
|
|
74
|
+
*/
|
|
75
|
+
async fetchTaskContext(taskId: string): Promise<TaskFullContext> {
|
|
76
|
+
const response = await fetch(
|
|
77
|
+
`${this.config.baseUrl}/tasks/${taskId}/context`,
|
|
78
|
+
{
|
|
79
|
+
headers: {
|
|
80
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
81
|
+
'Accept': 'application/json',
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
88
|
+
throw new Error(`Failed to fetch task context: ${response.status} - ${errorText}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
return data as TaskFullContext;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Prompt Builder
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build a rich prompt with full context
|
|
102
|
+
*/
|
|
103
|
+
export function buildPromptWithContext(context: TaskFullContext): string {
|
|
104
|
+
const parts: string[] = [];
|
|
105
|
+
|
|
106
|
+
// System instruction
|
|
107
|
+
parts.push(`You are an AI agent working within the Artyfacts task management system.
|
|
108
|
+
|
|
109
|
+
Your job is to complete the assigned task. You have full context about the organization, project, and related work.
|
|
110
|
+
|
|
111
|
+
Guidelines:
|
|
112
|
+
- Be thorough but concise
|
|
113
|
+
- If the task requires code, provide working code
|
|
114
|
+
- If the task requires analysis, provide structured findings
|
|
115
|
+
- If the task requires a decision, explain your reasoning
|
|
116
|
+
- If you cannot complete the task, explain why
|
|
117
|
+
|
|
118
|
+
Format your response as follows:
|
|
119
|
+
1. First, provide your main output (the task deliverable)
|
|
120
|
+
2. End with a brief summary line starting with "SUMMARY:"`);
|
|
121
|
+
|
|
122
|
+
parts.push('');
|
|
123
|
+
parts.push('---');
|
|
124
|
+
parts.push('');
|
|
125
|
+
|
|
126
|
+
// Organization context
|
|
127
|
+
parts.push('## Organization Context');
|
|
128
|
+
parts.push(`**${context.organization.name}**`);
|
|
129
|
+
if (context.organization.context) {
|
|
130
|
+
parts.push('');
|
|
131
|
+
parts.push(formatOrgContext(context.organization.context));
|
|
132
|
+
}
|
|
133
|
+
parts.push('');
|
|
134
|
+
|
|
135
|
+
// Project context (if available)
|
|
136
|
+
if (context.project) {
|
|
137
|
+
parts.push(`## Project: ${context.project.name}`);
|
|
138
|
+
if (context.project.description) {
|
|
139
|
+
parts.push(context.project.description);
|
|
140
|
+
}
|
|
141
|
+
parts.push('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Artifact context
|
|
145
|
+
parts.push(`## Artifact: ${context.artifact.title}`);
|
|
146
|
+
if (context.artifact.summary) {
|
|
147
|
+
parts.push(context.artifact.summary);
|
|
148
|
+
}
|
|
149
|
+
if (context.artifact.description) {
|
|
150
|
+
parts.push('');
|
|
151
|
+
parts.push(context.artifact.description);
|
|
152
|
+
}
|
|
153
|
+
parts.push('');
|
|
154
|
+
|
|
155
|
+
// Related sections (non-task sections for context, or other tasks for awareness)
|
|
156
|
+
const relatedSections = context.artifact.sections.filter(
|
|
157
|
+
s => s.id !== context.task.id
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (relatedSections.length > 0) {
|
|
161
|
+
parts.push('### Related Sections:');
|
|
162
|
+
for (const section of relatedSections) {
|
|
163
|
+
const preview = section.content
|
|
164
|
+
? section.content.substring(0, 200) + (section.content.length > 200 ? '...' : '')
|
|
165
|
+
: 'No content';
|
|
166
|
+
const statusBadge = section.task_status ? ` [${section.task_status}]` : '';
|
|
167
|
+
parts.push(`- **${section.heading}**${statusBadge}: ${preview}`);
|
|
168
|
+
}
|
|
169
|
+
parts.push('');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// The task itself
|
|
173
|
+
parts.push('---');
|
|
174
|
+
parts.push('');
|
|
175
|
+
parts.push(`## Your Task: ${context.task.heading}`);
|
|
176
|
+
|
|
177
|
+
if (context.task.priority) {
|
|
178
|
+
const priorityLabels = ['🔴 High', '🟡 Medium', '🟢 Low'];
|
|
179
|
+
parts.push(`**Priority:** ${priorityLabels[context.task.priority - 1] || 'Medium'}`);
|
|
180
|
+
}
|
|
181
|
+
parts.push('');
|
|
182
|
+
|
|
183
|
+
parts.push('### Description');
|
|
184
|
+
parts.push(context.task.content || 'No additional description provided.');
|
|
185
|
+
parts.push('');
|
|
186
|
+
|
|
187
|
+
// Expected output
|
|
188
|
+
if (context.task.expected_output) {
|
|
189
|
+
parts.push('### Expected Output');
|
|
190
|
+
if (context.task.expected_output.format) {
|
|
191
|
+
parts.push(`**Format:** ${context.task.expected_output.format}`);
|
|
192
|
+
}
|
|
193
|
+
if (context.task.expected_output.requirements && context.task.expected_output.requirements.length > 0) {
|
|
194
|
+
parts.push('**Requirements:**');
|
|
195
|
+
for (const req of context.task.expected_output.requirements) {
|
|
196
|
+
parts.push(`- ${req}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
parts.push('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Final instruction
|
|
203
|
+
parts.push('---');
|
|
204
|
+
parts.push('');
|
|
205
|
+
parts.push('Complete this task and provide your output below.');
|
|
206
|
+
|
|
207
|
+
return parts.join('\n');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Format organization context (may contain markdown or JSON)
|
|
212
|
+
*/
|
|
213
|
+
function formatOrgContext(context: string): string {
|
|
214
|
+
// If it looks like JSON, try to parse and format it nicely
|
|
215
|
+
const trimmed = context.trim();
|
|
216
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
217
|
+
try {
|
|
218
|
+
const parsed = JSON.parse(trimmed);
|
|
219
|
+
// Convert to readable bullet points
|
|
220
|
+
return formatContextObject(parsed);
|
|
221
|
+
} catch {
|
|
222
|
+
// Not valid JSON, return as-is
|
|
223
|
+
return context;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return context;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Format a context object into readable bullet points
|
|
231
|
+
*/
|
|
232
|
+
function formatContextObject(obj: unknown, indent = ''): string {
|
|
233
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
234
|
+
return String(obj);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (Array.isArray(obj)) {
|
|
238
|
+
return obj.map(item => `${indent}- ${formatContextObject(item, indent + ' ')}`).join('\n');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const lines: string[] = [];
|
|
242
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
243
|
+
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
244
|
+
if (typeof value === 'object' && value !== null) {
|
|
245
|
+
lines.push(`${indent}**${label}:**`);
|
|
246
|
+
lines.push(formatContextObject(value, indent + ' '));
|
|
247
|
+
} else {
|
|
248
|
+
lines.push(`${indent}- **${label}:** ${value}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return lines.join('\n');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// Factory
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
export function createContextFetcher(config: ContextFetcherConfig): ContextFetcher {
|
|
259
|
+
return new ContextFetcher(config);
|
|
260
|
+
}
|
package/src/executor.ts
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Executes tasks by shelling out to the Claude Code CLI (claude).
|
|
5
5
|
* This uses the user's existing Claude Code authentication - no separate API key needed.
|
|
6
|
+
*
|
|
7
|
+
* Now supports fetching full context (organization, project, artifact, related sections)
|
|
8
|
+
* to provide Claude with the information needed to complete tasks effectively.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { spawn } from 'child_process';
|
|
12
|
+
import { ContextFetcher, createContextFetcher, buildPromptWithContext, TaskFullContext } from './context';
|
|
9
13
|
|
|
10
14
|
// ============================================================================
|
|
11
15
|
// Types
|
|
@@ -37,6 +41,8 @@ export interface ExecutionResult {
|
|
|
37
41
|
summary: string;
|
|
38
42
|
/** Any error message if failed */
|
|
39
43
|
error?: string;
|
|
44
|
+
/** The prompt that was used (for debugging) */
|
|
45
|
+
promptUsed?: string;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export interface ExecutorConfig {
|
|
@@ -46,6 +52,12 @@ export interface ExecutorConfig {
|
|
|
46
52
|
timeout?: number;
|
|
47
53
|
/** System prompt prefix */
|
|
48
54
|
systemPromptPrefix?: string;
|
|
55
|
+
/** Artyfacts API base URL (for fetching context) */
|
|
56
|
+
baseUrl?: string;
|
|
57
|
+
/** Artyfacts API key (for fetching context) */
|
|
58
|
+
apiKey?: string;
|
|
59
|
+
/** Whether to use full context from API (default: true if apiKey provided) */
|
|
60
|
+
useFullContext?: boolean;
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
// ============================================================================
|
|
@@ -78,6 +90,7 @@ Format your response as follows:
|
|
|
78
90
|
|
|
79
91
|
export class ClaudeExecutor {
|
|
80
92
|
private config: Required<Pick<ExecutorConfig, 'timeout'>> & ExecutorConfig;
|
|
93
|
+
private contextFetcher: ContextFetcher | null = null;
|
|
81
94
|
|
|
82
95
|
constructor(config: ExecutorConfig = {}) {
|
|
83
96
|
this.config = {
|
|
@@ -85,15 +98,43 @@ export class ClaudeExecutor {
|
|
|
85
98
|
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
86
99
|
claudePath: config.claudePath || 'claude',
|
|
87
100
|
};
|
|
101
|
+
|
|
102
|
+
// Initialize context fetcher if API credentials provided
|
|
103
|
+
if (config.baseUrl && config.apiKey) {
|
|
104
|
+
this.contextFetcher = createContextFetcher({
|
|
105
|
+
baseUrl: config.baseUrl,
|
|
106
|
+
apiKey: config.apiKey,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
88
109
|
}
|
|
89
110
|
|
|
90
111
|
/**
|
|
91
112
|
* Execute a task using Claude Code CLI
|
|
113
|
+
*
|
|
114
|
+
* If full context is available (baseUrl + apiKey configured), fetches
|
|
115
|
+
* organization, project, artifact, and related sections for a rich prompt.
|
|
92
116
|
*/
|
|
93
117
|
async execute(task: TaskContext): Promise<ExecutionResult> {
|
|
94
118
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
119
|
+
let prompt: string;
|
|
120
|
+
let fullContext: TaskFullContext | null = null;
|
|
121
|
+
|
|
122
|
+
// Try to fetch full context if configured
|
|
123
|
+
const useFullContext = this.config.useFullContext !== false && this.contextFetcher;
|
|
124
|
+
|
|
125
|
+
if (useFullContext) {
|
|
126
|
+
try {
|
|
127
|
+
fullContext = await this.contextFetcher!.fetchTaskContext(task.taskId);
|
|
128
|
+
prompt = buildPromptWithContext(fullContext);
|
|
129
|
+
console.log(' 📚 Using full context (org, project, artifact, related sections)');
|
|
130
|
+
} catch (contextError) {
|
|
131
|
+
console.warn(' ⚠️ Could not fetch full context, using minimal prompt');
|
|
132
|
+
console.warn(` ${contextError instanceof Error ? contextError.message : contextError}`);
|
|
133
|
+
prompt = this.buildTaskPrompt(task);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
prompt = this.buildTaskPrompt(task);
|
|
137
|
+
}
|
|
97
138
|
|
|
98
139
|
// Execute via Claude Code CLI
|
|
99
140
|
const output = await this.runClaude(prompt);
|
|
@@ -105,6 +146,7 @@ export class ClaudeExecutor {
|
|
|
105
146
|
success: true,
|
|
106
147
|
output: content,
|
|
107
148
|
summary,
|
|
149
|
+
promptUsed: prompt,
|
|
108
150
|
};
|
|
109
151
|
} catch (error) {
|
|
110
152
|
const errorMessage = error instanceof Error ? error.message : String(error);
|