@openweave/weave-cli 1.0.2
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/LICENSE +21 -0
- package/README.md +116 -0
- package/package.json +45 -0
- package/src/cli.test.ts +722 -0
- package/src/cli.ts +155 -0
- package/src/commands/errors.ts +211 -0
- package/src/commands/init.ts +133 -0
- package/src/commands/migrate.ts +239 -0
- package/src/commands/milestones.ts +249 -0
- package/src/commands/orphans.ts +210 -0
- package/src/commands/query.ts +170 -0
- package/src/commands/save-node.ts +161 -0
- package/src/commands/skills.ts +230 -0
- package/src/commands/status.ts +122 -0
- package/src/commands/tools.ts +346 -0
- package/src/index.ts +12 -0
- package/src/types.ts +53 -0
- package/src/utils.ts +19 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { CLIArgs, CommandResult, CliCommand } from '../types';
|
|
4
|
+
import { resolveProjectRoot } from '../utils';
|
|
5
|
+
|
|
6
|
+
interface GraphNode {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
type: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
frequency: number;
|
|
12
|
+
last_accessed?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Query Command - Search the knowledge graph
|
|
17
|
+
*/
|
|
18
|
+
export class QueryCommand implements CliCommand {
|
|
19
|
+
name = 'query';
|
|
20
|
+
description = 'Search the knowledge graph for nodes and relationships';
|
|
21
|
+
usage = 'weave query <search-term> [--limit=10] [--type=all]';
|
|
22
|
+
flags = {
|
|
23
|
+
limit: {
|
|
24
|
+
short: 'l',
|
|
25
|
+
description: 'Maximum results to return (default: 10)',
|
|
26
|
+
default: '10',
|
|
27
|
+
},
|
|
28
|
+
type: {
|
|
29
|
+
short: 't',
|
|
30
|
+
description: 'Filter by node type (default: all)',
|
|
31
|
+
default: 'all',
|
|
32
|
+
},
|
|
33
|
+
json: {
|
|
34
|
+
short: 'j',
|
|
35
|
+
description: 'Output as JSON',
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async execute(args: CLIArgs): Promise<CommandResult> {
|
|
41
|
+
try {
|
|
42
|
+
const searchTerm = args.args[0];
|
|
43
|
+
if (!searchTerm) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
message: 'Error: Search term is required',
|
|
47
|
+
error: 'Usage: weave query <search-term>',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const projectRoot = resolveProjectRoot();
|
|
52
|
+
const weaveDir = join(projectRoot, '.weave');
|
|
53
|
+
const graphPath = join(weaveDir, 'graph.json');
|
|
54
|
+
|
|
55
|
+
if (!existsSync(graphPath)) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
message: 'Error: Knowledge graph not found',
|
|
59
|
+
error: 'Please run "weave init <project-name>" first',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Mock search results for demonstration
|
|
64
|
+
const mockResults: GraphNode[] = [
|
|
65
|
+
{
|
|
66
|
+
id: 'n1',
|
|
67
|
+
label: 'initializeProject',
|
|
68
|
+
type: 'FUNCTION',
|
|
69
|
+
description: 'Initializes a new project with default settings',
|
|
70
|
+
frequency: 5,
|
|
71
|
+
last_accessed: new Date().toISOString().split('T')[0],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'n2',
|
|
75
|
+
label: 'ProjectManager',
|
|
76
|
+
type: 'CLASS',
|
|
77
|
+
description: 'Manages project lifecycle and state',
|
|
78
|
+
frequency: 12,
|
|
79
|
+
last_accessed: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000)
|
|
80
|
+
.toISOString()
|
|
81
|
+
.split('T')[0],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'n3',
|
|
85
|
+
label: 'project_config',
|
|
86
|
+
type: 'VARIABLE',
|
|
87
|
+
description: 'Global configuration object',
|
|
88
|
+
frequency: 3,
|
|
89
|
+
last_accessed: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000)
|
|
90
|
+
.toISOString()
|
|
91
|
+
.split('T')[0],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const limit = parseInt((args.flags.limit as string) || '10', 10);
|
|
96
|
+
const typeFilter = (args.flags.type as string) || 'all';
|
|
97
|
+
|
|
98
|
+
let results = mockResults.filter((r) =>
|
|
99
|
+
r.label.toLowerCase().includes(searchTerm.toLowerCase())
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (typeFilter !== 'all') {
|
|
103
|
+
results = results.filter((r) => r.type === typeFilter.toUpperCase());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
results = results.slice(0, limit);
|
|
107
|
+
|
|
108
|
+
if (args.flags.json) {
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
message: JSON.stringify(results, null, 2),
|
|
112
|
+
data: results,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let output = `\nš Query Results for: "${searchTerm}"\n`;
|
|
117
|
+
output += '='.repeat(60) + '\n';
|
|
118
|
+
|
|
119
|
+
if (results.length === 0) {
|
|
120
|
+
output += 'No results found.\n';
|
|
121
|
+
} else {
|
|
122
|
+
for (const node of results) {
|
|
123
|
+
output += this.formatNode(node);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
output += '\n' + '='.repeat(60) + '\n';
|
|
128
|
+
output += `Found: ${results.length} result(s)\n`;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
message: output,
|
|
133
|
+
data: results,
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
message: 'Error querying knowledge graph',
|
|
139
|
+
error: error instanceof Error ? error.message : String(error),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private formatNode(node: GraphNode): string {
|
|
145
|
+
const typeEmoji = {
|
|
146
|
+
FUNCTION: 'š',
|
|
147
|
+
CLASS: 'šļø',
|
|
148
|
+
INTERFACE: 'š',
|
|
149
|
+
ENUM: 'š',
|
|
150
|
+
TYPE: 'š',
|
|
151
|
+
VARIABLE: 'š¦',
|
|
152
|
+
METHOD: 'š§',
|
|
153
|
+
MODULE: 'š',
|
|
154
|
+
}[node.type] || 'ā';
|
|
155
|
+
|
|
156
|
+
let output = `\n${typeEmoji} ${node.label} (${node.type})\n`;
|
|
157
|
+
if (node.description) {
|
|
158
|
+
output += ` Description: ${node.description}\n`;
|
|
159
|
+
}
|
|
160
|
+
output += ` ID: ${node.id}\n`;
|
|
161
|
+
output += ` Frequency: ${node.frequency} times\n`;
|
|
162
|
+
if (node.last_accessed) {
|
|
163
|
+
output += ` Last accessed: ${node.last_accessed}\n`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const queryCommand = new QueryCommand();
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { CLIArgs, CommandResult, CliCommand } from '../types';
|
|
4
|
+
import { resolveProjectRoot } from '../utils';
|
|
5
|
+
|
|
6
|
+
interface GraphNode {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
type: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
frequency?: number;
|
|
13
|
+
created_at: string;
|
|
14
|
+
updated_at: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* SaveNode Command - Manually add or update a node in the knowledge graph
|
|
19
|
+
*/
|
|
20
|
+
export class SaveNodeCommand implements CliCommand {
|
|
21
|
+
name = 'save-node';
|
|
22
|
+
description = 'Manually add or update a node in the knowledge graph';
|
|
23
|
+
usage = 'weave save-node --label=<name> --type=<type> [--description=<desc>]';
|
|
24
|
+
flags = {
|
|
25
|
+
label: {
|
|
26
|
+
short: 'l',
|
|
27
|
+
description: 'Node label/name (required)',
|
|
28
|
+
},
|
|
29
|
+
type: {
|
|
30
|
+
short: 't',
|
|
31
|
+
description: 'Node type: function, class, variable, interface, enum, etc.',
|
|
32
|
+
},
|
|
33
|
+
description: {
|
|
34
|
+
short: 'd',
|
|
35
|
+
description: 'Node description/documentation',
|
|
36
|
+
default: '',
|
|
37
|
+
},
|
|
38
|
+
file: {
|
|
39
|
+
short: 'f',
|
|
40
|
+
description: 'Source file location',
|
|
41
|
+
default: '',
|
|
42
|
+
},
|
|
43
|
+
line: {
|
|
44
|
+
description: 'Line number in source file',
|
|
45
|
+
default: '0',
|
|
46
|
+
},
|
|
47
|
+
frequency: {
|
|
48
|
+
description: 'Reference frequency hint',
|
|
49
|
+
default: '1',
|
|
50
|
+
},
|
|
51
|
+
json: {
|
|
52
|
+
short: 'j',
|
|
53
|
+
description: 'Output as JSON',
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
async execute(args: CLIArgs): Promise<CommandResult> {
|
|
59
|
+
try {
|
|
60
|
+
const label = args.flags.label as string;
|
|
61
|
+
const type = args.flags.type as string;
|
|
62
|
+
|
|
63
|
+
if (!label) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
message: 'Error: --label is required',
|
|
67
|
+
error: 'Usage: weave save-node --label=<name> --type=<type>',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!type) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
message: 'Error: --type is required',
|
|
75
|
+
error:
|
|
76
|
+
'Valid types: function, class, variable, interface, enum, module, type, method',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const projectRoot = resolveProjectRoot();
|
|
81
|
+
const weaveDir = join(projectRoot, '.weave');
|
|
82
|
+
const graphPath = join(weaveDir, 'graph.json');
|
|
83
|
+
|
|
84
|
+
if (!existsSync(graphPath)) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
message: 'Error: Knowledge graph not found',
|
|
88
|
+
error: 'Please run "weave init <project-name>" first',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Read existing graph
|
|
93
|
+
const graphContent = readFileSync(graphPath, 'utf-8');
|
|
94
|
+
const graphData = JSON.parse(graphContent);
|
|
95
|
+
|
|
96
|
+
// Create new node
|
|
97
|
+
const nodeId = `node_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
98
|
+
const now = new Date().toISOString();
|
|
99
|
+
|
|
100
|
+
const newNode: GraphNode = {
|
|
101
|
+
id: nodeId,
|
|
102
|
+
label,
|
|
103
|
+
type: type.toUpperCase(),
|
|
104
|
+
description: (args.flags.description as string) || undefined,
|
|
105
|
+
frequency: parseInt((args.flags.frequency as string) || '1', 10),
|
|
106
|
+
created_at: now,
|
|
107
|
+
updated_at: now,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (args.flags.file) {
|
|
111
|
+
newNode.metadata = {
|
|
112
|
+
file: args.flags.file,
|
|
113
|
+
line: parseInt((args.flags.line as string) || '0', 10),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add to graph
|
|
118
|
+
graphData.nodes.push(newNode);
|
|
119
|
+
|
|
120
|
+
// Write back
|
|
121
|
+
writeFileSync(graphPath, JSON.stringify(graphData, null, 2));
|
|
122
|
+
|
|
123
|
+
if (args.flags.json) {
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
message: JSON.stringify(newNode, null, 2),
|
|
127
|
+
data: newNode,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const output = `
|
|
132
|
+
ā
Node created successfully!
|
|
133
|
+
|
|
134
|
+
š Node ID: ${newNode.id}
|
|
135
|
+
š Label: ${newNode.label}
|
|
136
|
+
š·ļø Type: ${newNode.type}
|
|
137
|
+
${newNode.description ? `š Description: ${newNode.description}\n` : ''}
|
|
138
|
+
|
|
139
|
+
Graph Statistics:
|
|
140
|
+
⢠Total nodes: ${graphData.nodes.length}
|
|
141
|
+
⢠Total edges: ${graphData.edges.length}
|
|
142
|
+
|
|
143
|
+
Use 'weave query ${newNode.label}' to search for this node.
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
success: true,
|
|
148
|
+
message: output,
|
|
149
|
+
data: newNode,
|
|
150
|
+
};
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
message: 'Error saving node',
|
|
155
|
+
error: error instanceof Error ? error.message : String(error),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const saveNodeCommand = new SaveNodeCommand();
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { CLIArgs, CommandResult, CliCommand } from '../types.js';
|
|
3
|
+
import { resolveProjectRoot } from '../utils.js';
|
|
4
|
+
import {
|
|
5
|
+
loadSkillConfig,
|
|
6
|
+
setSkillEnabled,
|
|
7
|
+
CONFIG_FILENAME,
|
|
8
|
+
} from '@openweave/weave-skills';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SkillsCommand ā manage OpenWeave skill modules
|
|
12
|
+
*
|
|
13
|
+
* Subcommands:
|
|
14
|
+
* weave skills list List all skills and their enabled state
|
|
15
|
+
* weave skills enable <id> Enable a skill in .weave.config.json
|
|
16
|
+
* weave skills disable <id> Disable a skill in .weave.config.json
|
|
17
|
+
* weave skills info <id> Show config entry for a skill
|
|
18
|
+
*/
|
|
19
|
+
export const skillsCommand: CliCommand = {
|
|
20
|
+
name: 'skills',
|
|
21
|
+
description: 'Manage OpenWeave skill modules',
|
|
22
|
+
usage: 'weave skills <list|enable|disable|info> [skill-id]',
|
|
23
|
+
flags: {
|
|
24
|
+
json: {
|
|
25
|
+
short: 'j',
|
|
26
|
+
description: 'Output as JSON',
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
async execute(args: CLIArgs): Promise<CommandResult> {
|
|
32
|
+
const sub = args.args[0] ?? 'list';
|
|
33
|
+
const skillId = args.args[1];
|
|
34
|
+
const outputJson = Boolean(args.flags.json);
|
|
35
|
+
const projectRoot = resolveProjectRoot();
|
|
36
|
+
|
|
37
|
+
switch (sub) {
|
|
38
|
+
case 'list':
|
|
39
|
+
return handleList(projectRoot, outputJson);
|
|
40
|
+
|
|
41
|
+
case 'enable': {
|
|
42
|
+
if (!skillId) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
message: 'ā Usage: weave skills enable <skill-id>',
|
|
46
|
+
error: 'Missing skill id',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return handleSetEnabled(projectRoot, skillId, true, outputJson);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case 'disable': {
|
|
53
|
+
if (!skillId) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
message: 'ā Usage: weave skills disable <skill-id>',
|
|
57
|
+
error: 'Missing skill id',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return handleSetEnabled(projectRoot, skillId, false, outputJson);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'info': {
|
|
64
|
+
if (!skillId) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
message: 'ā Usage: weave skills info <skill-id>',
|
|
68
|
+
error: 'Missing skill id',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return handleInfo(projectRoot, skillId, outputJson);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
message: `ā Unknown subcommand: "${sub}"\n\nUsage: weave skills <list|enable|disable|info>`,
|
|
78
|
+
error: `Unknown subcommand: ${sub}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Handlers
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
function handleList(projectRoot: string, outputJson: boolean): CommandResult {
|
|
89
|
+
const config = loadSkillConfig(projectRoot);
|
|
90
|
+
const entries = Object.entries(config.skills);
|
|
91
|
+
|
|
92
|
+
if (outputJson) {
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
message: JSON.stringify(config.skills, null, 2),
|
|
96
|
+
data: config.skills,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const configFile = join(projectRoot, CONFIG_FILENAME);
|
|
101
|
+
|
|
102
|
+
if (entries.length === 0) {
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
message: [
|
|
106
|
+
'',
|
|
107
|
+
'š§ OpenWeave Skills',
|
|
108
|
+
'ā'.repeat(50),
|
|
109
|
+
'',
|
|
110
|
+
' No skills configured yet.',
|
|
111
|
+
'',
|
|
112
|
+
' Skills are enabled/disabled in:',
|
|
113
|
+
` ${configFile}`,
|
|
114
|
+
'',
|
|
115
|
+
' Example:',
|
|
116
|
+
' weave skills enable auto-fix',
|
|
117
|
+
' weave skills enable code-review',
|
|
118
|
+
'',
|
|
119
|
+
' Available skill ids (Phase 9):',
|
|
120
|
+
' auto-fix Ā· code-review Ā· test-gen Ā· docs-gen Ā· refactor',
|
|
121
|
+
' pipeline-aware Ā· dep-audit Ā· perf-profile Ā· container-advisor Ā· deploy-provision',
|
|
122
|
+
' onboarding Ā· commit-composer Ā· context-memory Ā· multi-repo Ā· cli-interactive',
|
|
123
|
+
'',
|
|
124
|
+
].join('\n'),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const rows = entries.map(([id, enabled]) => {
|
|
129
|
+
const icon = enabled ? 'ā
' : 'ā¬';
|
|
130
|
+
const state = enabled ? 'enabled ' : 'disabled';
|
|
131
|
+
return ` ${icon} ${state} ${id}`;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
message: [
|
|
137
|
+
'',
|
|
138
|
+
'š§ OpenWeave Skills',
|
|
139
|
+
'ā'.repeat(50),
|
|
140
|
+
'',
|
|
141
|
+
...rows,
|
|
142
|
+
'',
|
|
143
|
+
` Config: ${configFile}`,
|
|
144
|
+
'',
|
|
145
|
+
' weave skills enable <id> ā activate a skill',
|
|
146
|
+
' weave skills disable <id> ā deactivate a skill',
|
|
147
|
+
'',
|
|
148
|
+
].join('\n'),
|
|
149
|
+
data: config.skills,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleSetEnabled(
|
|
154
|
+
projectRoot: string,
|
|
155
|
+
skillId: string,
|
|
156
|
+
enabled: boolean,
|
|
157
|
+
outputJson: boolean
|
|
158
|
+
): CommandResult {
|
|
159
|
+
try {
|
|
160
|
+
setSkillEnabled(skillId, enabled, projectRoot);
|
|
161
|
+
|
|
162
|
+
const action = enabled ? 'enabled' : 'disabled';
|
|
163
|
+
const icon = enabled ? 'ā
' : 'ā¬';
|
|
164
|
+
const data = { skillId, enabled };
|
|
165
|
+
|
|
166
|
+
if (outputJson) {
|
|
167
|
+
return { success: true, message: JSON.stringify(data, null, 2), data };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
success: true,
|
|
172
|
+
message: `\n${icon} Skill '${skillId}' ${action} in ${CONFIG_FILENAME}\n`,
|
|
173
|
+
data,
|
|
174
|
+
};
|
|
175
|
+
} catch (err) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
message: `ā Failed to update skill config`,
|
|
179
|
+
error: err instanceof Error ? err.message : String(err),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function handleInfo(projectRoot: string, skillId: string, outputJson: boolean): CommandResult {
|
|
185
|
+
const config = loadSkillConfig(projectRoot);
|
|
186
|
+
const hasEntry = skillId in config.skills;
|
|
187
|
+
|
|
188
|
+
if (!hasEntry) {
|
|
189
|
+
if (outputJson) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
message: JSON.stringify({ skillId, configured: false }),
|
|
193
|
+
data: { skillId, configured: false },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
message: [
|
|
199
|
+
'',
|
|
200
|
+
`ā ļø Skill '${skillId}' is not in ${CONFIG_FILENAME}`,
|
|
201
|
+
'',
|
|
202
|
+
` To add it:`,
|
|
203
|
+
` weave skills enable ${skillId}`,
|
|
204
|
+
` weave skills disable ${skillId}`,
|
|
205
|
+
'',
|
|
206
|
+
].join('\n'),
|
|
207
|
+
error: `Skill '${skillId}' not configured`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const enabled = config.skills[skillId];
|
|
212
|
+
const data = { skillId, enabled, configured: true };
|
|
213
|
+
|
|
214
|
+
if (outputJson) {
|
|
215
|
+
return { success: true, message: JSON.stringify(data, null, 2), data };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
message: [
|
|
221
|
+
'',
|
|
222
|
+
`š§ Skill: ${skillId}`,
|
|
223
|
+
'ā'.repeat(40),
|
|
224
|
+
` Status : ${enabled ? 'ā
enabled' : '⬠disabled'}`,
|
|
225
|
+
` Config : ${join(projectRoot, CONFIG_FILENAME)}`,
|
|
226
|
+
'',
|
|
227
|
+
].join('\n'),
|
|
228
|
+
data,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { CLIArgs, CommandResult, CliCommand, CLIConfig, ProjectState } from '../types';
|
|
4
|
+
import { resolveProjectRoot } from '../utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Status Command - Display project status
|
|
8
|
+
*/
|
|
9
|
+
export class StatusCommand implements CliCommand {
|
|
10
|
+
name = 'status';
|
|
11
|
+
description = 'Show current project status and progress';
|
|
12
|
+
usage = 'weave status [--verbose]';
|
|
13
|
+
flags = {
|
|
14
|
+
verbose: {
|
|
15
|
+
short: 'v',
|
|
16
|
+
description: 'Show detailed information',
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
json: {
|
|
20
|
+
short: 'j',
|
|
21
|
+
description: 'Output as JSON',
|
|
22
|
+
default: false,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
async execute(args: CLIArgs): Promise<CommandResult> {
|
|
27
|
+
try {
|
|
28
|
+
const projectRoot = resolveProjectRoot();
|
|
29
|
+
const weaveDir = join(projectRoot, '.weave');
|
|
30
|
+
const configPath = join(weaveDir, 'config.json');
|
|
31
|
+
|
|
32
|
+
if (!existsSync(configPath)) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
message: 'Error: .weave directory not found',
|
|
36
|
+
error: 'Please run "weave init <project-name>" first',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const configContent = readFileSync(configPath, 'utf-8');
|
|
41
|
+
const config: CLIConfig = JSON.parse(configContent);
|
|
42
|
+
|
|
43
|
+
const graphPath = join(weaveDir, 'graph.json');
|
|
44
|
+
let graphData = { nodes: [], edges: [], sessions: {} };
|
|
45
|
+
|
|
46
|
+
if (existsSync(graphPath)) {
|
|
47
|
+
const graphContent = readFileSync(graphPath, 'utf-8');
|
|
48
|
+
graphData = JSON.parse(graphContent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const projectState: ProjectState = {
|
|
52
|
+
created_at: new Date(),
|
|
53
|
+
last_updated: new Date(),
|
|
54
|
+
session_id: `session_${Date.now()}`,
|
|
55
|
+
milestones: 0,
|
|
56
|
+
total_nodes: graphData.nodes.length,
|
|
57
|
+
total_edges: graphData.edges.length,
|
|
58
|
+
context_usage_percent: Math.min(
|
|
59
|
+
100,
|
|
60
|
+
((graphData.nodes.length + graphData.edges.length) / 10000) * 100
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const outputJson = args.flags.json as boolean;
|
|
65
|
+
|
|
66
|
+
if (outputJson) {
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
message: JSON.stringify(projectState, null, 2),
|
|
70
|
+
data: projectState,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const verbose = args.flags.verbose as boolean;
|
|
75
|
+
|
|
76
|
+
const statusMessage = `
|
|
77
|
+
š Project Status: ${config.project_name}
|
|
78
|
+
${'-'.repeat(50)}
|
|
79
|
+
|
|
80
|
+
š Location: ${config.project_root}
|
|
81
|
+
š Session: ${projectState.session_id}
|
|
82
|
+
|
|
83
|
+
š Graph Statistics:
|
|
84
|
+
⢠Nodes: ${projectState.total_nodes}
|
|
85
|
+
⢠Edges: ${projectState.total_edges}
|
|
86
|
+
⢠Context Usage: ${projectState.context_usage_percent.toFixed(1)}%
|
|
87
|
+
|
|
88
|
+
${verbose ? this.getVerboseInfo(config) : ''}
|
|
89
|
+
|
|
90
|
+
⨠Quick Commands:
|
|
91
|
+
⢠weave query <term> - Search the knowledge graph
|
|
92
|
+
⢠weave orphans - Analyze code for unused exports
|
|
93
|
+
⢠weave milestones - View milestone progress
|
|
94
|
+
⢠weave errors - Show error registry
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
message: statusMessage,
|
|
100
|
+
data: projectState,
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
message: 'Error reading project status',
|
|
106
|
+
error: error instanceof Error ? error.message : String(error),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getVerboseInfo(config: CLIConfig): string {
|
|
112
|
+
return `
|
|
113
|
+
āļø Configuration:
|
|
114
|
+
⢠Include Tests: ${config.include_tests}
|
|
115
|
+
⢠Max Context Depth: ${config.max_context_depth}
|
|
116
|
+
⢠Verbose Mode: ${config.verbose}
|
|
117
|
+
⢠Debug Mode: ${config.debug}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const statusCommand = new StatusCommand();
|