lightspec 0.1.1 → 0.2.1

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.
Files changed (59) hide show
  1. package/README.md +11 -10
  2. package/dist/cli/index.js +12 -24
  3. package/dist/commands/feedback.js +2 -2
  4. package/dist/core/config.d.ts +1 -0
  5. package/dist/core/config.js +1 -0
  6. package/dist/core/configurators/slash/amazon-q.js +6 -2
  7. package/dist/core/configurators/slash/antigravity.js +4 -2
  8. package/dist/core/configurators/slash/auggie.js +8 -1
  9. package/dist/core/configurators/slash/base.d.ts +14 -3
  10. package/dist/core/configurators/slash/base.js +160 -16
  11. package/dist/core/configurators/slash/claude.js +8 -1
  12. package/dist/core/configurators/slash/cline.js +4 -2
  13. package/dist/core/configurators/slash/codebuddy.js +8 -1
  14. package/dist/core/configurators/slash/codex.d.ts +0 -8
  15. package/dist/core/configurators/slash/codex.js +0 -103
  16. package/dist/core/configurators/slash/continue.js +8 -1
  17. package/dist/core/configurators/slash/costrict.js +4 -0
  18. package/dist/core/configurators/slash/crush.js +8 -1
  19. package/dist/core/configurators/slash/cursor.js +8 -1
  20. package/dist/core/configurators/slash/factory.js +8 -1
  21. package/dist/core/configurators/slash/gemini.js +4 -2
  22. package/dist/core/configurators/slash/github-copilot.js +6 -2
  23. package/dist/core/configurators/slash/iflow.js +8 -1
  24. package/dist/core/configurators/slash/kilocode.js +2 -1
  25. package/dist/core/configurators/slash/mistral-vibe.d.ts +6 -0
  26. package/dist/core/configurators/slash/mistral-vibe.js +6 -0
  27. package/dist/core/configurators/slash/opencode.js +4 -0
  28. package/dist/core/configurators/slash/qoder.js +8 -1
  29. package/dist/core/configurators/slash/qwen.js +4 -2
  30. package/dist/core/configurators/slash/registry.d.ts +2 -1
  31. package/dist/core/configurators/slash/registry.js +8 -0
  32. package/dist/core/configurators/slash/roocode.js +4 -2
  33. package/dist/core/configurators/slash/toml-base.d.ts +0 -6
  34. package/dist/core/configurators/slash/toml-base.js +0 -49
  35. package/dist/core/configurators/slash/windsurf.js +4 -2
  36. package/dist/core/init.d.ts +6 -0
  37. package/dist/core/init.js +49 -22
  38. package/dist/core/templates/agents-template.d.ts +1 -1
  39. package/dist/core/templates/agents-template.js +4 -7
  40. package/dist/core/templates/apply-template.d.ts +3 -0
  41. package/dist/core/templates/apply-template.js +21 -0
  42. package/dist/core/templates/archive-template.d.ts +3 -0
  43. package/dist/core/templates/archive-template.js +29 -0
  44. package/dist/core/templates/context-check-template.d.ts +3 -0
  45. package/dist/core/templates/context-check-template.js +128 -0
  46. package/dist/core/templates/index.d.ts +1 -0
  47. package/dist/core/templates/index.js +4 -6
  48. package/dist/core/templates/proposal-template.d.ts +3 -0
  49. package/dist/core/templates/proposal-template.js +30 -0
  50. package/dist/core/templates/skill-common-template.d.ts +2 -0
  51. package/dist/core/templates/skill-common-template.js +5 -0
  52. package/dist/core/templates/slash-command-templates.d.ts +3 -1
  53. package/dist/core/templates/slash-command-templates.js +17 -43
  54. package/dist/core/update.js +5 -5
  55. package/dist/telemetry/index.d.ts +2 -3
  56. package/dist/telemetry/index.js +2 -3
  57. package/dist/types/index.d.ts +8 -0
  58. package/dist/types/index.js +5 -0
  59. 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
- <source srcset="assets/lightspec_pixel_dark.svg" media="(prefers-color-scheme: dark)">
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="64">
7
5
  </picture>
8
6
  </a>
9
7
 
@@ -19,7 +17,7 @@
19
17
  </p>
20
18
 
21
19
  <p align="center">
22
- <img src="assets/lightspec_dashboard.png" alt="LightSpec dashboard preview" width="90%">
20
+ <img src="assets/openspec_dashboard.png" alt="LightSpec dashboard preview" width="90%">
23
21
  </p>
24
22
 
25
23
  <p align="center">
@@ -28,7 +26,7 @@
28
26
 
29
27
  # LightSpec
30
28
 
