lightspec 0.4.0 → 0.5.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/README.md +25 -14
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +68 -17
- package/dist/core/configurators/registry.js +6 -5
- package/dist/core/configurators/skills/base.d.ts +8 -1
- package/dist/core/configurators/skills/base.js +235 -54
- package/dist/core/configurators/skills/registry.js +2 -2
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +27 -40
- package/dist/core/templates/agents-root-stub.d.ts +1 -1
- package/dist/core/templates/agents-root-stub.js +7 -3
- package/dist/core/update.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -82,28 +82,39 @@ See the full comparison in [How LightSpec Compares](#how-lightspec-compares).
|
|
|
82
82
|
|
|
83
83
|
### Supported AI Tools
|
|
84
84
|
|
|
85
|
+
- AdaL
|
|
85
86
|
- Amazon Q Developer
|
|
86
|
-
-
|
|
87
|
-
- Auggie (Augment CLI)
|
|
87
|
+
- Augment
|
|
88
88
|
- Claude Code
|
|
89
89
|
- Cline
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
- Continue
|
|
90
|
+
- CodeBuddy
|
|
91
|
+
- Command Code
|
|
92
|
+
- Continue
|
|
93
93
|
- CoStrict
|
|
94
|
+
- Cortex Code
|
|
94
95
|
- Crush
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
- GitHub Copilot
|
|
99
|
-
- iFlow
|
|
96
|
+
- Droid
|
|
97
|
+
- iFlow CLI
|
|
98
|
+
- Junie
|
|
100
99
|
- Kilo Code
|
|
100
|
+
- Kiro CLI
|
|
101
|
+
- Kode
|
|
102
|
+
- MCPJam
|
|
101
103
|
- Mistral Vibe
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
+
- Mux
|
|
105
|
+
- Neovate
|
|
106
|
+
- OpenClaw
|
|
107
|
+
- OpenHands
|
|
108
|
+
- Pochi
|
|
109
|
+
- Pi
|
|
110
|
+
- Qoder
|
|
104
111
|
- Qwen Code
|
|
105
|
-
-
|
|
112
|
+
- Roo Code
|
|
113
|
+
- Trae
|
|
114
|
+
- Trae CN
|
|
115
|
+
- Universal agent skills (`.agents`, for Codex, Amp, VS Code, Zed, Warp, Goose, Cursor, Gemini CLI, GitHub Copilot, OpenCode, Replit, and similar assistants)
|
|
106
116
|
- Windsurf
|
|
117
|
+
- Zencoder
|
|
107
118
|
- Any AGENTS.md-compatible assistant (via Universal `AGENTS.md`)
|
|
108
119
|
|
|
109
120
|
### Install & Initialize
|
|
@@ -170,7 +181,7 @@ lightspec init
|
|
|
170
181
|
```
|
|
171
182
|
|
|
172
183
|
**What happens during initialization:**
|
|
173
|
-
- You'll be prompted to pick any natively supported AI tools
|
|
184
|
+
- You'll be prompted to pick any natively supported AI tools using the current LightSpec provider IDs and install paths (for example `claude-code`, `cline`, `costrict`, `qoder`, `qwen-code`, `roo`, `universal`)
|
|
174
185
|
- LightSpec automatically configures skills for the tools you choose and always writes a managed `AGENTS.md` hand-off at the project root
|
|
175
186
|
- A new `lightspec/` directory structure is created in your project
|
|
176
187
|
|
package/dist/core/config.d.ts
CHANGED
|
@@ -13,5 +13,7 @@ export interface AIToolOption {
|
|
|
13
13
|
available: boolean;
|
|
14
14
|
successLabel?: string;
|
|
15
15
|
}
|
|
16
|
+
export declare const LEGACY_TOOL_ALIASES: Record<string, string>;
|
|
17
|
+
export declare function normalizeToolId(toolId: string): string;
|
|
16
18
|
export declare const AI_TOOLS: AIToolOption[];
|
|
17
19
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/core/config.js
CHANGED
|
@@ -3,29 +3,80 @@ export const LIGHTSPEC_MARKERS = {
|
|
|
3
3
|
start: '<!-- LIGHTSPEC:START -->',
|
|
4
4
|
end: '<!-- LIGHTSPEC:END -->'
|
|
5
5
|
};
|
|
6
|
+
const UNIVERSAL_AGENTS_SUPPORTED_PROVIDERS = [
|
|
7
|
+
'Codex',
|
|
8
|
+
'Amp',
|
|
9
|
+
'VS Code',
|
|
10
|
+
'Zed',
|
|
11
|
+
'Warp',
|
|
12
|
+
'Goose',
|
|
13
|
+
];
|
|
14
|
+
const UNIVERSAL_AGENTS_PROVIDER_PREVIEW_COUNT = 5;
|
|
15
|
+
const universalAgentsProviderPreview = UNIVERSAL_AGENTS_SUPPORTED_PROVIDERS.slice(0, UNIVERSAL_AGENTS_PROVIDER_PREVIEW_COUNT).join(', ');
|
|
16
|
+
const universalAgentsProviderSuffix = UNIVERSAL_AGENTS_SUPPORTED_PROVIDERS.length > UNIVERSAL_AGENTS_PROVIDER_PREVIEW_COUNT
|
|
17
|
+
? ', ...'
|
|
18
|
+
: '';
|
|
19
|
+
const UNIVERSAL_AGENTS_OPTION_LABEL = `Universal agent skills (${universalAgentsProviderPreview}${universalAgentsProviderSuffix})`;
|
|
20
|
+
export const LEGACY_TOOL_ALIASES = {
|
|
21
|
+
agents: 'universal',
|
|
22
|
+
amp: 'universal',
|
|
23
|
+
antigravity: 'universal',
|
|
24
|
+
auggie: 'augment',
|
|
25
|
+
claude: 'claude-code',
|
|
26
|
+
codex: 'universal',
|
|
27
|
+
cursor: 'universal',
|
|
28
|
+
deepagents: 'universal',
|
|
29
|
+
factory: 'droid',
|
|
30
|
+
gemini: 'universal',
|
|
31
|
+
'gemini-cli': 'universal',
|
|
32
|
+
goose: 'universal',
|
|
33
|
+
'github-copilot': 'universal',
|
|
34
|
+
iflow: 'iflow-cli',
|
|
35
|
+
'kimi-cli': 'universal',
|
|
36
|
+
kilocode: 'kilo',
|
|
37
|
+
opencode: 'universal',
|
|
38
|
+
qwen: 'qwen-code',
|
|
39
|
+
replit: 'universal',
|
|
40
|
+
roocode: 'roo',
|
|
41
|
+
warp: 'universal',
|
|
42
|
+
};
|
|
43
|
+
export function normalizeToolId(toolId) {
|
|
44
|
+
const normalized = toolId.trim().toLowerCase();
|
|
45
|
+
return LEGACY_TOOL_ALIASES[normalized] ?? normalized;
|
|
46
|
+
}
|
|
6
47
|
export const AI_TOOLS = [
|
|
48
|
+
{ name: UNIVERSAL_AGENTS_OPTION_LABEL, value: 'universal', available: true, successLabel: 'Universal agent skills' },
|
|
7
49
|
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer' },
|
|
8
|
-
{ name: '
|
|
9
|
-
{ name: '
|
|
10
|
-
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code' },
|
|
50
|
+
{ name: 'Augment', value: 'augment', available: true, successLabel: 'Augment' },
|
|
51
|
+
{ name: 'Claude Code', value: 'claude-code', available: true, successLabel: 'Claude Code' },
|
|
11
52
|
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline' },
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: 'Continue', value: 'continue', available: true, successLabel: 'Continue
|
|
53
|
+
{ name: 'CodeBuddy', value: 'codebuddy', available: true, successLabel: 'CodeBuddy' },
|
|
54
|
+
{ name: 'Command Code', value: 'command-code', available: true, successLabel: 'Command Code' },
|
|
55
|
+
{ name: 'Continue', value: 'continue', available: true, successLabel: 'Continue' },
|
|
15
56
|
{ name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict' },
|
|
57
|
+
{ name: 'Cortex Code', value: 'cortex', available: true, successLabel: 'Cortex Code' },
|
|
16
58
|
{ name: 'Crush', value: 'crush', available: true, successLabel: 'Crush' },
|
|
17
|
-
{ name: '
|
|
18
|
-
{ name: '
|
|
19
|
-
{ name: '
|
|
20
|
-
{ name: '
|
|
21
|
-
{ name: '
|
|
22
|
-
{ name: '
|
|
59
|
+
{ name: 'Droid', value: 'droid', available: true, successLabel: 'Droid' },
|
|
60
|
+
{ name: 'iFlow CLI', value: 'iflow-cli', available: true, successLabel: 'iFlow CLI' },
|
|
61
|
+
{ name: 'Junie', value: 'junie', available: true, successLabel: 'Junie' },
|
|
62
|
+
{ name: 'Kilo Code', value: 'kilo', available: true, successLabel: 'Kilo Code' },
|
|
63
|
+
{ name: 'Kiro CLI', value: 'kiro-cli', available: true, successLabel: 'Kiro CLI' },
|
|
64
|
+
{ name: 'Kode', value: 'kode', available: true, successLabel: 'Kode' },
|
|
65
|
+
{ name: 'MCPJam', value: 'mcpjam', available: true, successLabel: 'MCPJam' },
|
|
23
66
|
{ name: 'Mistral Vibe', value: 'mistral-vibe', available: true, successLabel: 'Mistral Vibe' },
|
|
24
|
-
{ name: '
|
|
25
|
-
{ name: '
|
|
26
|
-
{ name: '
|
|
27
|
-
{ name: '
|
|
67
|
+
{ name: 'Mux', value: 'mux', available: true, successLabel: 'Mux' },
|
|
68
|
+
{ name: 'Neovate', value: 'neovate', available: true, successLabel: 'Neovate' },
|
|
69
|
+
{ name: 'OpenClaw', value: 'openclaw', available: true, successLabel: 'OpenClaw' },
|
|
70
|
+
{ name: 'OpenHands', value: 'openhands', available: true, successLabel: 'OpenHands' },
|
|
71
|
+
{ name: 'Pochi', value: 'pochi', available: true, successLabel: 'Pochi' },
|
|
72
|
+
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi' },
|
|
73
|
+
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder' },
|
|
74
|
+
{ name: 'Qwen Code', value: 'qwen-code', available: true, successLabel: 'Qwen Code' },
|
|
75
|
+
{ name: 'Roo Code', value: 'roo', available: true, successLabel: 'Roo Code' },
|
|
76
|
+
{ name: 'Trae', value: 'trae', available: true, successLabel: 'Trae' },
|
|
77
|
+
{ name: 'Trae CN', value: 'trae-cn', available: true, successLabel: 'Trae CN' },
|
|
28
78
|
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
|
|
29
|
-
{ name: '
|
|
79
|
+
{ name: 'Zencoder', value: 'zencoder', available: true, successLabel: 'Zencoder' },
|
|
80
|
+
{ name: 'AdaL', value: 'adal', available: true, successLabel: 'AdaL' },
|
|
30
81
|
];
|
|
31
82
|
//# sourceMappingURL=config.js.map
|
|
@@ -6,6 +6,7 @@ import { QoderConfigurator } from './qoder.js';
|
|
|
6
6
|
import { IflowConfigurator } from './iflow.js';
|
|
7
7
|
import { AgentsStandardConfigurator } from './agents.js';
|
|
8
8
|
import { QwenConfigurator } from './qwen.js';
|
|
9
|
+
import { normalizeToolId } from '../config.js';
|
|
9
10
|
export class ToolRegistry {
|
|
10
11
|
static tools = new Map();
|
|
11
12
|
static {
|
|
@@ -18,20 +19,20 @@ export class ToolRegistry {
|
|
|
18
19
|
const agentsConfigurator = new AgentsStandardConfigurator();
|
|
19
20
|
const qwenConfigurator = new QwenConfigurator();
|
|
20
21
|
// Register with the ID that matches the checkbox value
|
|
21
|
-
this.tools.set('claude', claudeConfigurator);
|
|
22
|
+
this.tools.set('claude-code', claudeConfigurator);
|
|
22
23
|
this.tools.set('cline', clineConfigurator);
|
|
23
24
|
this.tools.set('codebuddy', codeBuddyConfigurator);
|
|
24
25
|
this.tools.set('costrict', costrictConfigurator);
|
|
25
26
|
this.tools.set('qoder', qoderConfigurator);
|
|
26
|
-
this.tools.set('iflow', iflowConfigurator);
|
|
27
|
-
this.tools.set('
|
|
28
|
-
this.tools.set('qwen', qwenConfigurator);
|
|
27
|
+
this.tools.set('iflow-cli', iflowConfigurator);
|
|
28
|
+
this.tools.set('universal', agentsConfigurator);
|
|
29
|
+
this.tools.set('qwen-code', qwenConfigurator);
|
|
29
30
|
}
|
|
30
31
|
static register(tool) {
|
|
31
32
|
this.tools.set(tool.name.toLowerCase().replace(/\s+/g, '-'), tool);
|
|
32
33
|
}
|
|
33
34
|
static get(toolId) {
|
|
34
|
-
return this.tools.get(toolId);
|
|
35
|
+
return this.tools.get(normalizeToolId(toolId));
|
|
35
36
|
}
|
|
36
37
|
static getAll() {
|
|
37
38
|
return Array.from(this.tools.values());
|
|
@@ -5,6 +5,7 @@ export interface AgentSkillTarget {
|
|
|
5
5
|
kind: 'skill';
|
|
6
6
|
}
|
|
7
7
|
export type SkillInstallLocation = 'project' | 'home';
|
|
8
|
+
export declare function normalizeAgentSkillToolId(toolId: string): string;
|
|
8
9
|
export declare const AGENT_SKILL_TOOL_IDS: readonly string[];
|
|
9
10
|
export declare class AgentSkillConfigurator {
|
|
10
11
|
readonly toolId: string;
|
|
@@ -17,8 +18,14 @@ export declare class AgentSkillConfigurator {
|
|
|
17
18
|
updateExisting(projectPath: string, _lightspecDir: string): Promise<string[]>;
|
|
18
19
|
protected getBody(id: AgentSkillId): string;
|
|
19
20
|
resolveAbsolutePath(projectPath: string, id: AgentSkillId): string;
|
|
21
|
+
resolveExistingAbsolutePaths(projectPath: string, id: AgentSkillId): Array<{
|
|
22
|
+
absolutePath: string;
|
|
23
|
+
relativePath: string;
|
|
24
|
+
}>;
|
|
20
25
|
private getRelativeSkillPath;
|
|
21
|
-
private
|
|
26
|
+
private getAllRelativeSkillPaths;
|
|
27
|
+
private resolvePathFromRelative;
|
|
28
|
+
private getDescriptor;
|
|
22
29
|
private getHomeRootPath;
|
|
23
30
|
private getSkillName;
|
|
24
31
|
private buildSkillFile;
|
|
@@ -1,46 +1,210 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
|
-
import path from 'path';
|
|
3
2
|
import { FileSystemUtils } from '../../../utils/file-system.js';
|
|
4
3
|
import { TemplateManager } from '../../templates/index.js';
|
|
5
|
-
import { LIGHTSPEC_MARKERS } from '../../config.js';
|
|
4
|
+
import { LIGHTSPEC_MARKERS, normalizeToolId } from '../../config.js';
|
|
6
5
|
const ALL_SKILL_IDS = ['proposal', 'apply', 'archive', 'agentsmd-check'];
|
|
7
|
-
const
|
|
8
|
-
'amazon-q':
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
6
|
+
const TOOL_SKILL_DESCRIPTORS = {
|
|
7
|
+
'amazon-q': {
|
|
8
|
+
projectSkillDir: '.amazonq/skills',
|
|
9
|
+
homeSkillDir: '.amazonq/skills',
|
|
10
|
+
},
|
|
11
|
+
adal: {
|
|
12
|
+
projectSkillDir: '.adal/skills',
|
|
13
|
+
homeSkillDir: '.adal/skills',
|
|
14
|
+
},
|
|
15
|
+
augment: {
|
|
16
|
+
projectSkillDir: '.augment/skills',
|
|
17
|
+
homeSkillDir: '.augment/skills',
|
|
18
|
+
aliases: ['auggie'],
|
|
19
|
+
legacyProjectSkillDirs: ['.auggie/skills'],
|
|
20
|
+
legacyHomeSkillDirs: ['.auggie/skills'],
|
|
21
|
+
},
|
|
22
|
+
'claude-code': {
|
|
23
|
+
projectSkillDir: '.claude/skills',
|
|
24
|
+
homeSkillDir: '.claude/skills',
|
|
25
|
+
aliases: ['claude'],
|
|
26
|
+
},
|
|
27
|
+
cline: {
|
|
28
|
+
projectSkillDir: '.agents/skills',
|
|
29
|
+
homeSkillDir: '.agents/skills',
|
|
30
|
+
legacyProjectSkillDirs: ['.cline/skills'],
|
|
31
|
+
legacyHomeSkillDirs: ['.cline/skills'],
|
|
32
|
+
},
|
|
33
|
+
codebuddy: {
|
|
34
|
+
projectSkillDir: '.codebuddy/skills',
|
|
35
|
+
homeSkillDir: '.codebuddy/skills',
|
|
36
|
+
},
|
|
37
|
+
'command-code': {
|
|
38
|
+
projectSkillDir: '.commandcode/skills',
|
|
39
|
+
homeSkillDir: '.commandcode/skills',
|
|
40
|
+
},
|
|
41
|
+
continue: {
|
|
42
|
+
projectSkillDir: '.continue/skills',
|
|
43
|
+
homeSkillDir: '.continue/skills',
|
|
44
|
+
},
|
|
45
|
+
costrict: {
|
|
46
|
+
projectSkillDir: '.cospec/lightspec/skills',
|
|
47
|
+
homeSkillDir: '.cospec/lightspec/skills',
|
|
48
|
+
},
|
|
49
|
+
cortex: {
|
|
50
|
+
projectSkillDir: '.cortex/skills',
|
|
51
|
+
homeSkillDir: '.snowflake/cortex/skills',
|
|
52
|
+
},
|
|
53
|
+
crush: {
|
|
54
|
+
projectSkillDir: '.crush/skills',
|
|
55
|
+
homeSkillDir: '.config/crush/skills',
|
|
56
|
+
legacyHomeSkillDirs: ['.crush/skills'],
|
|
57
|
+
},
|
|
58
|
+
droid: {
|
|
59
|
+
projectSkillDir: '.factory/skills',
|
|
60
|
+
homeSkillDir: '.factory/skills',
|
|
61
|
+
aliases: ['factory'],
|
|
62
|
+
},
|
|
63
|
+
'iflow-cli': {
|
|
64
|
+
projectSkillDir: '.iflow/skills',
|
|
65
|
+
homeSkillDir: '.iflow/skills',
|
|
66
|
+
aliases: ['iflow'],
|
|
67
|
+
},
|
|
68
|
+
junie: {
|
|
69
|
+
projectSkillDir: '.junie/skills',
|
|
70
|
+
homeSkillDir: '.junie/skills',
|
|
71
|
+
},
|
|
72
|
+
kilo: {
|
|
73
|
+
projectSkillDir: '.kilocode/skills',
|
|
74
|
+
homeSkillDir: '.kilocode/skills',
|
|
75
|
+
aliases: ['kilocode'],
|
|
76
|
+
},
|
|
77
|
+
'kiro-cli': {
|
|
78
|
+
projectSkillDir: '.kiro/skills',
|
|
79
|
+
homeSkillDir: '.kiro/skills',
|
|
80
|
+
},
|
|
81
|
+
kode: {
|
|
82
|
+
projectSkillDir: '.kode/skills',
|
|
83
|
+
homeSkillDir: '.kode/skills',
|
|
84
|
+
},
|
|
85
|
+
mcpjam: {
|
|
86
|
+
projectSkillDir: '.mcpjam/skills',
|
|
87
|
+
homeSkillDir: '.mcpjam/skills',
|
|
88
|
+
},
|
|
89
|
+
'mistral-vibe': {
|
|
90
|
+
projectSkillDir: '.vibe/skills',
|
|
91
|
+
homeSkillDir: '.vibe/skills',
|
|
92
|
+
},
|
|
93
|
+
mux: {
|
|
94
|
+
projectSkillDir: '.mux/skills',
|
|
95
|
+
homeSkillDir: '.mux/skills',
|
|
96
|
+
},
|
|
97
|
+
neovate: {
|
|
98
|
+
projectSkillDir: '.neovate/skills',
|
|
99
|
+
homeSkillDir: '.neovate/skills',
|
|
100
|
+
},
|
|
101
|
+
openclaw: {
|
|
102
|
+
projectSkillDir: 'skills',
|
|
103
|
+
homeSkillDir: '.openclaw/skills',
|
|
104
|
+
},
|
|
105
|
+
openhands: {
|
|
106
|
+
projectSkillDir: '.openhands/skills',
|
|
107
|
+
homeSkillDir: '.openhands/skills',
|
|
108
|
+
},
|
|
109
|
+
pochi: {
|
|
110
|
+
projectSkillDir: '.pochi/skills',
|
|
111
|
+
homeSkillDir: '.pochi/skills',
|
|
112
|
+
},
|
|
113
|
+
pi: {
|
|
114
|
+
projectSkillDir: '.pi/skills',
|
|
115
|
+
homeSkillDir: '.pi/agent/skills',
|
|
116
|
+
},
|
|
117
|
+
qoder: {
|
|
118
|
+
projectSkillDir: '.qoder/skills',
|
|
119
|
+
homeSkillDir: '.qoder/skills',
|
|
120
|
+
},
|
|
121
|
+
'qwen-code': {
|
|
122
|
+
projectSkillDir: '.qwen/skills',
|
|
123
|
+
homeSkillDir: '.qwen/skills',
|
|
124
|
+
aliases: ['qwen'],
|
|
125
|
+
},
|
|
126
|
+
roo: {
|
|
127
|
+
projectSkillDir: '.roo/skills',
|
|
128
|
+
homeSkillDir: '.roo/skills',
|
|
129
|
+
aliases: ['roocode'],
|
|
130
|
+
legacyProjectSkillDirs: ['.roocode/skills'],
|
|
131
|
+
legacyHomeSkillDirs: ['.roocode/skills'],
|
|
132
|
+
},
|
|
133
|
+
'trae-cn': {
|
|
134
|
+
projectSkillDir: '.trae/skills',
|
|
135
|
+
homeSkillDir: '.trae-cn/skills',
|
|
136
|
+
},
|
|
137
|
+
trae: {
|
|
138
|
+
projectSkillDir: '.trae/skills',
|
|
139
|
+
homeSkillDir: '.trae/skills',
|
|
140
|
+
},
|
|
141
|
+
universal: {
|
|
142
|
+
projectSkillDir: '.agents/skills',
|
|
143
|
+
homeSkillDir: '.config/agents/skills',
|
|
144
|
+
aliases: [
|
|
145
|
+
'agents',
|
|
146
|
+
'amp',
|
|
147
|
+
'antigravity',
|
|
148
|
+
'codex',
|
|
149
|
+
'cursor',
|
|
150
|
+
'deepagents',
|
|
151
|
+
'gemini',
|
|
152
|
+
'gemini-cli',
|
|
153
|
+
'github-copilot',
|
|
154
|
+
'goose',
|
|
155
|
+
'kimi-cli',
|
|
156
|
+
'opencode',
|
|
157
|
+
'replit',
|
|
158
|
+
'warp',
|
|
159
|
+
],
|
|
160
|
+
legacyProjectSkillDirs: [
|
|
161
|
+
'.antigravity/skills',
|
|
162
|
+
'.cursor/skills',
|
|
163
|
+
'.codex/skills',
|
|
164
|
+
'.gemini/skills',
|
|
165
|
+
'.github/copilot/skills',
|
|
166
|
+
'.opencode/skills',
|
|
167
|
+
],
|
|
168
|
+
legacyHomeSkillDirs: [
|
|
169
|
+
'.agents/skills',
|
|
170
|
+
'.antigravity/skills',
|
|
171
|
+
'.cursor/skills',
|
|
172
|
+
'.codex/skills',
|
|
173
|
+
'.deepagents/agent/skills',
|
|
174
|
+
'.gemini/skills',
|
|
175
|
+
'.github/copilot/skills',
|
|
176
|
+
'.copilot/skills',
|
|
177
|
+
'.opencode/skills',
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
windsurf: {
|
|
181
|
+
projectSkillDir: '.windsurf/skills',
|
|
182
|
+
homeSkillDir: '.codeium/windsurf/skills',
|
|
183
|
+
legacyHomeSkillDirs: ['.windsurf/skills'],
|
|
184
|
+
},
|
|
185
|
+
zencoder: {
|
|
186
|
+
projectSkillDir: '.zencoder/skills',
|
|
187
|
+
homeSkillDir: '.zencoder/skills',
|
|
188
|
+
},
|
|
30
189
|
};
|
|
31
|
-
|
|
190
|
+
const TOOL_ID_ALIASES = Object.freeze(Object.fromEntries(Object.entries(TOOL_SKILL_DESCRIPTORS).flatMap(([toolId, descriptor]) => (descriptor.aliases ?? []).map((alias) => [alias, toolId]))));
|
|
191
|
+
export function normalizeAgentSkillToolId(toolId) {
|
|
192
|
+
const normalized = normalizeToolId(toolId);
|
|
193
|
+
return TOOL_ID_ALIASES[normalized] ?? normalized;
|
|
194
|
+
}
|
|
195
|
+
export const AGENT_SKILL_TOOL_IDS = Object.freeze(Object.keys(TOOL_SKILL_DESCRIPTORS));
|
|
32
196
|
const TOOL_BODY_SUFFIX = {
|
|
33
|
-
|
|
197
|
+
droid: '\n\n$ARGUMENTS',
|
|
34
198
|
};
|
|
35
199
|
export class AgentSkillConfigurator {
|
|
36
200
|
toolId;
|
|
37
201
|
isAvailable;
|
|
38
202
|
installLocation = 'project';
|
|
39
203
|
constructor(toolId, isAvailable = true) {
|
|
40
|
-
this.toolId = toolId;
|
|
204
|
+
this.toolId = normalizeAgentSkillToolId(toolId);
|
|
41
205
|
this.isAvailable = isAvailable;
|
|
42
|
-
if (!
|
|
43
|
-
throw new Error(`No skill root directory configured for tool '${
|
|
206
|
+
if (!TOOL_SKILL_DESCRIPTORS[this.toolId]) {
|
|
207
|
+
throw new Error(`No skill root directory configured for tool '${toolId}'`);
|
|
44
208
|
}
|
|
45
209
|
}
|
|
46
210
|
setInstallLocation(location) {
|
|
@@ -73,11 +237,14 @@ export class AgentSkillConfigurator {
|
|
|
73
237
|
async updateExisting(projectPath, _lightspecDir) {
|
|
74
238
|
const updated = [];
|
|
75
239
|
for (const target of this.getTargets()) {
|
|
76
|
-
const
|
|
77
|
-
|
|
240
|
+
const candidatePaths = this.resolveExistingAbsolutePaths(projectPath, target.id);
|
|
241
|
+
for (const candidate of candidatePaths) {
|
|
242
|
+
if (!await FileSystemUtils.fileExists(candidate.absolutePath)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
78
245
|
const body = this.getBody(target.id);
|
|
79
|
-
await this.updateBody(
|
|
80
|
-
updated.push(
|
|
246
|
+
await this.updateBody(candidate.absolutePath, body);
|
|
247
|
+
updated.push(candidate.relativePath);
|
|
81
248
|
}
|
|
82
249
|
}
|
|
83
250
|
return updated;
|
|
@@ -89,40 +256,54 @@ export class AgentSkillConfigurator {
|
|
|
89
256
|
}
|
|
90
257
|
resolveAbsolutePath(projectPath, id) {
|
|
91
258
|
const relativePath = this.getRelativeSkillPath(id);
|
|
259
|
+
return this.resolvePathFromRelative(projectPath, relativePath);
|
|
260
|
+
}
|
|
261
|
+
resolveExistingAbsolutePaths(projectPath, id) {
|
|
262
|
+
const relativePaths = this.getAllRelativeSkillPaths(id);
|
|
263
|
+
return relativePaths.map((relativePath) => ({
|
|
264
|
+
absolutePath: this.resolvePathFromRelative(projectPath, relativePath),
|
|
265
|
+
relativePath,
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
getRelativeSkillPath(id) {
|
|
269
|
+
const descriptor = this.getDescriptor();
|
|
270
|
+
const skillName = this.getSkillName(id);
|
|
271
|
+
const skillDir = this.installLocation === 'project'
|
|
272
|
+
? descriptor.projectSkillDir
|
|
273
|
+
: descriptor.homeSkillDir;
|
|
274
|
+
return `${skillDir}/${skillName}/SKILL.md`;
|
|
275
|
+
}
|
|
276
|
+
getAllRelativeSkillPaths(id) {
|
|
277
|
+
const descriptor = this.getDescriptor();
|
|
278
|
+
const skillName = this.getSkillName(id);
|
|
279
|
+
const skillDirs = this.installLocation === 'project'
|
|
280
|
+
? [descriptor.projectSkillDir, ...(descriptor.legacyProjectSkillDirs ?? [])]
|
|
281
|
+
: [descriptor.homeSkillDir, ...(descriptor.legacyHomeSkillDirs ?? [])];
|
|
282
|
+
return Array.from(new Set(skillDirs.map((dir) => `${dir}/${skillName}/SKILL.md`)));
|
|
283
|
+
}
|
|
284
|
+
resolvePathFromRelative(projectPath, relativePath) {
|
|
92
285
|
if (this.installLocation === 'project') {
|
|
93
286
|
return FileSystemUtils.joinPath(projectPath, relativePath);
|
|
94
287
|
}
|
|
95
288
|
const homeRoot = this.getHomeRootPath();
|
|
96
|
-
|
|
97
|
-
const normalizedRelativePath = FileSystemUtils.toPosixPath(relativePath);
|
|
98
|
-
if (!normalizedRelativePath.startsWith(`${rootPrefix}/`)) {
|
|
99
|
-
throw new Error(`Skill path '${relativePath}' does not match expected root '${rootPrefix}' for ${this.toolId}`);
|
|
100
|
-
}
|
|
101
|
-
const relativeUnderRoot = normalizedRelativePath.slice(rootPrefix.length + 1);
|
|
102
|
-
return FileSystemUtils.joinPath(homeRoot, relativeUnderRoot);
|
|
103
|
-
}
|
|
104
|
-
getRelativeSkillPath(id) {
|
|
105
|
-
const root = this.getToolRoot();
|
|
106
|
-
const skillName = this.getSkillName(id);
|
|
107
|
-
return `${root}/skills/${skillName}/SKILL.md`;
|
|
289
|
+
return FileSystemUtils.joinPath(homeRoot, relativePath);
|
|
108
290
|
}
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
291
|
+
getDescriptor() {
|
|
292
|
+
const descriptor = TOOL_SKILL_DESCRIPTORS[this.toolId];
|
|
293
|
+
if (!descriptor) {
|
|
112
294
|
throw new Error(`No skill root directory configured for tool '${this.toolId}'`);
|
|
113
295
|
}
|
|
114
|
-
return
|
|
296
|
+
return descriptor;
|
|
115
297
|
}
|
|
116
298
|
getHomeRootPath() {
|
|
117
|
-
|
|
299
|
+
const descriptor = this.getDescriptor();
|
|
300
|
+
if (descriptor.homeBase === 'codex-home') {
|
|
118
301
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
119
302
|
return codexHome && codexHome.length > 0
|
|
120
303
|
? codexHome
|
|
121
304
|
: FileSystemUtils.joinPath(os.homedir(), '.codex');
|
|
122
305
|
}
|
|
123
|
-
|
|
124
|
-
const trimmed = toolRoot.startsWith('./') ? toolRoot.slice(2) : toolRoot;
|
|
125
|
-
return path.join(os.homedir(), trimmed);
|
|
306
|
+
return os.homedir();
|
|
126
307
|
}
|
|
127
308
|
getSkillName(id) {
|
|
128
309
|
return `lightspec-${id}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGENT_SKILL_TOOL_IDS, AgentSkillConfigurator, } from './base.js';
|
|
1
|
+
import { AGENT_SKILL_TOOL_IDS, AgentSkillConfigurator, normalizeAgentSkillToolId, } from './base.js';
|
|
2
2
|
export class AgentSkillRegistry {
|
|
3
3
|
static configurators = new Map();
|
|
4
4
|
static {
|
|
@@ -10,7 +10,7 @@ export class AgentSkillRegistry {
|
|
|
10
10
|
this.configurators.set(configurator.toolId, configurator);
|
|
11
11
|
}
|
|
12
12
|
static get(toolId) {
|
|
13
|
-
return this.configurators.get(toolId);
|
|
13
|
+
return this.configurators.get(normalizeAgentSkillToolId(toolId));
|
|
14
14
|
}
|
|
15
15
|
static getAll() {
|
|
16
16
|
return Array.from(this.configurators.values());
|
package/dist/core/init.d.ts
CHANGED
package/dist/core/init.js
CHANGED
|
@@ -7,7 +7,7 @@ import { FileSystemUtils } from '../utils/file-system.js';
|
|
|
7
7
|
import { TemplateManager } from './templates/index.js';
|
|
8
8
|
import { ToolRegistry } from './configurators/registry.js';
|
|
9
9
|
import { AgentSkillRegistry } from './configurators/skills/registry.js';
|
|
10
|
-
import { AI_TOOLS, LIGHTSPEC_DIR_NAME, LIGHTSPEC_MARKERS, } from './config.js';
|
|
10
|
+
import { AI_TOOLS, LIGHTSPEC_DIR_NAME, LIGHTSPEC_MARKERS, normalizeToolId, } from './config.js';
|
|
11
11
|
import { PALETTE } from './styles/palette.js';
|
|
12
12
|
const PROGRESS_SPINNER = {
|
|
13
13
|
interval: 80,
|
|
@@ -41,8 +41,6 @@ const parseToolLabel = (raw) => {
|
|
|
41
41
|
};
|
|
42
42
|
const isSelectableChoice = (choice) => choice.selectable;
|
|
43
43
|
const ROOT_STUB_CHOICE_VALUE = '__root_stub__';
|
|
44
|
-
const OTHER_TOOLS_HEADING_VALUE = '__heading-other__';
|
|
45
|
-
const LIST_SPACER_VALUE = '__list-spacer__';
|
|
46
44
|
const toolSelectionWizard = createPrompt((config, done) => {
|
|
47
45
|
const totalSteps = 3;
|
|
48
46
|
const [step, setStep] = useState('intro');
|
|
@@ -340,10 +338,10 @@ export class InitCommand {
|
|
|
340
338
|
async getSelectedTools(existingTools, extendMode) {
|
|
341
339
|
const nonInteractiveSelection = this.resolveToolsArg();
|
|
342
340
|
if (nonInteractiveSelection !== null) {
|
|
343
|
-
return nonInteractiveSelection;
|
|
341
|
+
return this.normalizeSelectedTools(nonInteractiveSelection);
|
|
344
342
|
}
|
|
345
343
|
// Fall back to interactive mode
|
|
346
|
-
return this.promptForAITools(existingTools, extendMode);
|
|
344
|
+
return this.normalizeSelectedTools(await this.promptForAITools(existingTools, extendMode));
|
|
347
345
|
}
|
|
348
346
|
resolveToolsArg() {
|
|
349
347
|
if (typeof this.toolsArg === 'undefined') {
|
|
@@ -371,7 +369,7 @@ export class InitCommand {
|
|
|
371
369
|
if (tokens.length === 0) {
|
|
372
370
|
throw new Error('The --tools option requires at least one tool ID when not using "all" or "none".');
|
|
373
371
|
}
|
|
374
|
-
const normalizedTokens = tokens.map((token) => token
|
|
372
|
+
const normalizedTokens = tokens.map((token) => normalizeToolId(token));
|
|
375
373
|
if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
|
|
376
374
|
throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
|
|
377
375
|
}
|
|
@@ -387,6 +385,18 @@ export class InitCommand {
|
|
|
387
385
|
}
|
|
388
386
|
return deduped;
|
|
389
387
|
}
|
|
388
|
+
normalizeSelectedTools(selected) {
|
|
389
|
+
const availableSet = new Set(AI_TOOLS.filter((tool) => tool.available).map((tool) => tool.value));
|
|
390
|
+
const normalized = [];
|
|
391
|
+
for (const toolId of selected) {
|
|
392
|
+
const canonical = normalizeToolId(toolId);
|
|
393
|
+
if (!availableSet.has(canonical) || normalized.includes(canonical)) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
normalized.push(canonical);
|
|
397
|
+
}
|
|
398
|
+
return normalized;
|
|
399
|
+
}
|
|
390
400
|
async promptForAITools(existingTools, extendMode) {
|
|
391
401
|
const availableTools = AI_TOOLS.filter((tool) => tool.available);
|
|
392
402
|
const baseMessage = extendMode
|
|
@@ -396,7 +406,7 @@ export class InitCommand {
|
|
|
396
406
|
? availableTools
|
|
397
407
|
.filter((tool) => existingTools[tool.value])
|
|
398
408
|
.map((tool) => tool.value)
|
|
399
|
-
: [];
|
|
409
|
+
: ['universal'];
|
|
400
410
|
const initialSelected = Array.from(new Set(initialNativeSelection));
|
|
401
411
|
const choices = [
|
|
402
412
|
{
|
|
@@ -414,34 +424,6 @@ export class InitCommand {
|
|
|
414
424
|
configured: Boolean(existingTools[tool.value]),
|
|
415
425
|
selectable: true,
|
|
416
426
|
})),
|
|
417
|
-
...(availableTools.length
|
|
418
|
-
? [
|
|
419
|
-
{
|
|
420
|
-
kind: 'info',
|
|
421
|
-
value: LIST_SPACER_VALUE,
|
|
422
|
-
label: { primary: '' },
|
|
423
|
-
selectable: false,
|
|
424
|
-
},
|
|
425
|
-
]
|
|
426
|
-
: []),
|
|
427
|
-
{
|
|
428
|
-
kind: 'heading',
|
|
429
|
-
value: OTHER_TOOLS_HEADING_VALUE,
|
|
430
|
-
label: {
|
|
431
|
-
primary: 'Other tools (use Universal AGENTS.md for Amp, VS Code, GitHub Copilot, …)',
|
|
432
|
-
},
|
|
433
|
-
selectable: false,
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
kind: 'option',
|
|
437
|
-
value: ROOT_STUB_CHOICE_VALUE,
|
|
438
|
-
label: {
|
|
439
|
-
primary: 'Universal AGENTS.md',
|
|
440
|
-
annotation: 'always available',
|
|
441
|
-
},
|
|
442
|
-
configured: extendMode,
|
|
443
|
-
selectable: true,
|
|
444
|
-
},
|
|
445
427
|
];
|
|
446
428
|
return this.prompt({
|
|
447
429
|
extendMode,
|
|
@@ -485,10 +467,15 @@ export class InitCommand {
|
|
|
485
467
|
const skillConfigurator = AgentSkillRegistry.get(toolId);
|
|
486
468
|
if (skillConfigurator) {
|
|
487
469
|
for (const target of skillConfigurator.getTargets()) {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
470
|
+
const candidates = skillConfigurator.resolveExistingAbsolutePaths(projectPath, target.id);
|
|
471
|
+
for (const candidate of candidates) {
|
|
472
|
+
if ((await FileSystemUtils.fileExists(candidate.absolutePath)) && (await fileHasMarkers(candidate.absolutePath))) {
|
|
473
|
+
hasSkills = true;
|
|
474
|
+
break; // At least one file with markers is sufficient
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (hasSkills) {
|
|
478
|
+
break;
|
|
492
479
|
}
|
|
493
480
|
}
|
|
494
481
|
}
|
|
@@ -560,7 +547,7 @@ export class InitCommand {
|
|
|
560
547
|
return rootStubStatus;
|
|
561
548
|
}
|
|
562
549
|
async configureRootAgentsStub(projectPath, lightspecDir) {
|
|
563
|
-
const configurator = ToolRegistry.get('
|
|
550
|
+
const configurator = ToolRegistry.get('universal');
|
|
564
551
|
if (!configurator || !configurator.isAvailable) {
|
|
565
552
|
return 'skipped';
|
|
566
553
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const agentsRootStubTemplate = "# LightSpec Instructions\n\
|
|
1
|
+
export declare const agentsRootStubTemplate = "# LightSpec Instructions\n\nIF (and only if) the user message:\n - Mentions planning or proposals (words like proposal, spec, change, plan)\n - Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work\n - Sounds ambiguous and you need the authoritative spec before coding\n\nTHEN: \nDo the following:\n- [ ] Suggest switching to `/plan` mode before writing the proposal if it's not already the case\n- [ ] When the user approves the plan and terminates planning mode, run `lightspec-apply` to implement the approved proposal.\n- [ ] Run `lightspec-proposal`.\n- [ ] Do not implement the feature until the proposal is approved by the user. Implementation MUST be operated using `lightspec-apply` to ensure the implementation is properly tracked and documented.\n- [ ] If unsure which skill to run, list installed skills.\n\nKeep this managed block so 'lightspec update' can refresh the instructions.\n";
|
|
2
2
|
//# sourceMappingURL=agents-root-stub.d.ts.map
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export const agentsRootStubTemplate = `# LightSpec Instructions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
IF (and only if) the user message:
|
|
4
|
+
- Mentions planning or proposals (words like proposal, spec, change, plan)
|
|
5
|
+
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
|
|
6
|
+
- Sounds ambiguous and you need the authoritative spec before coding
|
|
6
7
|
|
|
8
|
+
THEN:
|
|
9
|
+
Do the following:
|
|
7
10
|
- [ ] Suggest switching to \`/plan\` mode before writing the proposal if it's not already the case
|
|
11
|
+
- [ ] When the user approves the plan and terminates planning mode, run \`lightspec-apply\` to implement the approved proposal.
|
|
8
12
|
- [ ] Run \`lightspec-proposal\`.
|
|
9
13
|
- [ ] Do not implement the feature until the proposal is approved by the user. Implementation MUST be operated using \`lightspec-apply\` to ensure the implementation is properly tracked and documented.
|
|
10
14
|
- [ ] If unsure which skill to run, list installed skills.
|
package/dist/core/update.js
CHANGED
|
@@ -71,7 +71,7 @@ export class UpdateCommand {
|
|
|
71
71
|
}
|
|
72
72
|
if (updatedSkillFiles.length > 0) {
|
|
73
73
|
// Normalize to forward slashes for cross-platform log consistency
|
|
74
|
-
const normalized = updatedSkillFiles.map((p) => FileSystemUtils.toPosixPath(p));
|
|
74
|
+
const normalized = Array.from(new Set(updatedSkillFiles.map((p) => FileSystemUtils.toPosixPath(p))));
|
|
75
75
|
summaryParts.push(`Updated skills: ${normalized.join(', ')}`);
|
|
76
76
|
}
|
|
77
77
|
const failedItems = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightspec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AI-native system for spec-driven development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lightspec",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"check:pack-version": "node scripts/pack-version-check.mjs",
|
|
54
54
|
"release": "pnpm run release:ci",
|
|
55
55
|
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
|
|
56
|
+
"release:passkey": "pnpm run check:pack-version && npm publish --access public",
|
|
56
57
|
"release:manual": "bash scripts/release-manual.sh",
|
|
57
58
|
"changeset": "changeset"
|
|
58
59
|
},
|