@fractary/faber-cli 1.0.0 → 1.2.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.
- package/dist/commands/init.js +1 -1
- package/dist/commands/plan/index.d.ts +11 -0
- package/dist/commands/plan/index.d.ts.map +1 -0
- package/dist/commands/plan/index.js +383 -0
- package/dist/commands/workflow/index.d.ts +7 -7
- package/dist/commands/workflow/index.js +17 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -8
- package/dist/lib/anthropic-client.d.ts +69 -0
- package/dist/lib/anthropic-client.d.ts.map +1 -0
- package/dist/lib/anthropic-client.js +225 -0
- package/dist/lib/config.d.ts +30 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +127 -0
- package/dist/lib/repo-client.d.ts +81 -0
- package/dist/lib/repo-client.d.ts.map +1 -0
- package/dist/lib/repo-client.js +105 -0
- package/dist/types/config.d.ts +34 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +6 -0
- package/dist/utils/prompt.d.ts +8 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +19 -0
- package/dist/utils/validation.d.ts +71 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +157 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createRepoCommand } from './commands/repo/index.js';
|
|
|
13
13
|
import { createSpecCommand } from './commands/spec/index.js';
|
|
14
14
|
import { createLogsCommand } from './commands/logs/index.js';
|
|
15
15
|
import { createInitCommand } from './commands/init.js';
|
|
16
|
+
import { createPlanCommand } from './commands/plan/index.js';
|
|
16
17
|
const version = '1.0.0';
|
|
17
18
|
/**
|
|
18
19
|
* Create and configure the main CLI program
|
|
@@ -25,14 +26,95 @@ export function createFaberCLI() {
|
|
|
25
26
|
.enablePositionalOptions();
|
|
26
27
|
// Global options
|
|
27
28
|
program.option('--debug', 'Enable debug output');
|
|
28
|
-
// Workflow commands (top-level)
|
|
29
|
-
program.addCommand(createInitCommand());
|
|
30
|
-
program.addCommand(
|
|
31
|
-
program.addCommand(
|
|
32
|
-
program.addCommand(
|
|
33
|
-
program.addCommand(
|
|
34
|
-
program.addCommand(
|
|
35
|
-
program.addCommand(
|
|
29
|
+
// Workflow commands (top-level) - NEW NAMES
|
|
30
|
+
program.addCommand(createInitCommand()); // workflow-init
|
|
31
|
+
program.addCommand(createPlanCommand()); // plan - NEW
|
|
32
|
+
program.addCommand(createRunCommand()); // workflow-run
|
|
33
|
+
program.addCommand(createStatusCommand()); // workflow-status
|
|
34
|
+
program.addCommand(createResumeCommand()); // workflow-resume
|
|
35
|
+
program.addCommand(createPauseCommand()); // workflow-pause
|
|
36
|
+
program.addCommand(createRecoverCommand()); // workflow-recover
|
|
37
|
+
program.addCommand(createCleanupCommand()); // workflow-cleanup
|
|
38
|
+
// DEPRECATED: Old command names (backwards compatibility)
|
|
39
|
+
const showDeprecationWarning = (oldName, newName) => {
|
|
40
|
+
console.warn(chalk.yellow(`\n⚠️ DEPRECATED: "${oldName}" → use "${newName}"\n`));
|
|
41
|
+
};
|
|
42
|
+
program
|
|
43
|
+
.command('init')
|
|
44
|
+
.description('(DEPRECATED: Use workflow-init)')
|
|
45
|
+
.option('--preset <name>', 'Use a preset configuration', 'default')
|
|
46
|
+
.option('--force', 'Overwrite existing configuration')
|
|
47
|
+
.option('--json', 'Output as JSON')
|
|
48
|
+
.action((options) => {
|
|
49
|
+
showDeprecationWarning('init', 'workflow-init');
|
|
50
|
+
const initCmd = createInitCommand();
|
|
51
|
+
initCmd.parse(['', '', ...Object.entries(options).flatMap(([k, v]) => typeof v === 'boolean' && v ? [`--${k}`] : typeof v === 'string' ? [`--${k}`, v] : [])], { from: 'user' });
|
|
52
|
+
});
|
|
53
|
+
program
|
|
54
|
+
.command('run')
|
|
55
|
+
.description('(DEPRECATED: Use workflow-run)')
|
|
56
|
+
.requiredOption('--work-id <id>', 'Work item ID to process')
|
|
57
|
+
.option('--autonomy <level>', 'Autonomy level', 'supervised')
|
|
58
|
+
.option('--json', 'Output as JSON')
|
|
59
|
+
.action((options) => {
|
|
60
|
+
showDeprecationWarning('run', 'workflow-run');
|
|
61
|
+
const runCmd = createRunCommand();
|
|
62
|
+
runCmd.parse(['', '', '--work-id', options.workId,
|
|
63
|
+
...(options.autonomy ? ['--autonomy', options.autonomy] : []),
|
|
64
|
+
...(options.json ? ['--json'] : [])
|
|
65
|
+
], { from: 'user' });
|
|
66
|
+
});
|
|
67
|
+
program
|
|
68
|
+
.command('status')
|
|
69
|
+
.description('(DEPRECATED: Use workflow-status)')
|
|
70
|
+
.option('--work-id <id>', 'Work item ID to check')
|
|
71
|
+
.option('--workflow-id <id>', 'Workflow ID to check')
|
|
72
|
+
.option('--verbose', 'Show detailed status')
|
|
73
|
+
.option('--json', 'Output as JSON')
|
|
74
|
+
.action((options) => {
|
|
75
|
+
showDeprecationWarning('status', 'workflow-status');
|
|
76
|
+
const statusCmd = createStatusCommand();
|
|
77
|
+
statusCmd.parse(['', '', ...Object.entries(options).flatMap(([k, v]) => typeof v === 'boolean' && v ? [`--${k}`] : typeof v === 'string' ? [`--${k}`, v] : [])], { from: 'user' });
|
|
78
|
+
});
|
|
79
|
+
program
|
|
80
|
+
.command('resume <workflow_id>')
|
|
81
|
+
.description('(DEPRECATED: Use workflow-resume)')
|
|
82
|
+
.option('--json', 'Output as JSON')
|
|
83
|
+
.action((workflowId, options) => {
|
|
84
|
+
showDeprecationWarning('resume', 'workflow-resume');
|
|
85
|
+
const resumeCmd = createResumeCommand();
|
|
86
|
+
resumeCmd.parse(['', '', workflowId, ...(options.json ? ['--json'] : [])], { from: 'user' });
|
|
87
|
+
});
|
|
88
|
+
program
|
|
89
|
+
.command('pause <workflow_id>')
|
|
90
|
+
.description('(DEPRECATED: Use workflow-pause)')
|
|
91
|
+
.option('--json', 'Output as JSON')
|
|
92
|
+
.action((workflowId, options) => {
|
|
93
|
+
showDeprecationWarning('pause', 'workflow-pause');
|
|
94
|
+
const pauseCmd = createPauseCommand();
|
|
95
|
+
pauseCmd.parse(['', '', workflowId, ...(options.json ? ['--json'] : [])], { from: 'user' });
|
|
96
|
+
});
|
|
97
|
+
program
|
|
98
|
+
.command('recover <workflow_id>')
|
|
99
|
+
.description('(DEPRECATED: Use workflow-recover)')
|
|
100
|
+
.option('--checkpoint <id>', 'Specific checkpoint ID')
|
|
101
|
+
.option('--phase <phase>', 'Recover to specific phase')
|
|
102
|
+
.option('--json', 'Output as JSON')
|
|
103
|
+
.action((workflowId, options) => {
|
|
104
|
+
showDeprecationWarning('recover', 'workflow-recover');
|
|
105
|
+
const recoverCmd = createRecoverCommand();
|
|
106
|
+
recoverCmd.parse(['', '', workflowId, ...Object.entries(options).flatMap(([k, v]) => typeof v === 'boolean' && v ? [`--${k}`] : typeof v === 'string' ? [`--${k}`, v] : [])], { from: 'user' });
|
|
107
|
+
});
|
|
108
|
+
program
|
|
109
|
+
.command('cleanup')
|
|
110
|
+
.description('(DEPRECATED: Use workflow-cleanup)')
|
|
111
|
+
.option('--max-age <days>', 'Delete workflows older than N days', '30')
|
|
112
|
+
.option('--json', 'Output as JSON')
|
|
113
|
+
.action((options) => {
|
|
114
|
+
showDeprecationWarning('cleanup', 'workflow-cleanup');
|
|
115
|
+
const cleanupCmd = createCleanupCommand();
|
|
116
|
+
cleanupCmd.parse(['', '', ...Object.entries(options).flatMap(([k, v]) => typeof v === 'boolean' && v ? [`--${k}`] : typeof v === 'string' ? [`--${k}`, v] : [])], { from: 'user' });
|
|
117
|
+
});
|
|
36
118
|
// Subcommand trees
|
|
37
119
|
program.addCommand(createWorkCommand());
|
|
38
120
|
program.addCommand(createRepoCommand());
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic API Client
|
|
3
|
+
*
|
|
4
|
+
* Generates workflow plans via Claude API
|
|
5
|
+
*/
|
|
6
|
+
import type { FaberConfig } from '../types/config.js';
|
|
7
|
+
interface GeneratePlanInput {
|
|
8
|
+
workflow: string;
|
|
9
|
+
issueTitle: string;
|
|
10
|
+
issueDescription: string;
|
|
11
|
+
issueNumber: number;
|
|
12
|
+
}
|
|
13
|
+
interface WorkflowPlan {
|
|
14
|
+
plan_id: string;
|
|
15
|
+
created_by: string;
|
|
16
|
+
cli_version: string;
|
|
17
|
+
created_at: string;
|
|
18
|
+
issue: {
|
|
19
|
+
source: string;
|
|
20
|
+
id: string;
|
|
21
|
+
url: string;
|
|
22
|
+
};
|
|
23
|
+
branch: string;
|
|
24
|
+
worktree: string;
|
|
25
|
+
workflow: string;
|
|
26
|
+
phases: any[];
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Anthropic API Client
|
|
31
|
+
*/
|
|
32
|
+
export declare class AnthropicClient {
|
|
33
|
+
private client;
|
|
34
|
+
private config;
|
|
35
|
+
private git;
|
|
36
|
+
private ajv;
|
|
37
|
+
private planSchema;
|
|
38
|
+
constructor(config: FaberConfig);
|
|
39
|
+
/**
|
|
40
|
+
* Load plan JSON schema for validation
|
|
41
|
+
*/
|
|
42
|
+
private loadPlanSchema;
|
|
43
|
+
/**
|
|
44
|
+
* Validate plan JSON against schema
|
|
45
|
+
*/
|
|
46
|
+
private validatePlan;
|
|
47
|
+
/**
|
|
48
|
+
* Generate workflow plan via Claude API
|
|
49
|
+
*/
|
|
50
|
+
generatePlan(input: GeneratePlanInput): Promise<WorkflowPlan>;
|
|
51
|
+
/**
|
|
52
|
+
* Load workflow configuration
|
|
53
|
+
*/
|
|
54
|
+
private loadWorkflowConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Construct planning prompt for Claude
|
|
57
|
+
*/
|
|
58
|
+
private constructPlanningPrompt;
|
|
59
|
+
/**
|
|
60
|
+
* Extract JSON from Claude response
|
|
61
|
+
*/
|
|
62
|
+
private extractJsonFromResponse;
|
|
63
|
+
/**
|
|
64
|
+
* Extract repository organization and project name using SDK Git class
|
|
65
|
+
*/
|
|
66
|
+
private extractRepoInfo;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=anthropic-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-client.d.ts","sourceRoot":"","sources":["../../src/lib/anthropic-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,GAAG,EAAE,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,UAAU,CAAM;gBAEZ,MAAM,EAAE,WAAW;IAe/B;;OAEG;YACW,cAAc;IAiB5B;;OAEG;IACH,OAAO,CAAC,YAAY;IAepB;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IA6DnE;;OAEG;YACW,kBAAkB;IAchC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAqD/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;OAEG;YACW,eAAe;CAqB9B"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic API Client
|
|
3
|
+
*
|
|
4
|
+
* Generates workflow plans via Claude API
|
|
5
|
+
*/
|
|
6
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
7
|
+
import Ajv from 'ajv';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { Git } from '@fractary/faber';
|
|
12
|
+
import { validateJsonSize } from '../utils/validation.js';
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
/**
|
|
16
|
+
* Anthropic API Client
|
|
17
|
+
*/
|
|
18
|
+
export class AnthropicClient {
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.git = new Git();
|
|
22
|
+
this.ajv = new Ajv({ strict: false }); // Allow additional properties
|
|
23
|
+
const apiKey = config.anthropic?.api_key;
|
|
24
|
+
if (!apiKey) {
|
|
25
|
+
throw new Error('Anthropic API key not found. Set ANTHROPIC_API_KEY environment variable.');
|
|
26
|
+
}
|
|
27
|
+
this.client = new Anthropic({
|
|
28
|
+
apiKey,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Load plan JSON schema for validation
|
|
33
|
+
*/
|
|
34
|
+
async loadPlanSchema() {
|
|
35
|
+
if (this.planSchema) {
|
|
36
|
+
return; // Already loaded
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
// Schema path relative to this file
|
|
40
|
+
const schemaPath = path.resolve(__dirname, '../../../plugins/faber/config/schemas/plan.schema.json');
|
|
41
|
+
const schemaContent = await fs.readFile(schemaPath, 'utf8');
|
|
42
|
+
this.planSchema = JSON.parse(schemaContent);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// Schema not found or invalid - log warning but don't fail
|
|
46
|
+
console.warn('Warning: Could not load plan schema for validation:', error instanceof Error ? error.message : 'Unknown error');
|
|
47
|
+
this.planSchema = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate plan JSON against schema
|
|
52
|
+
*/
|
|
53
|
+
validatePlan(plan) {
|
|
54
|
+
if (!this.planSchema) {
|
|
55
|
+
// Schema not loaded - skip validation
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const validate = this.ajv.compile(this.planSchema);
|
|
59
|
+
const valid = validate(plan);
|
|
60
|
+
if (!valid) {
|
|
61
|
+
const errors = validate.errors?.map(e => `${e.instancePath} ${e.message}`).join(', ') || 'Unknown validation errors';
|
|
62
|
+
throw new Error(`Plan JSON validation failed: ${errors}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate workflow plan via Claude API
|
|
67
|
+
*/
|
|
68
|
+
async generatePlan(input) {
|
|
69
|
+
// Load plan schema for validation
|
|
70
|
+
await this.loadPlanSchema();
|
|
71
|
+
// Load workflow configuration
|
|
72
|
+
const workflowConfig = await this.loadWorkflowConfig(input.workflow);
|
|
73
|
+
// Generate plan ID
|
|
74
|
+
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z/, '').replace('T', '-');
|
|
75
|
+
const planId = `fractary-faber-${input.issueNumber}-${timestamp}`;
|
|
76
|
+
// Construct prompt for Claude
|
|
77
|
+
const prompt = this.constructPlanningPrompt(input, workflowConfig);
|
|
78
|
+
// Call Claude API
|
|
79
|
+
const response = await this.client.messages.create({
|
|
80
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
81
|
+
max_tokens: 8192,
|
|
82
|
+
messages: [
|
|
83
|
+
{
|
|
84
|
+
role: 'user',
|
|
85
|
+
content: prompt,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
// Extract plan JSON from response
|
|
90
|
+
const content = response.content[0];
|
|
91
|
+
if (content.type !== 'text') {
|
|
92
|
+
throw new Error('Unexpected response type from Claude API');
|
|
93
|
+
}
|
|
94
|
+
// Validate response size (prevent DoS)
|
|
95
|
+
validateJsonSize(content.text, 1024 * 1024); // 1MB limit
|
|
96
|
+
const planJson = this.extractJsonFromResponse(content.text);
|
|
97
|
+
// Add metadata
|
|
98
|
+
const { organization, project } = await this.extractRepoInfo();
|
|
99
|
+
const plan = {
|
|
100
|
+
...planJson,
|
|
101
|
+
plan_id: planId,
|
|
102
|
+
created_by: 'cli',
|
|
103
|
+
cli_version: '1.0.0',
|
|
104
|
+
created_at: new Date().toISOString(),
|
|
105
|
+
issue: {
|
|
106
|
+
source: 'github',
|
|
107
|
+
id: input.issueNumber.toString(),
|
|
108
|
+
url: `https://github.com/${organization}/${project}/issues/${input.issueNumber}`,
|
|
109
|
+
},
|
|
110
|
+
branch: `feature/${input.issueNumber}`,
|
|
111
|
+
worktree: `~/.claude-worktrees/${organization}-${project}-${input.issueNumber}`,
|
|
112
|
+
workflow: input.workflow,
|
|
113
|
+
};
|
|
114
|
+
// Validate plan against schema
|
|
115
|
+
this.validatePlan(plan);
|
|
116
|
+
return plan;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Load workflow configuration
|
|
120
|
+
*/
|
|
121
|
+
async loadWorkflowConfig(workflow) {
|
|
122
|
+
const workflowPath = path.join(this.config.workflow?.config_path || './plugins/faber/config/workflows', `${workflow}.json`);
|
|
123
|
+
try {
|
|
124
|
+
const content = await fs.readFile(workflowPath, 'utf-8');
|
|
125
|
+
return JSON.parse(content);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
throw new Error(`Failed to load workflow config: ${workflow} (${workflowPath})`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Construct planning prompt for Claude
|
|
133
|
+
*/
|
|
134
|
+
constructPlanningPrompt(input, workflowConfig) {
|
|
135
|
+
return `You are a workflow planning assistant for the FABER system. Your task is to generate a structured workflow plan based on the provided issue and workflow configuration.
|
|
136
|
+
|
|
137
|
+
**Issue Information:**
|
|
138
|
+
- Number: #${input.issueNumber}
|
|
139
|
+
- Title: ${input.issueTitle}
|
|
140
|
+
- Description: ${input.issueDescription}
|
|
141
|
+
|
|
142
|
+
**Workflow Type:** ${input.workflow}
|
|
143
|
+
|
|
144
|
+
**Workflow Configuration:**
|
|
145
|
+
${JSON.stringify(workflowConfig, null, 2)}
|
|
146
|
+
|
|
147
|
+
**Your Task:**
|
|
148
|
+
Generate a complete workflow plan that includes:
|
|
149
|
+
1. All phases from the workflow configuration
|
|
150
|
+
2. Specific steps for each phase based on the issue requirements
|
|
151
|
+
3. Success criteria for each phase
|
|
152
|
+
4. Estimated complexity
|
|
153
|
+
|
|
154
|
+
**Output Format:**
|
|
155
|
+
Return ONLY a valid JSON object with the following structure:
|
|
156
|
+
|
|
157
|
+
\`\`\`json
|
|
158
|
+
{
|
|
159
|
+
"phases": [
|
|
160
|
+
{
|
|
161
|
+
"phase": "phase_name",
|
|
162
|
+
"description": "What this phase accomplishes",
|
|
163
|
+
"steps": [
|
|
164
|
+
{
|
|
165
|
+
"action": "specific action to take",
|
|
166
|
+
"details": "additional context or requirements"
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
"success_criteria": [
|
|
170
|
+
"criterion 1",
|
|
171
|
+
"criterion 2"
|
|
172
|
+
],
|
|
173
|
+
"complexity": "low|medium|high"
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"overall_complexity": "low|medium|high",
|
|
177
|
+
"estimated_phases": 4,
|
|
178
|
+
"special_considerations": [
|
|
179
|
+
"Any special notes or warnings"
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
Generate the plan now:`;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Extract JSON from Claude response
|
|
188
|
+
*/
|
|
189
|
+
extractJsonFromResponse(text) {
|
|
190
|
+
// Try to find JSON in code blocks
|
|
191
|
+
const jsonBlockMatch = text.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
192
|
+
if (jsonBlockMatch) {
|
|
193
|
+
return JSON.parse(jsonBlockMatch[1]);
|
|
194
|
+
}
|
|
195
|
+
// Try to find JSON in the text
|
|
196
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
197
|
+
if (jsonMatch) {
|
|
198
|
+
return JSON.parse(jsonMatch[0]);
|
|
199
|
+
}
|
|
200
|
+
throw new Error('Could not extract JSON from Claude response');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Extract repository organization and project name using SDK Git class
|
|
204
|
+
*/
|
|
205
|
+
async extractRepoInfo() {
|
|
206
|
+
try {
|
|
207
|
+
const remoteUrl = this.git.exec('remote get-url origin');
|
|
208
|
+
// Parse git@github.com:organization/project.git or https://github.com/organization/project.git
|
|
209
|
+
const match = remoteUrl.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
210
|
+
if (match) {
|
|
211
|
+
return {
|
|
212
|
+
organization: match[1],
|
|
213
|
+
project: match[2],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
// Fall back to config or defaults
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
organization: this.config.github?.organization || 'unknown',
|
|
222
|
+
project: this.config.github?.project || 'unknown',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager
|
|
3
|
+
*
|
|
4
|
+
* Loads FABER CLI configuration and respects Claude Code settings
|
|
5
|
+
*/
|
|
6
|
+
import type { FaberConfig } from '../types/config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration Manager
|
|
9
|
+
*/
|
|
10
|
+
export declare class ConfigManager {
|
|
11
|
+
private config;
|
|
12
|
+
private constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Load configuration
|
|
15
|
+
*/
|
|
16
|
+
static load(): Promise<FaberConfig>;
|
|
17
|
+
/**
|
|
18
|
+
* Read Claude Code configuration for worktree location
|
|
19
|
+
*/
|
|
20
|
+
private static readClaudeCodeWorktreeLocation;
|
|
21
|
+
/**
|
|
22
|
+
* Get Claude Code configuration paths by platform
|
|
23
|
+
*/
|
|
24
|
+
private static getClaudeConfigPaths;
|
|
25
|
+
/**
|
|
26
|
+
* Get configuration value
|
|
27
|
+
*/
|
|
28
|
+
get(key: keyof FaberConfig): any;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,oBAAoB,CAAC;AAEpE;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAE5B,OAAO;IAIP;;OAEG;WACU,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAgEzC;;OAEG;mBACkB,8BAA8B;IAoBnD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAyBnC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,WAAW,GAAG,GAAG;CAGjC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager
|
|
3
|
+
*
|
|
4
|
+
* Loads FABER CLI configuration and respects Claude Code settings
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
/**
|
|
10
|
+
* Configuration Manager
|
|
11
|
+
*/
|
|
12
|
+
export class ConfigManager {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Load configuration
|
|
18
|
+
*/
|
|
19
|
+
static async load() {
|
|
20
|
+
const config = {};
|
|
21
|
+
// Load from environment variables
|
|
22
|
+
config.anthropic = {
|
|
23
|
+
api_key: process.env.ANTHROPIC_API_KEY,
|
|
24
|
+
};
|
|
25
|
+
config.github = {
|
|
26
|
+
token: process.env.GITHUB_TOKEN,
|
|
27
|
+
};
|
|
28
|
+
// Load from FABER config file
|
|
29
|
+
try {
|
|
30
|
+
const faberConfigPath = path.join(process.cwd(), '.fractary', 'settings.json');
|
|
31
|
+
const faberConfigContent = await fs.readFile(faberConfigPath, 'utf-8');
|
|
32
|
+
const faberConfig = JSON.parse(faberConfigContent);
|
|
33
|
+
// Merge with config
|
|
34
|
+
if (faberConfig.anthropic) {
|
|
35
|
+
config.anthropic = { ...config.anthropic, ...faberConfig.anthropic };
|
|
36
|
+
}
|
|
37
|
+
if (faberConfig.github) {
|
|
38
|
+
config.github = { ...config.github, ...faberConfig.github };
|
|
39
|
+
}
|
|
40
|
+
if (faberConfig.worktree) {
|
|
41
|
+
config.worktree = { ...config.worktree, ...faberConfig.worktree };
|
|
42
|
+
}
|
|
43
|
+
if (faberConfig.workflow) {
|
|
44
|
+
config.workflow = { ...config.workflow, ...faberConfig.workflow };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
// FABER config not found, use defaults
|
|
49
|
+
}
|
|
50
|
+
// Read Claude Code configuration for worktree location
|
|
51
|
+
if (!config.worktree?.location || config.worktree?.inherit_from_claude !== false) {
|
|
52
|
+
const claudeWorktreeLocation = await ConfigManager.readClaudeCodeWorktreeLocation();
|
|
53
|
+
if (claudeWorktreeLocation) {
|
|
54
|
+
if (!config.worktree) {
|
|
55
|
+
config.worktree = {};
|
|
56
|
+
}
|
|
57
|
+
config.worktree.location = claudeWorktreeLocation;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Set defaults
|
|
61
|
+
if (!config.worktree?.location) {
|
|
62
|
+
config.worktree = {
|
|
63
|
+
...config.worktree,
|
|
64
|
+
location: path.join(os.homedir(), '.claude-worktrees'),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (!config.workflow?.config_path) {
|
|
68
|
+
config.workflow = {
|
|
69
|
+
...config.workflow,
|
|
70
|
+
config_path: path.join(process.cwd(), 'plugins', 'faber', 'config', 'workflows'),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read Claude Code configuration for worktree location
|
|
77
|
+
*/
|
|
78
|
+
static async readClaudeCodeWorktreeLocation() {
|
|
79
|
+
const configPaths = ConfigManager.getClaudeConfigPaths();
|
|
80
|
+
for (const configPath of configPaths) {
|
|
81
|
+
try {
|
|
82
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
83
|
+
const config = JSON.parse(content);
|
|
84
|
+
if (config.worktree?.directory) {
|
|
85
|
+
return config.worktree.directory;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
// Config file not found or not readable, try next
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get Claude Code configuration paths by platform
|
|
97
|
+
*/
|
|
98
|
+
static getClaudeConfigPaths() {
|
|
99
|
+
const homeDir = os.homedir();
|
|
100
|
+
const platform = os.platform();
|
|
101
|
+
switch (platform) {
|
|
102
|
+
case 'linux':
|
|
103
|
+
return [
|
|
104
|
+
path.join(homeDir, '.config', 'claude', 'config.json'),
|
|
105
|
+
path.join(homeDir, '.claude', 'config.json'),
|
|
106
|
+
];
|
|
107
|
+
case 'darwin': // macOS
|
|
108
|
+
return [
|
|
109
|
+
path.join(homeDir, 'Library', 'Application Support', 'Claude', 'config.json'),
|
|
110
|
+
path.join(homeDir, '.config', 'claude', 'config.json'),
|
|
111
|
+
];
|
|
112
|
+
case 'win32': // Windows
|
|
113
|
+
return [
|
|
114
|
+
path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), 'Claude', 'config.json'),
|
|
115
|
+
path.join(homeDir, '.claude', 'config.json'),
|
|
116
|
+
];
|
|
117
|
+
default:
|
|
118
|
+
return [path.join(homeDir, '.claude', 'config.json')];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get configuration value
|
|
123
|
+
*/
|
|
124
|
+
get(key) {
|
|
125
|
+
return this.config[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo Client
|
|
3
|
+
*
|
|
4
|
+
* Wrapper for fractary-repo plugin commands (CLI-based)
|
|
5
|
+
*/
|
|
6
|
+
interface Issue {
|
|
7
|
+
id: string;
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
labels: string[];
|
|
12
|
+
url: string;
|
|
13
|
+
state: string;
|
|
14
|
+
}
|
|
15
|
+
interface WorktreeResult {
|
|
16
|
+
path: string;
|
|
17
|
+
absolute_path: string;
|
|
18
|
+
branch: string;
|
|
19
|
+
created_at: string;
|
|
20
|
+
organization: string;
|
|
21
|
+
project: string;
|
|
22
|
+
work_id: string;
|
|
23
|
+
}
|
|
24
|
+
interface IssueUpdateOptions {
|
|
25
|
+
id: string;
|
|
26
|
+
comment?: string;
|
|
27
|
+
addLabel?: string;
|
|
28
|
+
removeLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Repo Client - wraps fractary-repo plugin CLI operations
|
|
32
|
+
*
|
|
33
|
+
* Note: This calls fractary-repo CLI commands as specified in SPEC-00030.
|
|
34
|
+
* These commands must be implemented in the fractary-repo plugin.
|
|
35
|
+
*/
|
|
36
|
+
export declare class RepoClient {
|
|
37
|
+
private config;
|
|
38
|
+
constructor(config: any);
|
|
39
|
+
/**
|
|
40
|
+
* Fetch specific issues by ID
|
|
41
|
+
*
|
|
42
|
+
* Calls: fractary-repo issue-fetch --ids 258,259,260 --format json
|
|
43
|
+
*/
|
|
44
|
+
fetchIssues(ids: string[]): Promise<Issue[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Search issues by labels
|
|
47
|
+
*
|
|
48
|
+
* Calls: fractary-repo issue-search --labels "workflow:etl,status:approved" --format json
|
|
49
|
+
*/
|
|
50
|
+
searchIssues(labels: string[]): Promise<Issue[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Create a git branch
|
|
53
|
+
*
|
|
54
|
+
* Calls: fractary-repo branch-create <branch-name> --format json
|
|
55
|
+
*/
|
|
56
|
+
createBranch(branchName: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Create a git worktree
|
|
59
|
+
*
|
|
60
|
+
* Calls: fractary-repo worktree-create --work-id 258 --format json
|
|
61
|
+
*/
|
|
62
|
+
createWorktree(options: {
|
|
63
|
+
workId: string;
|
|
64
|
+
path?: string;
|
|
65
|
+
}): Promise<WorktreeResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Update GitHub issue
|
|
68
|
+
*
|
|
69
|
+
* Calls: fractary-repo issue-update --id 258 --comment "..." --add-label "..."
|
|
70
|
+
*/
|
|
71
|
+
updateIssue(options: IssueUpdateOptions): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Call fractary-repo CLI command
|
|
74
|
+
*
|
|
75
|
+
* This will be implemented to spawn fractary-repo CLI process safely.
|
|
76
|
+
* For now, this is a placeholder showing the intended interface.
|
|
77
|
+
*/
|
|
78
|
+
private callRepoCommand;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=repo-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-client.d.ts","sourceRoot":"","sources":["../../src/lib/repo-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU,KAAK;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAM;gBAER,MAAM,EAAE,GAAG;IASvB;;;;OAIG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAQlD;;;;OAIG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAQtD;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAazF;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB7D;;;;;OAKG;YACW,eAAe;CAU9B"}
|