31
- A fork of [LightSpec](https://github.com/Fission-AI/LightSpec) v0.23.0
29
+ A fork of [LightSpec](https://github.com/augmenter-dev/LightSpec), focused on simplicity and skill-based agents.
32
30
 
33
31
  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
32
 
@@ -207,14 +205,14 @@ lightspec init
207
205
 
208
206
  ### Optional: Populate Project Context
209
207
 
210
- After `lightspec init` completes, you'll receive a suggested prompt to help populate your project context:
208
+ After `lightspec init` completes, you'll receive a suggested command to validate and populate your project context:
211
209
 
212
210
  ```text
213
- Populate your project context:
214
- "Please read lightspec/project.md and help me fill it out with details about my project, tech stack, and conventions"
211
+ Validate and populate your project context:
212
+ "/lightspec:context-check"
215
213
  ```
216
214
 
217
- Use `lightspec/project.md` to define project-level conventions, standards, architectural patterns, and other guidelines that should be followed across all changes.
215
+ 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
216
 
219
217
  ### Create Your First Change
220
218
 
@@ -379,6 +377,9 @@ Deltas are "patches" that show how specs change:
379
377
 
380
378
  ## How LightSpec Compares
381
379
 
380
+ ### vs. OpenSpec
381
+ 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.
382
+
382
383
  ### vs. spec-kit
383
384
  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
385
 
@@ -424,7 +425,7 @@ See [MAINTAINERS.md](MAINTAINERS.md) for the list of core maintainers and adviso
424
425
  ## Agent Skills
425
426
 
426
427
  LightSpec includes 3 Claude Code skills for the core development workflow:
427
- - `lightspec-new` - Create a new change
428
+ - `lightspec-proposal` - Create a new change
428
429
  - `lightspec-apply` - Get apply instructions for implementation
429
430
  - `lightspec-archive` - Archive a completed change
430
431
 
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
- // Note: preAction receives (thisCommand, actionCommand) where:
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 (use actionCommand to get the actual subcommand)
57
- const commandPath = getCommandPath(actionCommand);
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 = 'Fission-AI/LightSpec';
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
- 'Fission-AI/LightSpec',
117
+ 'augmenter-dev/LightSpec',
118
118
  '--title',
119
119
  title,
120
120
  '--body',
@@ -5,6 +5,7 @@ export declare const LIGHTSPEC_MARKERS: {
5
5
  };
6
6
  export interface LightSpecConfig {
7
7
  aiTools: string[];
8
+ skillLocation: 'project' | 'home';
8
9
  }
9
10
  export interface AIToolOption {
10
11
  name: string;
@@ -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: 'slash';
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.getRelativePath(id),
10
- kind: 'slash'
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 = FileSystemUtils.joinPath(projectPath, target.path);
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 = this.getFrontmatter(target.id);
23
- const sections = [];
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 = FileSystemUtils.joinPath(projectPath, target.path);
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 rel = this.getRelativePath(id);
54
- return FileSystemUtils.joinPath(projectPath, rel);
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 {
@@ -2,7 +2,8 @@ import { SlashCommandConfigurator } from './base.js';
2
2
  const FILE_PATHS = {
3
3
  proposal: '.clinerules/workflows/lightspec-proposal.md',
4
4
  apply: '.clinerules/workflows/lightspec-apply.md',
5
- archive: '.clinerules/workflows/lightspec-archive.md'
5
+ archive: '.clinerules/workflows/lightspec-archive.md',
6
+ 'context-check': '.cline/commands/lightspec-context-check.md'
6
7
  };
7
8
  export class ClineSlashCommandConfigurator extends SlashCommandConfigurator {
8
9
  toolId = 'cline';
@@ -14,7 +15,8 @@ export class ClineSlashCommandConfigurator extends SlashCommandConfigurator {
14
15
  const descriptions = {
15
16
  proposal: 'Scaffold a new LightSpec change and validate strictly.',
16
17
  apply: 'Implement an approved LightSpec change and keep tasks in sync.',
17
- archive: 'Archive a deployed LightSpec change and update specs.'
18
+ archive: 'Archive a deployed LightSpec change and update specs.',
19
+ 'context-check': 'Validate project context in agent instruction files and help populate missing information.'
18
20
  };
19
21
  const description = descriptions[id];
20
22
  return `# LightSpec: ${id.charAt(0).toUpperCase() + id.slice(1)}\n\n${description}`;
@@ -2,7 +2,8 @@ import { SlashCommandConfigurator } from './base.js';
2
2
  const FILE_PATHS = {
3
3
  proposal: '.codebuddy/commands/lightspec/proposal.md',
4
4
  apply: '.codebuddy/commands/lightspec/apply.md',
5
- archive: '.codebuddy/commands/lightspec/archive.md'
5
+ archive: '.codebuddy/commands/lightspec/archive.md',
6
+ 'context-check': '.codebuddy/commands/lightspec-context-check.md'
6
7
  };
7
8
  const FRONTMATTER = {
8
9
  proposal: `---
@@ -19,6 +20,12 @@ argument-hint: "[change-id]"
19
20
  name: LightSpec: Archive
20
21
  description: "Archive a deployed LightSpec change and update specs."
21
22
  argument-hint: "[change-id]"
23
+ ---`,
24
+ 'context-check': `---
25
+ name: LightSpec: Context Check
26
+ description: Validate project context in agent instruction files and help populate missing information.
27
+ category: LightSpec
28
+ tags: [lightspec, context, validation]
22
29
  ---`
23
30
  };
24
31
  export class CodeBuddySlashCommandConfigurator extends SlashCommandConfigurator {
@@ -1,14 +1,6 @@
1
1
  import { SlashCommandConfigurator } from "./base.js";
2
- import { SlashCommandId } from "../../templates/index.js";
3
2
  export declare class CodexSlashCommandConfigurator extends SlashCommandConfigurator {
4
3
  readonly toolId = "codex";
5
4
  readonly isAvailable = true;
6
- protected getRelativePath(id: SlashCommandId): string;
7
- protected getFrontmatter(id: SlashCommandId): string | undefined;
8
- private getGlobalPromptsDir;
9
- generateAll(projectPath: string, _lightspecDir: string): Promise<string[]>;
10
- updateExisting(projectPath: string, _lightspecDir: string): Promise<string[]>;
11
- private updateFullFile;
12
- resolveAbsolutePath(_projectPath: string, id: SlashCommandId): string;
13
5
  }
14
6
  //# sourceMappingURL=codex.d.ts.map