lightspec 0.1.1 → 0.2.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/README.md +41 -65
- package/dist/cli/index.js +12 -24
- package/dist/commands/feedback.js +2 -2
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.js +1 -0
- package/dist/core/configurators/slash/amazon-q.js +6 -2
- package/dist/core/configurators/slash/antigravity.js +4 -2
- package/dist/core/configurators/slash/auggie.js +8 -1
- package/dist/core/configurators/slash/base.d.ts +14 -3
- package/dist/core/configurators/slash/base.js +160 -16
- package/dist/core/configurators/slash/claude.js +8 -1
- package/dist/core/configurators/slash/cline.js +4 -2
- package/dist/core/configurators/slash/codebuddy.js +8 -1
- package/dist/core/configurators/slash/codex.d.ts +0 -8
- package/dist/core/configurators/slash/codex.js +0 -103
- package/dist/core/configurators/slash/continue.js +8 -1
- package/dist/core/configurators/slash/costrict.js +4 -0
- package/dist/core/configurators/slash/crush.js +8 -1
- package/dist/core/configurators/slash/cursor.js +8 -1
- package/dist/core/configurators/slash/factory.js +8 -1
- package/dist/core/configurators/slash/gemini.js +4 -2
- package/dist/core/configurators/slash/github-copilot.js +6 -2
- package/dist/core/configurators/slash/iflow.js +8 -1
- package/dist/core/configurators/slash/kilocode.js +2 -1
- package/dist/core/configurators/slash/mistral-vibe.d.ts +6 -0
- package/dist/core/configurators/slash/mistral-vibe.js +6 -0
- package/dist/core/configurators/slash/opencode.js +4 -0
- package/dist/core/configurators/slash/qoder.js +8 -1
- package/dist/core/configurators/slash/qwen.js +4 -2
- package/dist/core/configurators/slash/registry.d.ts +2 -1
- package/dist/core/configurators/slash/registry.js +8 -0
- package/dist/core/configurators/slash/roocode.js +4 -2
- package/dist/core/configurators/slash/toml-base.d.ts +0 -6
- package/dist/core/configurators/slash/toml-base.js +0 -49
- package/dist/core/configurators/slash/windsurf.js +4 -2
- package/dist/core/init.d.ts +6 -0
- package/dist/core/init.js +49 -22
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +4 -7
- package/dist/core/templates/apply-template.d.ts +3 -0
- package/dist/core/templates/apply-template.js +21 -0
- package/dist/core/templates/archive-template.d.ts +3 -0
- package/dist/core/templates/archive-template.js +29 -0
- package/dist/core/templates/context-check-template.d.ts +3 -0
- package/dist/core/templates/context-check-template.js +128 -0
- package/dist/core/templates/index.d.ts +1 -0
- package/dist/core/templates/index.js +4 -6
- package/dist/core/templates/proposal-template.d.ts +3 -0
- package/dist/core/templates/proposal-template.js +30 -0
- package/dist/core/templates/skill-common-template.d.ts +2 -0
- package/dist/core/templates/skill-common-template.js +5 -0
- package/dist/core/templates/slash-command-templates.d.ts +3 -1
- package/dist/core/templates/slash-command-templates.js +17 -43
- package/dist/core/update.js +5 -5
- package/dist/telemetry/index.d.ts +2 -3
- package/dist/telemetry/index.js +2 -3
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +5 -0
- package/package.json +19 -22
package/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://github.com/augmenter-dev/lightspec">
|
|
3
3
|
<picture>
|
|
4
|
-
<
|
|
5
|
-
<source srcset="assets/lightspec_pixel_light.svg" media="(prefers-color-scheme: light)">
|
|
6
|
-
<img src="assets/lightspec_pixel_light.svg" alt="LightSpec logo" height="64">
|
|
4
|
+
<img src="assets/augmenter-lightspec.svg" alt="LightSpec logo" height="156">
|
|
7
5
|
</picture>
|
|
8
6
|
</a>
|
|
9
7
|
|
|
@@ -19,16 +17,12 @@
|
|
|
19
17
|
</p>
|
|
20
18
|
|
|
21
19
|
<p align="center">
|
|
22
|
-
<img src="assets/
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
|
-
<p align="center">
|
|
26
|
-
Follow <a href="https://x.com/0xTab">@0xTab on X</a> for updates · Join the <a href="https://discord.gg/YctCnvvshC">LightSpec Discord</a> for help and questions.
|
|
20
|
+
<img src="assets/openspec_dashboard.png" alt="LightSpec dashboard preview" width="90%">
|
|
27
21
|
</p>
|
|
28
22
|
|
|
29
23
|
# LightSpec
|
|
30
24
|
|
|
31
|
-
A fork of [
|
|
25
|
+
A fork of [OpenSpec](https://github.com/Fission-AI/OpenSpec), focused on simplicity and skill-based agents.
|
|
32
26
|
|
|
33
27
|
LightSpec aligns humans and AI coding assistants with spec-driven development so you agree on what to build before any code is written. **No API keys required.**
|
|
34
28
|
|
|
@@ -40,13 +34,14 @@ Key outcomes:
|
|
|
40
34
|
- Human and AI stakeholders agree on specs before work begins.
|
|
41
35
|
- Structured change folders (proposals, tasks, and spec updates) keep scope explicit and auditable.
|
|
42
36
|
- Shared visibility into what's proposed, active, or archived.
|
|
43
|
-
- Works with the AI tools you already use
|
|
37
|
+
- Works with the AI tools you already use via [agent skills](https://agentskills.io/).
|
|
44
38
|
|
|
45
39
|
## How LightSpec compares (at a glance)
|
|
46
40
|
|
|
47
41
|
- **Lightweight**: simple workflow, no API keys, minimal setup.
|
|
48
42
|
- **Brownfield-first**: works great beyond 0→1. LightSpec separates the source of truth from proposals: `lightspec/specs/` (current truth) and `lightspec/changes/` (proposed updates). This keeps diffs explicit and manageable across features.
|
|
49
43
|
- **Change tracking**: proposals, tasks, and spec deltas live together; archiving merges the approved updates back into specs.
|
|
44
|
+
- **Compared to OpenSpec**: LightSpec is a streamlined alternative to OpenSpec, focused on simplicity and ease of adoption. It has fewer commands and a more opinionated workflow, which can reduce cognitive overhead for teams new to spec-driven development.
|
|
50
45
|
- **Compared to spec-kit & Kiro**: those shine for brand-new features (0→1). LightSpec also excels when modifying existing behavior (1→n), especially when updates span multiple specs.
|
|
51
46
|
|
|
52
47
|
See the full comparison in [How LightSpec Compares](#how-lightspec-compares).
|
|
@@ -87,49 +82,29 @@ See the full comparison in [How LightSpec Compares](#how-lightspec-compares).
|
|
|
87
82
|
|
|
88
83
|
### Supported AI Tools
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
| **OpenCode** | `/lightspec-proposal`, `/lightspec-apply`, `/lightspec-archive` |
|
|
114
|
-
| **Qoder (CLI)** | `/lightspec:proposal`, `/lightspec:apply`, `/lightspec:archive` (`.qoder/commands/lightspec/`) — see [docs](https://qoder.com/cli) |
|
|
115
|
-
| **Qwen Code** | `/lightspec-proposal`, `/lightspec-apply`, `/lightspec-archive` (`.qwen/commands/`) |
|
|
116
|
-
| **RooCode** | `/lightspec-proposal`, `/lightspec-apply`, `/lightspec-archive` (`.roo/commands/`) |
|
|
117
|
-
| **Windsurf** | `/lightspec-proposal`, `/lightspec-apply`, `/lightspec-archive` (`.windsurf/workflows/`) |
|
|
118
|
-
|
|
119
|
-
Kilo Code discovers team workflows automatically. Save the generated files under `.kilocode/workflows/` and trigger them from the command palette with `/lightspec-proposal.md`, `/lightspec-apply.md`, or `/lightspec-archive.md`.
|
|
120
|
-
|
|
121
|
-
</details>
|
|
122
|
-
|
|
123
|
-
<details>
|
|
124
|
-
<summary><strong>AGENTS.md Compatible</strong> (click to expand)</summary>
|
|
125
|
-
|
|
126
|
-
These tools automatically read workflow instructions from `lightspec/AGENTS.md`. Ask them to follow the LightSpec workflow if they need a reminder. Learn more about the [AGENTS.md convention](https://agents.md/).
|
|
127
|
-
|
|
128
|
-
| Tools |
|
|
129
|
-
|-------|
|
|
130
|
-
| Amp • Jules • Others |
|
|
131
|
-
|
|
132
|
-
</details>
|
|
85
|
+
- Amazon Q Developer
|
|
86
|
+
- Antigravity
|
|
87
|
+
- Auggie (Augment CLI)
|
|
88
|
+
- Claude Code
|
|
89
|
+
- Cline
|
|
90
|
+
- Codex
|
|
91
|
+
- CodeBuddy Code (CLI)
|
|
92
|
+
- Continue (VS Code / JetBrains / CLI)
|
|
93
|
+
- CoStrict
|
|
94
|
+
- Crush
|
|
95
|
+
- Cursor
|
|
96
|
+
- Factory Droid
|
|
97
|
+
- Gemini CLI
|
|
98
|
+
- GitHub Copilot
|
|
99
|
+
- iFlow
|
|
100
|
+
- Kilo Code
|
|
101
|
+
- Mistral Vibe
|
|
102
|
+
- OpenCode
|
|
103
|
+
- Qoder (CLI)
|
|
104
|
+
- Qwen Code
|
|
105
|
+
- RooCode
|
|
106
|
+
- Windsurf
|
|
107
|
+
- Any AGENTS.md-compatible assistant (via Universal `AGENTS.md`)
|
|
133
108
|
|
|
134
109
|
### Install & Initialize
|
|
135
110
|
|
|
@@ -196,29 +171,29 @@ lightspec init
|
|
|
196
171
|
|
|
197
172
|
**What happens during initialization:**
|
|
198
173
|
- You'll be prompted to pick any natively supported AI tools (Claude Code, CodeBuddy, Cursor, OpenCode, Qoder,etc.); other assistants always rely on the shared `AGENTS.md` stub
|
|
199
|
-
- LightSpec automatically configures
|
|
174
|
+
- LightSpec automatically configures skills for the tools you choose and always writes a managed `AGENTS.md` hand-off at the project root
|
|
200
175
|
- A new `lightspec/` directory structure is created in your project
|
|
201
176
|
|
|
202
177
|
**After setup:**
|
|
203
178
|
- Primary AI tools can trigger `/lightspec` workflows without additional configuration
|
|
204
179
|
- Run `lightspec list` to verify the setup and view any active changes
|
|
205
|
-
- If your coding assistant doesn't surface the new
|
|
206
|
-
|
|
180
|
+
- If your coding assistant doesn't surface the new skills right away, restart it. Skills are loaded at startup, so a fresh launch ensures they appear
|
|
181
|
+
- Depending on your AI tool, you'll need to invoke the lightspec skills with either slash commands (e.g. `/lightspec:proposal`) or dollar commands (e.g. `$lightspec-proposal`) to create change proposals, apply changes, or archive completed work
|
|
207
182
|
|
|
208
183
|
### Optional: Populate Project Context
|
|
209
184
|
|
|
210
|
-
After `lightspec init` completes, you'll receive a suggested
|
|
185
|
+
After `lightspec init` completes, you'll receive a suggested command to validate and populate your project context:
|
|
211
186
|
|
|
212
187
|
```text
|
|
213
|
-
|
|
214
|
-
"
|
|
188
|
+
Validate and populate your project context:
|
|
189
|
+
"/lightspec:context-check"
|
|
215
190
|
```
|
|
216
191
|
|
|
217
|
-
Use
|
|
192
|
+
Use the `/lightspec:context-check` skill to validate that your agent instruction file (CLAUDE.md or AGENTS.md) contains adequate project context. The skill will check for required properties like Purpose, Tech Stack, Architecture Patterns, and more. If anything is missing, it can help you explore the codebase and populate the missing information.
|
|
218
193
|
|
|
219
194
|
### Create Your First Change
|
|
220
195
|
|
|
221
|
-
Here's a real example showing the complete LightSpec workflow. This works with any AI tool.
|
|
196
|
+
Here's a real example showing the complete LightSpec workflow. This works with any AI tool.
|
|
222
197
|
|
|
223
198
|
#### 1. Draft the Proposal
|
|
224
199
|
Start by asking your AI to create a change proposal:
|
|
@@ -281,8 +256,6 @@ Or run the command yourself in terminal:
|
|
|
281
256
|
$ lightspec archive add-profile-filters --yes # Archive the completed change without prompts
|
|
282
257
|
```
|
|
283
258
|
|
|
284
|
-
**Note:** Tools with native slash commands (Claude Code, CodeBuddy, Cursor, Codex, Qoder, RooCode) can use the shortcuts shown. All other tools work with natural language requests to "create an LightSpec proposal", "apply the LightSpec change", or "archive the change".
|
|
285
|
-
|
|
286
259
|
## Command Reference
|
|
287
260
|
|
|
288
261
|
```bash
|
|
@@ -379,6 +352,9 @@ Deltas are "patches" that show how specs change:
|
|
|
379
352
|
|
|
380
353
|
## How LightSpec Compares
|
|
381
354
|
|
|
355
|
+
### vs. OpenSpec
|
|
356
|
+
OpenSpec has evolved into a more mature yet complex tool with a rich feature set. LightSpec focuses on simplicity and ease of adoption, especially for teams new to spec-driven development. LightSpec's minimalist approach has the additional benefit of reducing the number of skills and commands needed, and reducing the risk of involuntary skill activation from AI assistants.
|
|
357
|
+
|
|
382
358
|
### vs. spec-kit
|
|
383
359
|
LightSpec’s two-folder model (`lightspec/specs/` for the current truth, `lightspec/changes/` for proposed updates) keeps state and diffs separate. This scales when you modify existing features or touch multiple specs. spec-kit is strong for greenfield/0→1 but provides less structure for cross-spec updates and evolving features.
|
|
384
360
|
|
|
@@ -404,7 +380,7 @@ Run `lightspec update` whenever someone switches tools so your agents pick up th
|
|
|
404
380
|
npm install -g lightspec@latest
|
|
405
381
|
```
|
|
406
382
|
2. **Refresh agent instructions**
|
|
407
|
-
- Run `lightspec update` inside each project to regenerate AI guidance and ensure the latest
|
|
383
|
+
- Run `lightspec update` inside each project to regenerate AI guidance and ensure the latest skills are active.
|
|
408
384
|
|
|
409
385
|
## Contributing
|
|
410
386
|
|
|
@@ -424,7 +400,7 @@ See [MAINTAINERS.md](MAINTAINERS.md) for the list of core maintainers and adviso
|
|
|
424
400
|
## Agent Skills
|
|
425
401
|
|
|
426
402
|
LightSpec includes 3 Claude Code skills for the core development workflow:
|
|
427
|
-
- `lightspec-
|
|
403
|
+
- `lightspec-proposal` - Create a new change
|
|
428
404
|
- `lightspec-apply` - Get apply instructions for implementation
|
|
429
405
|
- `lightspec-archive` - Archive a completed change
|
|
430
406
|
|
package/dist/cli/index.js
CHANGED
|
@@ -19,23 +19,6 @@ import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/i
|
|
|
19
19
|
const program = new Command();
|
|
20
20
|
const require = createRequire(import.meta.url);
|
|
21
21
|
const { version } = require('../../package.json');
|
|
22
|
-
/**
|
|
23
|
-
* Get the full command path for nested commands.
|
|
24
|
-
* For example: 'change show' -> 'change:show'
|
|
25
|
-
*/
|
|
26
|
-
function getCommandPath(command) {
|
|
27
|
-
const names = [];
|
|
28
|
-
let current = command;
|
|
29
|
-
while (current) {
|
|
30
|
-
const name = current.name();
|
|
31
|
-
// Skip the root 'lightspec' command
|
|
32
|
-
if (name && name !== 'lightspec') {
|
|
33
|
-
names.unshift(name);
|
|
34
|
-
}
|
|
35
|
-
current = current.parent;
|
|
36
|
-
}
|
|
37
|
-
return names.join(':') || 'lightspec';
|
|
38
|
-
}
|
|
39
22
|
program
|
|
40
23
|
.name('lightspec')
|
|
41
24
|
.description('AI-native system for spec-driven development')
|
|
@@ -43,19 +26,15 @@ program
|
|
|
43
26
|
// Global options
|
|
44
27
|
program.option('--no-color', 'Disable color output');
|
|
45
28
|
// Apply global flags and telemetry before any command runs
|
|
46
|
-
|
|
47
|
-
// - thisCommand: the command where hook was added (root program)
|
|
48
|
-
// - actionCommand: the command actually being executed (subcommand)
|
|
49
|
-
program.hook('preAction', async (thisCommand, actionCommand) => {
|
|
29
|
+
program.hook('preAction', async (thisCommand) => {
|
|
50
30
|
const opts = thisCommand.opts();
|
|
51
31
|
if (opts.color === false) {
|
|
52
32
|
process.env.NO_COLOR = '1';
|
|
53
33
|
}
|
|
54
34
|
// Show first-run telemetry notice (if not seen)
|
|
55
35
|
await maybeShowTelemetryNotice();
|
|
56
|
-
// Track command execution
|
|
57
|
-
|
|
58
|
-
await trackCommand(commandPath, version);
|
|
36
|
+
// Track command execution
|
|
37
|
+
await trackCommand();
|
|
59
38
|
});
|
|
60
39
|
// Shutdown telemetry after command completes
|
|
61
40
|
program.hook('postAction', async () => {
|
|
@@ -63,10 +42,12 @@ program.hook('postAction', async () => {
|
|
|
63
42
|
});
|
|
64
43
|
const availableToolIds = AI_TOOLS.filter((tool) => tool.available).map((tool) => tool.value);
|
|
65
44
|
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
|
|
45
|
+
const skillLocationOptionDescription = 'Install generated skills in "project" or "home" location (defaults to interactive selection).';
|
|
66
46
|
program
|
|
67
47
|
.command('init [path]')
|
|
68
48
|
.description('Initialize LightSpec in your project')
|
|
69
49
|
.option('--tools <tools>', toolsOptionDescription)
|
|
50
|
+
.option('--skills-location <location>', skillLocationOptionDescription)
|
|
70
51
|
.action(async (targetPath = '.', options) => {
|
|
71
52
|
try {
|
|
72
53
|
// Validate that the path is a valid directory
|
|
@@ -90,8 +71,15 @@ program
|
|
|
90
71
|
}
|
|
91
72
|
}
|
|
92
73
|
const { InitCommand } = await import('../core/init.js');
|
|
74
|
+
const skillLocation = options?.skillsLocation === 'project' || options?.skillsLocation === 'home'
|
|
75
|
+
? options.skillsLocation
|
|
76
|
+
: undefined;
|
|
77
|
+
if (options?.skillsLocation && !skillLocation) {
|
|
78
|
+
throw new Error('Invalid --skills-location value. Use "project" or "home".');
|
|
79
|
+
}
|
|
93
80
|
const initCommand = new InitCommand({
|
|
94
81
|
tools: options?.tools,
|
|
82
|
+
skillLocation,
|
|
95
83
|
});
|
|
96
84
|
await initCommand.execute(targetPath);
|
|
97
85
|
}
|
|
@@ -87,7 +87,7 @@ function formatBody(bodyText) {
|
|
|
87
87
|
* Generate a pre-filled GitHub issue URL for manual submission
|
|
88
88
|
*/
|
|
89
89
|
function generateManualSubmissionUrl(title, body) {
|
|
90
|
-
const repo = '
|
|
90
|
+
const repo = 'augmenter-dev/LightSpec';
|
|
91
91
|
const encodedTitle = encodeURIComponent(title);
|
|
92
92
|
const encodedBody = encodeURIComponent(body);
|
|
93
93
|
const encodedLabels = encodeURIComponent('feedback');
|
|
@@ -114,7 +114,7 @@ function submitViaGhCli(title, body) {
|
|
|
114
114
|
'issue',
|
|
115
115
|
'create',
|
|
116
116
|
'--repo',
|
|
117
|
-
'
|
|
117
|
+
'augmenter-dev/LightSpec',
|
|
118
118
|
'--title',
|
|
119
119
|
title,
|
|
120
120
|
'--body',
|
package/dist/core/config.d.ts
CHANGED
package/dist/core/config.js
CHANGED
|
@@ -20,6 +20,7 @@ export const AI_TOOLS = [
|
|
|
20
20
|
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
|
|
21
21
|
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow' },
|
|
22
22
|
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
|
|
23
|
+
{ name: 'Mistral Vibe', value: 'mistral-vibe', available: true, successLabel: 'Mistral Vibe' },
|
|
23
24
|
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' },
|
|
24
25
|
{ name: 'Qoder (CLI)', value: 'qoder', available: true, successLabel: 'Qoder' },
|
|
25
26
|
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code' },
|
|
@@ -2,7 +2,8 @@ import { SlashCommandConfigurator } from './base.js';
|
|
|
2
2
|
const FILE_PATHS = {
|
|
3
3
|
proposal: '.amazonq/prompts/lightspec-proposal.md',
|
|
4
4
|
apply: '.amazonq/prompts/lightspec-apply.md',
|
|
5
|
-
archive: '.amazonq/prompts/lightspec-archive.md'
|
|
5
|
+
archive: '.amazonq/prompts/lightspec-archive.md',
|
|
6
|
+
'context-check': '.aws/amazonq/commands/lightspec-context-check.md'
|
|
6
7
|
};
|
|
7
8
|
const FRONTMATTER = {
|
|
8
9
|
proposal: `---
|
|
@@ -31,7 +32,10 @@ The user wants to archive the following deployed change. Use the lightspec instr
|
|
|
31
32
|
|
|
32
33
|
<ChangeId>
|
|
33
34
|
$ARGUMENTS
|
|
34
|
-
</ChangeId
|
|
35
|
+
</ChangeId>`,
|
|
36
|
+
'context-check': `---
|
|
37
|
+
description: Validate project context in agent instruction files and help populate missing information.
|
|
38
|
+
---`
|
|
35
39
|
};
|
|
36
40
|
export class AmazonQSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
37
41
|
toolId = 'amazon-q';
|
|
@@ -2,12 +2,14 @@ import { SlashCommandConfigurator } from './base.js';
|
|
|
2
2
|
const FILE_PATHS = {
|
|
3
3
|
proposal: '.agent/workflows/lightspec-proposal.md',
|
|
4
4
|
apply: '.agent/workflows/lightspec-apply.md',
|
|
5
|
-
archive: '.agent/workflows/lightspec-archive.md'
|
|
5
|
+
archive: '.agent/workflows/lightspec-archive.md',
|
|
6
|
+
'context-check': '.antigravity/commands/lightspec-context-check.md'
|
|
6
7
|
};
|
|
7
8
|
const DESCRIPTIONS = {
|
|
8
9
|
proposal: 'Scaffold a new LightSpec change and validate strictly.',
|
|
9
10
|
apply: 'Implement an approved LightSpec change and keep tasks in sync.',
|
|
10
|
-
archive: 'Archive a deployed LightSpec change and update specs.'
|
|
11
|
+
archive: 'Archive a deployed LightSpec change and update specs.',
|
|
12
|
+
'context-check': 'Validate project context in agent instruction files and help populate missing information.'
|
|
11
13
|
};
|
|
12
14
|
export class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
|
|
13
15
|
toolId = 'antigravity';
|
|
@@ -2,7 +2,8 @@ import { SlashCommandConfigurator } from './base.js';
|
|
|
2
2
|
const FILE_PATHS = {
|
|
3
3
|
proposal: '.augment/commands/lightspec-proposal.md',
|
|
4
4
|
apply: '.augment/commands/lightspec-apply.md',
|
|
5
|
-
archive: '.augment/commands/lightspec-archive.md'
|
|
5
|
+
archive: '.augment/commands/lightspec-archive.md',
|
|
6
|
+
'context-check': '.auggie/commands/lightspec-context-check.md'
|
|
6
7
|
};
|
|
7
8
|
const FRONTMATTER = {
|
|
8
9
|
proposal: `---
|
|
@@ -16,6 +17,12 @@ argument-hint: change-id
|
|
|
16
17
|
archive: `---
|
|
17
18
|
description: Archive a deployed LightSpec change and update specs.
|
|
18
19
|
argument-hint: change-id
|
|
20
|
+
---`,
|
|
21
|
+
'context-check': `---
|
|
22
|
+
name: LightSpec: Context Check
|
|
23
|
+
description: Validate project context in agent instruction files and help populate missing information.
|
|
24
|
+
category: LightSpec
|
|
25
|
+
tags: [lightspec, context, validation]
|
|
19
26
|
---`
|
|
20
27
|
};
|
|
21
28
|
export class AuggieSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
@@ -2,18 +2,29 @@ import { SlashCommandId } from '../../templates/index.js';
|
|
|
2
2
|
export interface SlashCommandTarget {
|
|
3
3
|
id: SlashCommandId;
|
|
4
4
|
path: string;
|
|
5
|
-
kind: '
|
|
5
|
+
kind: 'skill';
|
|
6
6
|
}
|
|
7
|
+
export type SkillInstallLocation = 'project' | 'home';
|
|
7
8
|
export declare abstract class SlashCommandConfigurator {
|
|
8
9
|
abstract readonly toolId: string;
|
|
9
10
|
abstract readonly isAvailable: boolean;
|
|
11
|
+
private installLocation;
|
|
12
|
+
setInstallLocation(location: SkillInstallLocation): void;
|
|
10
13
|
getTargets(): SlashCommandTarget[];
|
|
11
14
|
generateAll(projectPath: string, _lightspecDir: string): Promise<string[]>;
|
|
12
15
|
updateExisting(projectPath: string, _lightspecDir: string): Promise<string[]>;
|
|
13
|
-
protected abstract getRelativePath(id: SlashCommandId): string;
|
|
14
|
-
protected abstract getFrontmatter(id: SlashCommandId): string | undefined;
|
|
15
16
|
protected getBody(id: SlashCommandId): string;
|
|
16
17
|
resolveAbsolutePath(projectPath: string, id: SlashCommandId): string;
|
|
18
|
+
private getRelativeSkillPath;
|
|
19
|
+
private getToolRoot;
|
|
20
|
+
private getHomeRootPath;
|
|
21
|
+
private getSkillName;
|
|
22
|
+
private buildSkillFile;
|
|
17
23
|
protected updateBody(filePath: string, body: string): Promise<void>;
|
|
24
|
+
private cleanupLegacyArtifacts;
|
|
25
|
+
private relativeToToolRoot;
|
|
26
|
+
private removeLegacyLightSpecFiles;
|
|
27
|
+
private walkAndRemove;
|
|
28
|
+
private isLegacyLightSpecFile;
|
|
18
29
|
}
|
|
19
30
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -1,57 +1,151 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
1
4
|
import { FileSystemUtils } from '../../../utils/file-system.js';
|
|
2
5
|
import { TemplateManager } from '../../templates/index.js';
|
|
3
6
|
import { LIGHTSPEC_MARKERS } from '../../config.js';
|
|
4
|
-
const ALL_COMMANDS = ['proposal', 'apply', 'archive'];
|
|
7
|
+
const ALL_COMMANDS = ['proposal', 'apply', 'archive', 'context-check'];
|
|
8
|
+
const TOOL_SKILL_ROOTS = {
|
|
9
|
+
'amazon-q': '.amazonq',
|
|
10
|
+
antigravity: '.antigravity',
|
|
11
|
+
auggie: '.auggie',
|
|
12
|
+
claude: '.claude',
|
|
13
|
+
cline: '.cline',
|
|
14
|
+
codex: '.codex',
|
|
15
|
+
codebuddy: '.codebuddy',
|
|
16
|
+
continue: '.continue',
|
|
17
|
+
costrict: '.cospec/lightspec',
|
|
18
|
+
crush: '.crush',
|
|
19
|
+
cursor: '.cursor',
|
|
20
|
+
factory: '.factory',
|
|
21
|
+
gemini: '.gemini',
|
|
22
|
+
'github-copilot': '.github/copilot',
|
|
23
|
+
iflow: '.iflow',
|
|
24
|
+
kilocode: '.kilocode',
|
|
25
|
+
'mistral-vibe': '.vibe',
|
|
26
|
+
opencode: '.opencode',
|
|
27
|
+
qoder: '.qoder',
|
|
28
|
+
qwen: '.qwen',
|
|
29
|
+
roocode: '.roocode',
|
|
30
|
+
windsurf: '.windsurf',
|
|
31
|
+
};
|
|
32
|
+
const TOOL_LEGACY_DIRS = {
|
|
33
|
+
'amazon-q': ['.amazonq/prompts', '.aws/amazonq/commands'],
|
|
34
|
+
antigravity: ['.antigravity/commands', '.agent/workflows'],
|
|
35
|
+
auggie: ['.augment/commands', '.auggie/commands'],
|
|
36
|
+
claude: ['.claude/commands'],
|
|
37
|
+
cline: ['.cline/commands', '.clinerules/workflows'],
|
|
38
|
+
codex: ['.codex/prompts'],
|
|
39
|
+
codebuddy: ['.codebuddy/commands'],
|
|
40
|
+
continue: ['.continue/prompts', '.continue/commands'],
|
|
41
|
+
costrict: ['.cospec/lightspec/commands'],
|
|
42
|
+
crush: ['.crush/commands'],
|
|
43
|
+
cursor: ['.cursor/commands'],
|
|
44
|
+
factory: ['.factory/commands'],
|
|
45
|
+
gemini: ['.gemini/commands'],
|
|
46
|
+
'github-copilot': ['.github/prompts', '.github/copilot/prompts'],
|
|
47
|
+
iflow: ['.iflow/commands'],
|
|
48
|
+
kilocode: ['.kilocode/commands', '.kilocode/workflows'],
|
|
49
|
+
'mistral-vibe': ['.vibe/commands', '.vibe/workflows', '.vibe/prompts'],
|
|
50
|
+
opencode: ['.opencode/command', '.opencode/commands'],
|
|
51
|
+
qoder: ['.qoder/commands', '.qoder/prompts'],
|
|
52
|
+
qwen: ['.qwen/commands', '.qwen/prompts'],
|
|
53
|
+
roocode: ['.roo/commands', '.roocode/commands'],
|
|
54
|
+
windsurf: ['.windsurf/workflows', '.windsurf/commands'],
|
|
55
|
+
};
|
|
5
56
|
export class SlashCommandConfigurator {
|
|
57
|
+
installLocation = 'project';
|
|
58
|
+
setInstallLocation(location) {
|
|
59
|
+
this.installLocation = location;
|
|
60
|
+
}
|
|
6
61
|
getTargets() {
|
|
7
62
|
return ALL_COMMANDS.map((id) => ({
|
|
8
63
|
id,
|
|
9
|
-
path: this.
|
|
10
|
-
kind: '
|
|
64
|
+
path: this.getRelativeSkillPath(id),
|
|
65
|
+
kind: 'skill',
|
|
11
66
|
}));
|
|
12
67
|
}
|
|
13
68
|
async generateAll(projectPath, _lightspecDir) {
|
|
14
69
|
const createdOrUpdated = [];
|
|
15
70
|
for (const target of this.getTargets()) {
|
|
16
71
|
const body = this.getBody(target.id);
|
|
17
|
-
const filePath =
|
|
72
|
+
const filePath = this.resolveAbsolutePath(projectPath, target.id);
|
|
18
73
|
if (await FileSystemUtils.fileExists(filePath)) {
|
|
19
74
|
await this.updateBody(filePath, body);
|
|
20
75
|
}
|
|
21
76
|
else {
|
|
22
|
-
const frontmatter =
|
|
23
|
-
const
|
|
24
|
-
if (frontmatter) {
|
|
25
|
-
sections.push(frontmatter.trim());
|
|
26
|
-
}
|
|
27
|
-
sections.push(`${LIGHTSPEC_MARKERS.start}\n${body}\n${LIGHTSPEC_MARKERS.end}`);
|
|
28
|
-
const content = sections.join('\n') + '\n';
|
|
77
|
+
const frontmatter = TemplateManager.getSlashCommandFrontmatter(target.id).trim();
|
|
78
|
+
const content = this.buildSkillFile(frontmatter, body);
|
|
29
79
|
await FileSystemUtils.writeFile(filePath, content);
|
|
30
80
|
}
|
|
31
81
|
createdOrUpdated.push(target.path);
|
|
32
82
|
}
|
|
83
|
+
await this.cleanupLegacyArtifacts(projectPath);
|
|
33
84
|
return createdOrUpdated;
|
|
34
85
|
}
|
|
35
86
|
async updateExisting(projectPath, _lightspecDir) {
|
|
36
87
|
const updated = [];
|
|
37
88
|
for (const target of this.getTargets()) {
|
|
38
|
-
const filePath =
|
|
89
|
+
const filePath = this.resolveAbsolutePath(projectPath, target.id);
|
|
39
90
|
if (await FileSystemUtils.fileExists(filePath)) {
|
|
40
91
|
const body = this.getBody(target.id);
|
|
41
92
|
await this.updateBody(filePath, body);
|
|
42
93
|
updated.push(target.path);
|
|
43
94
|
}
|
|
44
95
|
}
|
|
96
|
+
await this.cleanupLegacyArtifacts(projectPath);
|
|
45
97
|
return updated;
|
|
46
98
|
}
|
|
47
99
|
getBody(id) {
|
|
48
100
|
return TemplateManager.getSlashCommandBody(id).trim();
|
|
49
101
|
}
|
|
50
|
-
// Resolve absolute path for a given slash command target. Subclasses may override
|
|
51
|
-
// to redirect to tool-specific locations (e.g., global directories).
|
|
52
102
|
resolveAbsolutePath(projectPath, id) {
|
|
53
|
-
const
|
|
54
|
-
|
|
103
|
+
const relativePath = this.getRelativeSkillPath(id);
|
|
104
|
+
if (this.installLocation === 'project') {
|
|
105
|
+
return FileSystemUtils.joinPath(projectPath, relativePath);
|
|
106
|
+
}
|
|
107
|
+
const homeRoot = this.getHomeRootPath();
|
|
108
|
+
const rootPrefix = this.getToolRoot();
|
|
109
|
+
const normalizedRelativePath = FileSystemUtils.toPosixPath(relativePath);
|
|
110
|
+
if (!normalizedRelativePath.startsWith(`${rootPrefix}/`)) {
|
|
111
|
+
throw new Error(`Skill path '${relativePath}' does not match expected root '${rootPrefix}' for ${this.toolId}`);
|
|
112
|
+
}
|
|
113
|
+
const relativeUnderRoot = normalizedRelativePath.slice(rootPrefix.length + 1);
|
|
114
|
+
return FileSystemUtils.joinPath(homeRoot, relativeUnderRoot);
|
|
115
|
+
}
|
|
116
|
+
getRelativeSkillPath(id) {
|
|
117
|
+
const root = this.getToolRoot();
|
|
118
|
+
const skillName = this.getSkillName(id);
|
|
119
|
+
return `${root}/skills/${skillName}/SKILL.md`;
|
|
120
|
+
}
|
|
121
|
+
getToolRoot() {
|
|
122
|
+
const root = TOOL_SKILL_ROOTS[this.toolId];
|
|
123
|
+
if (!root) {
|
|
124
|
+
throw new Error(`No skill root directory configured for tool '${this.toolId}'`);
|
|
125
|
+
}
|
|
126
|
+
return root;
|
|
127
|
+
}
|
|
128
|
+
getHomeRootPath() {
|
|
129
|
+
if (this.toolId === 'codex') {
|
|
130
|
+
const codexHome = process.env.CODEX_HOME?.trim();
|
|
131
|
+
return codexHome && codexHome.length > 0
|
|
132
|
+
? codexHome
|
|
133
|
+
: FileSystemUtils.joinPath(os.homedir(), '.codex');
|
|
134
|
+
}
|
|
135
|
+
const toolRoot = this.getToolRoot();
|
|
136
|
+
const trimmed = toolRoot.startsWith('./') ? toolRoot.slice(2) : toolRoot;
|
|
137
|
+
return path.join(os.homedir(), trimmed);
|
|
138
|
+
}
|
|
139
|
+
getSkillName(id) {
|
|
140
|
+
return `lightspec-${id}`;
|
|
141
|
+
}
|
|
142
|
+
buildSkillFile(frontmatter, body) {
|
|
143
|
+
const sections = [];
|
|
144
|
+
if (frontmatter) {
|
|
145
|
+
sections.push(frontmatter);
|
|
146
|
+
}
|
|
147
|
+
sections.push(`${LIGHTSPEC_MARKERS.start}\n${body}\n${LIGHTSPEC_MARKERS.end}`);
|
|
148
|
+
return `${sections.join('\n\n')}\n`;
|
|
55
149
|
}
|
|
56
150
|
async updateBody(filePath, body) {
|
|
57
151
|
const content = await FileSystemUtils.readFile(filePath);
|
|
@@ -65,5 +159,55 @@ export class SlashCommandConfigurator {
|
|
|
65
159
|
const updatedContent = `${before}\n${body}\n${after}`;
|
|
66
160
|
await FileSystemUtils.writeFile(filePath, updatedContent);
|
|
67
161
|
}
|
|
162
|
+
async cleanupLegacyArtifacts(projectPath) {
|
|
163
|
+
const legacyDirs = TOOL_LEGACY_DIRS[this.toolId] ?? [];
|
|
164
|
+
for (const legacyDir of legacyDirs) {
|
|
165
|
+
const absoluteDir = this.installLocation === 'project'
|
|
166
|
+
? FileSystemUtils.joinPath(projectPath, legacyDir)
|
|
167
|
+
: FileSystemUtils.joinPath(this.getHomeRootPath(), this.relativeToToolRoot(legacyDir));
|
|
168
|
+
await this.removeLegacyLightSpecFiles(absoluteDir);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
relativeToToolRoot(relativePath) {
|
|
172
|
+
const rootPrefix = this.getToolRoot();
|
|
173
|
+
const normalized = FileSystemUtils.toPosixPath(relativePath);
|
|
174
|
+
if (normalized.startsWith(`${rootPrefix}/`)) {
|
|
175
|
+
return normalized.slice(rootPrefix.length + 1);
|
|
176
|
+
}
|
|
177
|
+
if (normalized === rootPrefix) {
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
return normalized.startsWith('./') ? normalized.slice(2) : normalized;
|
|
181
|
+
}
|
|
182
|
+
async removeLegacyLightSpecFiles(dirPath) {
|
|
183
|
+
if (!(await FileSystemUtils.directoryExists(dirPath))) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await this.walkAndRemove(dirPath);
|
|
187
|
+
}
|
|
188
|
+
async walkAndRemove(currentPath) {
|
|
189
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
await this.walkAndRemove(entryPath);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (this.isLegacyLightSpecFile(entryPath)) {
|
|
197
|
+
await fs.unlink(entryPath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
isLegacyLightSpecFile(filePath) {
|
|
202
|
+
const normalized = FileSystemUtils.toPosixPath(filePath).toLowerCase();
|
|
203
|
+
return (normalized.includes('/lightspec-proposal') ||
|
|
204
|
+
normalized.includes('/lightspec-apply') ||
|
|
205
|
+
normalized.includes('/lightspec-archive') ||
|
|
206
|
+
normalized.includes('/lightspec-context-check') ||
|
|
207
|
+
normalized.includes('/lightspec/proposal') ||
|
|
208
|
+
normalized.includes('/lightspec/apply') ||
|
|
209
|
+
normalized.includes('/lightspec/archive') ||
|
|
210
|
+
normalized.includes('/lightspec/context-check'));
|
|
211
|
+
}
|
|
68
212
|
}
|
|
69
213
|
//# sourceMappingURL=base.js.map
|
|
@@ -2,7 +2,8 @@ import { SlashCommandConfigurator } from './base.js';
|
|
|
2
2
|
const FILE_PATHS = {
|
|
3
3
|
proposal: '.claude/commands/lightspec/proposal.md',
|
|
4
4
|
apply: '.claude/commands/lightspec/apply.md',
|
|
5
|
-
archive: '.claude/commands/lightspec/archive.md'
|
|
5
|
+
archive: '.claude/commands/lightspec/archive.md',
|
|
6
|
+
'context-check': '.claude/commands/lightspec/context-check.md'
|
|
6
7
|
};
|
|
7
8
|
const FRONTMATTER = {
|
|
8
9
|
proposal: `---
|
|
@@ -22,6 +23,12 @@ name: LightSpec: Archive
|
|
|
22
23
|
description: Archive a deployed LightSpec change and update specs.
|
|
23
24
|
category: LightSpec
|
|
24
25
|
tags: [lightspec, archive]
|
|
26
|
+
---`,
|
|
27
|
+
'context-check': `---
|
|
28
|
+
name: LightSpec: Context Check
|
|
29
|
+
description: Validate project context in agent instruction files and help populate missing information.
|
|
30
|
+
category: LightSpec
|
|
31
|
+
tags: [lightspec, context, validation]
|
|
25
32
|
---`
|
|
26
33
|
};
|
|
27
34
|
export class ClaudeSlashCommandConfigurator extends SlashCommandConfigurator {
|