lightspec 0.4.1 → 0.6.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 +236 -56
- 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/agent-skill-templates.d.ts +1 -1
- package/dist/core/templates/agent-skill-templates.js +3 -0
- package/dist/core/templates/agents-root-stub.d.ts +1 -1
- package/dist/core/templates/agents-root-stub.js +7 -3
- package/dist/core/templates/loop-template.d.ts +3 -0
- package/dist/core/templates/loop-template.js +62 -0
- package/dist/core/update.js +1 -1
- package/package.json +1 -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,47 +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';
|
|
6
|
-
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
|
-
|
|
30
|
-
|
|
4
|
+
import { LIGHTSPEC_MARKERS, normalizeToolId } from '../../config.js';
|
|
5
|
+
const ALL_SKILL_IDS = ['proposal', 'apply', 'archive', 'agentsmd-check', 'loop'];
|
|
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
|
+
},
|
|
31
189
|
};
|
|
32
|
-
|
|
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));
|
|
33
196
|
const TOOL_BODY_SUFFIX = {
|
|
34
|
-
|
|
197
|
+
droid: '\n\n$ARGUMENTS',
|
|
35
198
|
};
|
|
36
199
|
export class AgentSkillConfigurator {
|
|
37
200
|
toolId;
|
|
38
201
|
isAvailable;
|
|
39
202
|
installLocation = 'project';
|
|
40
203
|
constructor(toolId, isAvailable = true) {
|
|
41
|
-
this.toolId = toolId;
|
|
204
|
+
this.toolId = normalizeAgentSkillToolId(toolId);
|
|
42
205
|
this.isAvailable = isAvailable;
|
|
43
|
-
if (!
|
|
44
|
-
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}'`);
|
|
45
208
|
}
|
|
46
209
|
}
|
|
47
210
|
setInstallLocation(location) {
|
|
@@ -74,11 +237,14 @@ export class AgentSkillConfigurator {
|
|
|
74
237
|
async updateExisting(projectPath, _lightspecDir) {
|
|
75
238
|
const updated = [];
|
|
76
239
|
for (const target of this.getTargets()) {
|
|
77
|
-
const
|
|
78
|
-
|
|
240
|
+
const candidatePaths = this.resolveExistingAbsolutePaths(projectPath, target.id);
|
|
241
|
+
for (const candidate of candidatePaths) {
|
|
242
|
+
if (!await FileSystemUtils.fileExists(candidate.absolutePath)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
79
245
|
const body = this.getBody(target.id);
|
|
80
|
-
await this.updateBody(
|
|
81
|
-
updated.push(
|
|
246
|
+
await this.updateBody(candidate.absolutePath, body);
|
|
247
|
+
updated.push(candidate.relativePath);
|
|
82
248
|
}
|
|
83
249
|
}
|
|
84
250
|
return updated;
|
|
@@ -90,40 +256,54 @@ export class AgentSkillConfigurator {
|
|
|
90
256
|
}
|
|
91
257
|
resolveAbsolutePath(projectPath, id) {
|
|
92
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) {
|
|
93
285
|
if (this.installLocation === 'project') {
|
|
94
286
|
return FileSystemUtils.joinPath(projectPath, relativePath);
|
|
95
287
|
}
|
|
96
288
|
const homeRoot = this.getHomeRootPath();
|
|
97
|
-
|
|
98
|
-
const normalizedRelativePath = FileSystemUtils.toPosixPath(relativePath);
|
|
99
|
-
if (!normalizedRelativePath.startsWith(`${rootPrefix}/`)) {
|
|
100
|
-
throw new Error(`Skill path '${relativePath}' does not match expected root '${rootPrefix}' for ${this.toolId}`);
|
|
101
|
-
}
|
|
102
|
-
const relativeUnderRoot = normalizedRelativePath.slice(rootPrefix.length + 1);
|
|
103
|
-
return FileSystemUtils.joinPath(homeRoot, relativeUnderRoot);
|
|
104
|
-
}
|
|
105
|
-
getRelativeSkillPath(id) {
|
|
106
|
-
const root = this.getToolRoot();
|
|
107
|
-
const skillName = this.getSkillName(id);
|
|
108
|
-
return `${root}/skills/${skillName}/SKILL.md`;
|
|
289
|
+
return FileSystemUtils.joinPath(homeRoot, relativePath);
|
|
109
290
|
}
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
291
|
+
getDescriptor() {
|
|
292
|
+
const descriptor = TOOL_SKILL_DESCRIPTORS[this.toolId];
|
|
293
|
+
if (!descriptor) {
|
|
113
294
|
throw new Error(`No skill root directory configured for tool '${this.toolId}'`);
|
|
114
295
|
}
|
|
115
|
-
return
|
|
296
|
+
return descriptor;
|
|
116
297
|
}
|
|
117
298
|
getHomeRootPath() {
|
|
118
|
-
|
|
299
|
+
const descriptor = this.getDescriptor();
|
|
300
|
+
if (descriptor.homeBase === 'codex-home') {
|
|
119
301
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
120
302
|
return codexHome && codexHome.length > 0
|
|
121
303
|
? codexHome
|
|
122
304
|
: FileSystemUtils.joinPath(os.homedir(), '.codex');
|
|
123
305
|
}
|
|
124
|
-
|
|
125
|
-
const trimmed = toolRoot.startsWith('./') ? toolRoot.slice(2) : toolRoot;
|
|
126
|
-
return path.join(os.homedir(), trimmed);
|
|
306
|
+
return os.homedir();
|
|
127
307
|
}
|
|
128
308
|
getSkillName(id) {
|
|
129
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,4 +1,4 @@
|
|
|
1
|
-
export type AgentSkillId = 'proposal' | 'apply' | 'archive' | 'agentsmd-check';
|
|
1
|
+
export type AgentSkillId = 'proposal' | 'apply' | 'archive' | 'agentsmd-check' | 'loop';
|
|
2
2
|
export declare const agentSkillBodies: Record<AgentSkillId, string>;
|
|
3
3
|
export declare const agentSkillFrontmatter: Record<AgentSkillId, string>;
|
|
4
4
|
export declare function getAgentSkillBody(id: AgentSkillId): string;
|
|
@@ -2,17 +2,20 @@ import { applyTemplate, applyFrontmatter } from './apply-template.js';
|
|
|
2
2
|
import { archiveTemplate, archiveFrontmatter } from './archive-template.js';
|
|
3
3
|
import { proposalTemplate, proposalFrontmatter } from './proposal-template.js';
|
|
4
4
|
import { agentsmdCheckTemplate, agentsmdCheckFrontmatter, } from './agentsmd-check-template.js';
|
|
5
|
+
import { loopTemplate, loopFrontmatter } from './loop-template.js';
|
|
5
6
|
export const agentSkillBodies = {
|
|
6
7
|
proposal: proposalTemplate,
|
|
7
8
|
apply: applyTemplate,
|
|
8
9
|
archive: archiveTemplate,
|
|
9
10
|
'agentsmd-check': agentsmdCheckTemplate,
|
|
11
|
+
loop: loopTemplate,
|
|
10
12
|
};
|
|
11
13
|
export const agentSkillFrontmatter = {
|
|
12
14
|
proposal: proposalFrontmatter,
|
|
13
15
|
apply: applyFrontmatter,
|
|
14
16
|
archive: archiveFrontmatter,
|
|
15
17
|
'agentsmd-check': agentsmdCheckFrontmatter,
|
|
18
|
+
loop: loopFrontmatter,
|
|
16
19
|
};
|
|
17
20
|
export function getAgentSkillBody(id) {
|
|
18
21
|
return agentSkillBodies[id];
|
|
@@ -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.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const loopFrontmatter = "---\nname: lightspec-loop\ndescription: Use when systematically implementing LightSpec change proposals through clean, sequential delegation. For instance, when the user applies the plan using /lightspec:apply.\ndisable-model-invocation: false\nuser-invocable: true\nmetadata:\n source: lightspec\n workflow: loop\n---";
|
|
2
|
+
export declare const loopTemplate = "# LightSpec Loop: Subagent-Driven Implementation\n\nImplement LightSpec changes by dispatching a fresh subagent per change, with clean context isolation and sequential execution.\n\n**Core principle:** fresh subagent per change + sequential execution + clean context = isolated, predictable implementation.\n\n## When To Use\n- You have multiple active LightSpec changes to implement.\n- The changes are mostly independent.\n- You want strict context isolation between change implementations.\n\n## Process\n1. Run `lightspec list` to discover active changes.\n2. Present active change IDs to the user and confirm the execution order.\n3. For each change ID, execute the cycle below sequentially:\n - Dispatch a fresh general-purpose subagent.\n - First subagent action: clear context (for example, `/clear` if your assistant supports it).\n - Run `lightspec apply <change-id>`.\n - Implement all required code/docs/tests.\n - Run `lightspec archive <change-id> --yes` once implementation is complete.\n - Verify the archive succeeded before moving to the next change.\n4. After all requested changes are processed, provide a concise completion summary.\n\n## Subagent Instruction Template\n```\nYou are implementing one specific LightSpec change. Follow this workflow:\n1. First action: clear your context to avoid contamination from prior work.\n2. Run: lightspec apply <change-id>\n3. Implement all tasks in the change proposal.\n4. Archive the completed change: lightspec archive <change-id> --yes\n5. Report completion status and stop.\n```\n\n## Operational Constraints\n**Never**\n- Implement LightSpec changes in parallel.\n- Skip context reset between change implementations.\n- Move to the next change before confirming archive success for the current one.\n\n**Always**\n- Confirm the change ID exists before starting.\n- Ask the user how to proceed on failure (retry, skip, abort).\n- Keep a running status list: total, current, completed, remaining.\n\n## Error Handling\n- If a subagent fails implementation, report the failure and ask whether to retry with a fresh subagent, skip, or abort.\n- If `lightspec apply` or `lightspec archive` fails, stop and request user confirmation before continuing.\n\n## Integration\n- Uses `lightspec-apply` workflow semantics through `lightspec apply <change-id>`.\n- Uses `lightspec-archive` workflow semantics through `lightspec archive <change-id> --yes`.\n";
|
|
3
|
+
//# sourceMappingURL=loop-template.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const loopFrontmatter = `---
|
|
2
|
+
name: lightspec-loop
|
|
3
|
+
description: Use when systematically implementing LightSpec change proposals through clean, sequential delegation. For instance, when the user applies the plan using /lightspec:apply.
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
user-invocable: true
|
|
6
|
+
metadata:
|
|
7
|
+
source: lightspec
|
|
8
|
+
workflow: loop
|
|
9
|
+
---`;
|
|
10
|
+
export const loopTemplate = `# LightSpec Loop: Subagent-Driven Implementation
|
|
11
|
+
|
|
12
|
+
Implement LightSpec changes by dispatching a fresh subagent per change, with clean context isolation and sequential execution.
|
|
13
|
+
|
|
14
|
+
**Core principle:** fresh subagent per change + sequential execution + clean context = isolated, predictable implementation.
|
|
15
|
+
|
|
16
|
+
## When To Use
|
|
17
|
+
- You have multiple active LightSpec changes to implement.
|
|
18
|
+
- The changes are mostly independent.
|
|
19
|
+
- You want strict context isolation between change implementations.
|
|
20
|
+
|
|
21
|
+
## Process
|
|
22
|
+
1. Run \`lightspec list\` to discover active changes.
|
|
23
|
+
2. Present active change IDs to the user and confirm the execution order.
|
|
24
|
+
3. For each change ID, execute the cycle below sequentially:
|
|
25
|
+
- Dispatch a fresh general-purpose subagent.
|
|
26
|
+
- First subagent action: clear context (for example, \`/clear\` if your assistant supports it).
|
|
27
|
+
- Run \`lightspec apply <change-id>\`.
|
|
28
|
+
- Implement all required code/docs/tests.
|
|
29
|
+
- Run \`lightspec archive <change-id> --yes\` once implementation is complete.
|
|
30
|
+
- Verify the archive succeeded before moving to the next change.
|
|
31
|
+
4. After all requested changes are processed, provide a concise completion summary.
|
|
32
|
+
|
|
33
|
+
## Subagent Instruction Template
|
|
34
|
+
\`\`\`
|
|
35
|
+
You are implementing one specific LightSpec change. Follow this workflow:
|
|
36
|
+
1. First action: clear your context to avoid contamination from prior work.
|
|
37
|
+
2. Run: lightspec apply <change-id>
|
|
38
|
+
3. Implement all tasks in the change proposal.
|
|
39
|
+
4. Archive the completed change: lightspec archive <change-id> --yes
|
|
40
|
+
5. Report completion status and stop.
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
## Operational Constraints
|
|
44
|
+
**Never**
|
|
45
|
+
- Implement LightSpec changes in parallel.
|
|
46
|
+
- Skip context reset between change implementations.
|
|
47
|
+
- Move to the next change before confirming archive success for the current one.
|
|
48
|
+
|
|
49
|
+
**Always**
|
|
50
|
+
- Confirm the change ID exists before starting.
|
|
51
|
+
- Ask the user how to proceed on failure (retry, skip, abort).
|
|
52
|
+
- Keep a running status list: total, current, completed, remaining.
|
|
53
|
+
|
|
54
|
+
## Error Handling
|
|
55
|
+
- If a subagent fails implementation, report the failure and ask whether to retry with a fresh subagent, skip, or abort.
|
|
56
|
+
- If \`lightspec apply\` or \`lightspec archive\` fails, stop and request user confirmation before continuing.
|
|
57
|
+
|
|
58
|
+
## Integration
|
|
59
|
+
- Uses \`lightspec-apply\` workflow semantics through \`lightspec apply <change-id>\`.
|
|
60
|
+
- Uses \`lightspec-archive\` workflow semantics through \`lightspec archive <change-id> --yes\`.
|
|
61
|
+
`;
|
|
62
|
+
//# sourceMappingURL=loop-template.js.map
|
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 = [
|