gspec 1.15.0 → 1.16.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.
Files changed (40) hide show
  1. package/README.md +20 -9
  2. package/bin/gspec.js +158 -43
  3. package/commands/gspec.analyze.md +13 -8
  4. package/commands/gspec.audit.md +202 -0
  5. package/commands/gspec.implement.md +10 -7
  6. package/commands/gspec.migrate.md +29 -15
  7. package/commands/gspec.profile.md +55 -35
  8. package/commands/gspec.style.md +64 -12
  9. package/dist/antigravity/gspec-analyze/SKILL.md +14 -9
  10. package/dist/antigravity/gspec-audit/SKILL.md +206 -0
  11. package/dist/antigravity/gspec-implement/SKILL.md +11 -8
  12. package/dist/antigravity/gspec-migrate/SKILL.md +29 -15
  13. package/dist/antigravity/gspec-profile/SKILL.md +55 -35
  14. package/dist/antigravity/gspec-style/SKILL.md +65 -13
  15. package/dist/claude/gspec-analyze/SKILL.md +14 -9
  16. package/dist/claude/gspec-audit/SKILL.md +207 -0
  17. package/dist/claude/gspec-implement/SKILL.md +11 -8
  18. package/dist/claude/gspec-migrate/SKILL.md +29 -15
  19. package/dist/claude/gspec-profile/SKILL.md +55 -35
  20. package/dist/claude/gspec-style/SKILL.md +65 -13
  21. package/dist/codex/gspec-analyze/SKILL.md +14 -9
  22. package/dist/codex/gspec-audit/SKILL.md +206 -0
  23. package/dist/codex/gspec-implement/SKILL.md +11 -8
  24. package/dist/codex/gspec-migrate/SKILL.md +29 -15
  25. package/dist/codex/gspec-profile/SKILL.md +55 -35
  26. package/dist/codex/gspec-style/SKILL.md +65 -13
  27. package/dist/cursor/gspec-analyze.mdc +14 -9
  28. package/dist/cursor/gspec-audit.mdc +205 -0
  29. package/dist/cursor/gspec-implement.mdc +11 -8
  30. package/dist/cursor/gspec-migrate.mdc +29 -15
  31. package/dist/cursor/gspec-profile.mdc +55 -35
  32. package/dist/cursor/gspec-style.mdc +65 -13
  33. package/dist/opencode/gspec-analyze/SKILL.md +14 -9
  34. package/dist/opencode/gspec-audit/SKILL.md +206 -0
  35. package/dist/opencode/gspec-implement/SKILL.md +11 -8
  36. package/dist/opencode/gspec-migrate/SKILL.md +29 -15
  37. package/dist/opencode/gspec-profile/SKILL.md +55 -35
  38. package/dist/opencode/gspec-style/SKILL.md +65 -13
  39. package/package.json +1 -1
  40. package/templates/spec-sync.md +6 -3
package/README.md CHANGED
@@ -25,7 +25,7 @@ These documents become the shared context for all subsequent AI interactions. Wh
25
25
 
26
26
  The only commands you *need* are the four fundamentals and `/gspec-implement`. Everything else exists to help when your project calls for it.
27
27
 
28
- The fundamentals give your AI tool enough context to build well — it knows what the product is, how it should look, what technologies to use, and what engineering standards to follow. From there, `/gspec-implement` can take a plain-language description and start building. The remaining commands — `/gspec-research`, `/gspec-feature`, `/gspec-architect`, and `/gspec-analyze` — add structure and rigor when the scope or complexity warrants it.
28
+ The fundamentals give your AI tool enough context to build well — it knows what the product is, how it should look, what technologies to use, and what engineering standards to follow. From there, `/gspec-implement` can take a plain-language description and start building. The remaining commands — `/gspec-research`, `/gspec-feature`, `/gspec-architect`, `/gspec-analyze`, and `/gspec-audit` — add structure and rigor when the scope or complexity warrants it.
29
29
 
30
30
  ```mermaid
31
31
  flowchart LR
@@ -42,8 +42,9 @@ flowchart LR
42
42
  Architect["4. Architect
43
43
  technical blueprint"]
44
44
 
45
- Analyze["5. Analyze
46
- reconcile specs"]
45
+ Analyze["5. Analyze & Audit
46
+ reconcile specs
47
+ check specs vs code"]
47
48
 
48
49
  Build["6. Build
49
50
  implement"]
@@ -76,7 +77,7 @@ flowchart LR
76
77
  | Command | Role | What it produces |
77
78
  |---|---|---|
78
79
  | `/gspec-profile` | Business Strategist | Product identity, audience, value proposition, positioning |
79
- | `/gspec-style` | UI/UX Designer | Visual design language, design tokens, component patterns |
80
+ | `/gspec-style` | UI/UX Designer | Visual design language, design tokens, component patterns. Produces either a renderable `style.html` design system or a `style.md` Markdown guide |
80
81
  | `/gspec-stack` | Software Architect | Technology stack, frameworks, infrastructure, architecture |
81
82
  | `/gspec-practices` | Engineering Lead | Development standards, code quality, testing, workflows |
82
83
 
@@ -104,13 +105,16 @@ Use `/gspec-feature` when you want detailed PRDs with prioritized capabilities a
104
105
 
105
106
  Use `/gspec-architect` when your feature involves significant technical complexity — new data models, service boundaries, auth flows, or integration points that benefit from upfront design. It also **identifies technical gaps and ambiguities** in your specs and proposes solutions, so that `/gspec-implement` can focus on building rather than making architectural decisions. For straightforward features, `/gspec-implement` can make sound architectural decisions on its own using your `stack` and `practices` specs.
106
107
 
107
- **5. Analyze** *(optional)* — Reconcile discrepancies across specs before building.
108
+ **5. Analyze & Audit** *(optional)* — Reconcile discrepancies before building, and keep specs honest as the codebase evolves.
108
109
 
109
110
  | Command | Role | What it does |
110
111
  |---|---|---|
111
- | `/gspec-analyze` | Specification Analyst | Cross-references all specs, identifies contradictions, and walks you through reconciling each one |
112
+ | `/gspec-analyze` | Specification Analyst | Cross-references all specs against **each other**, identifies contradictions, and walks you through reconciling each one |
113
+ | `/gspec-audit` | Specification Auditor | Cross-references specs against the **actual codebase**, finds drift (stack mismatches, stale data models, design tokens that don't match the stylesheet, capability checkboxes that lie), and walks you through updating specs to match reality |
112
114
 
113
- Use `/gspec-analyze` after `/gspec-architect` (or any time multiple specs exist) to catch conflicts before `/gspec-implement` sees them. For example, if the stack says PostgreSQL but the architecture references MongoDB, or a feature PRD defines a data model that contradicts the architecture, `/gspec-analyze` will surface the discrepancy and let you choose the resolution. Each conflict is presented one at a time with options — no new files are created, only existing specs are updated.
115
+ Use `/gspec-analyze` after `/gspec-architect` (or any time multiple specs exist) to catch spec-to-spec conflicts before `/gspec-implement` sees them. For example, if the stack says PostgreSQL but the architecture references MongoDB.
116
+
117
+ Use `/gspec-audit` periodically — before a major release, after a long sprint, or any time you suspect docs have drifted from code. Audit reads package manifests, configs, source files, and test output, then asks you per-finding whether to update the spec to match the code, keep the spec and fix the code separately, or defer. Each finding is presented one at a time with the spec quote and the code evidence side by side. Audit never modifies code.
114
118
 
115
119
  **6. Build** — Implement with full context.
116
120
 
@@ -120,6 +124,8 @@ Use `/gspec-analyze` after `/gspec-architect` (or any time multiple specs exist)
120
124
 
121
125
  **Spec Sync** — gspec includes always-on spec sync that automatically keeps your specification documents in sync as the code evolves. This is installed alongside the skills and requires no manual intervention — when code changes affect spec-documented behavior, the sync rules prompt your AI tool to update the relevant gspec files.
122
126
 
127
+ **Design-tool integration** — The style guide supports both Markdown (`style.md`) and a renderable HTML design system (`style.html`) that design-aware AI tools can open, render, and reason about directly. Drop mockups from external design tools (Figma, v0, Framer AI, etc.) into `gspec/design/` and `/gspec-implement` will use them as authoritative visual guidance when building UI.
128
+
123
129
  **Maintenance** — Keep specs up to date with the latest gspec format.
124
130
 
125
131
  | Command | Role | What it does |
@@ -184,18 +190,23 @@ All specifications live in a `gspec/` directory at your project root:
184
190
  project-root/
185
191
  └── gspec/
186
192
  ├── profile.md # Product identity and positioning
187
- ├── style.md # Visual design language
193
+ ├── style.html # Visual design language (HTML — renderable design system)
194
+ │ # or style.md if you prefer a Markdown style guide
188
195
  ├── stack.md # Technology stack and architecture
189
196
  ├── practices.md # Development standards
190
197
  ├── architecture.md # Technical architecture blueprint
191
198
  ├── research.md # Competitive analysis and feature gaps
199
+ ├── design/ # Optional — external mockups read during implementation
200
+ │ ├── dashboard.html
201
+ │ ├── checkout-flow.png
202
+ │ └── ...
192
203
  └── features/
193
204
  ├── user-authentication.md
194
205
  ├── dashboard-analytics.md
195
206
  └── ...
196
207
  ```
197
208
 
198
- These are standard Markdown files. They live in your repo, are version-controlled with your code, and are readable by both humans and AI tools.
209
+ Most specs are Markdown. The style guide can also be a self-contained HTML file (`style.html`) that renders the design system as live swatches, typography specimens, and styled component previews — ideal for design-aware AI tools. The optional `gspec/design/` folder holds mockups (HTML, SVG, PNG, JPG) exported from external design tools like Figma, v0, or Framer AI; `/gspec-implement` reads them to reason about layout and visual intent. All files live in your repo, are version-controlled with your code, and are readable by both humans and AI tools.
199
210
 
200
211
  ## Key Design Decisions
201
212
 
package/bin/gspec.js CHANGED
@@ -255,11 +255,13 @@ async function seedFromSavedSpecs(cwd) {
255
255
  const gspecDir = join(cwd, 'gspec');
256
256
  const filesToWrite = [];
257
257
 
258
+ // `dest` is null for types whose destination filename depends on the saved file's extension
259
+ // (currently `styles`, which may be .md or .html).
258
260
  const CATEGORY_ORDER = [
259
261
  { type: 'profiles', label: 'Select a profile', dest: 'profile.md', mode: 'single' },
260
262
  { type: 'practices', label: 'Select practices', dest: 'practices.md', mode: 'single' },
261
263
  { type: 'stacks', label: 'Select a stack', dest: 'stack.md', mode: 'single' },
262
- { type: 'styles', label: 'Select a style', dest: 'style.md', mode: 'single' },
264
+ { type: 'styles', label: 'Select a style', dest: null, mode: 'single' },
263
265
  { type: 'features', label: 'Select features (optional)', dest: null, mode: 'multi' },
264
266
  ];
265
267
 
@@ -275,19 +277,24 @@ async function seedFromSavedSpecs(cwd) {
275
277
  : await promptSelect(cat.label, [...specs, NONE_OPTION]);
276
278
 
277
279
  if (selected !== '_none') {
280
+ const savedFilename = await resolveSavedSpecFilename(cat.type, selected);
281
+ if (!savedFilename) continue;
282
+ const destFilename = cat.dest || destFilenameForRestoredSpec(cat.type, savedFilename);
278
283
  filesToWrite.push({
279
- src: join(gspecHome, cat.type, `${selected}.md`),
280
- dest: join(gspecDir, cat.dest),
281
- label: `gspec/${cat.dest}`,
284
+ src: join(gspecHome, cat.type, savedFilename),
285
+ dest: join(gspecDir, destFilename),
286
+ label: `gspec/${destFilename}`,
282
287
  });
283
288
  }
284
289
  } else {
285
290
  let selectedSlugs = await promptMultiSelect(cat.label, specs);
286
291
  for (const slug of selectedSlugs) {
292
+ const savedFilename = await resolveSavedSpecFilename(cat.type, slug);
293
+ if (!savedFilename) continue;
287
294
  filesToWrite.push({
288
- src: join(gspecHome, cat.type, `${slug}.md`),
289
- dest: join(gspecDir, 'features', `${slug}.md`),
290
- label: `gspec/features/${slug}.md`,
295
+ src: join(gspecHome, cat.type, savedFilename),
296
+ dest: join(gspecDir, 'features', savedFilename),
297
+ label: `gspec/features/${savedFilename}`,
291
298
  });
292
299
  }
293
300
  }
@@ -561,6 +568,11 @@ const MIGRATE_COMMANDS = {
561
568
  };
562
569
 
563
570
  function parseSpecVersion(content) {
571
+ // HTML spec files store the version as a first-line comment:
572
+ // <!-- spec-version: v1 -->
573
+ const htmlMatch = content.match(/^\s*<!--\s*spec-version:\s*([^\s-][^-]*?)\s*-->/);
574
+ if (htmlMatch) return htmlMatch[1].trim();
575
+
564
576
  const match = content.match(/^---\n([\s\S]*?)\n---/);
565
577
  if (!match) return null;
566
578
  const newMatch = match[1].match(/^spec-version:\s*(.+)$/m);
@@ -578,6 +590,11 @@ async function collectGspecFiles(gspecDir) {
578
590
  if (entry.endsWith('.md') && entry.toLowerCase() !== 'readme.md') {
579
591
  files.push({ path: join(gspecDir, entry), label: `gspec/${entry}` });
580
592
  }
593
+ // Pick up style.html (the HTML-format style guide) alongside Markdown specs.
594
+ // Other .html files under gspec/ are not gspec-owned and are skipped.
595
+ if (entry === 'style.html') {
596
+ files.push({ path: join(gspecDir, entry), label: `gspec/${entry}` });
597
+ }
581
598
  }
582
599
 
583
600
  for (const subdir of ['features', 'epics']) {
@@ -648,19 +665,82 @@ const GSPEC_TYPE_MAP = {
648
665
  'profile.md': 'profiles',
649
666
  'stack.md': 'stacks',
650
667
  'style.md': 'styles',
668
+ 'style.html': 'styles',
651
669
  'practices.md': 'practices',
652
670
  };
653
671
 
654
672
  // Reverse: restore type folder → gspec/ destination filename
673
+ // The `styles` entry is a function because the destination depends on the saved file's extension.
655
674
  const RESTORE_DEST_MAP = {
656
675
  profiles: 'profile.md',
657
676
  stacks: 'stack.md',
658
- styles: 'style.md',
677
+ styles: 'style.md', // default when the saved extension is .md
659
678
  practices: 'practices.md',
660
679
  features: null, // features keep their own filename
661
680
  };
662
681
 
682
+ // Given a save-type folder and a saved slug, resolve the actual filename in ~/.gspec/<type>/.
683
+ // Styles can be stored as .md or .html; all others are .md.
684
+ async function resolveSavedSpecFilename(type, slug) {
685
+ const dir = join(GSPEC_HOME, type);
686
+ const candidates = type === 'styles'
687
+ ? [`${slug}.md`, `${slug}.html`]
688
+ : [`${slug}.md`];
689
+ for (const candidate of candidates) {
690
+ try {
691
+ await stat(join(dir, candidate));
692
+ return candidate;
693
+ } catch (e) {
694
+ if (e.code !== 'ENOENT') throw e;
695
+ }
696
+ }
697
+ return null;
698
+ }
699
+
700
+ // Destination filename in a project's gspec/ directory for a restored saved spec.
701
+ // For styles, preserve the saved file's extension so a .html style guide restores as style.html.
702
+ function destFilenameForRestoredSpec(type, savedFilename) {
703
+ if (type === 'features') return savedFilename;
704
+ if (type === 'styles') {
705
+ return savedFilename.endsWith('.html') ? 'style.html' : 'style.md';
706
+ }
707
+ return RESTORE_DEST_MAP[type];
708
+ }
709
+
710
+ function isHtmlSpec(content) {
711
+ const head = content.slice(0, 500).trimStart().toLowerCase();
712
+ if (head.startsWith('<!doctype') || head.startsWith('<html')) return true;
713
+ // Leading HTML comments before <!DOCTYPE> (where we store HTML spec metadata)
714
+ if (head.startsWith('<!--')) {
715
+ // Peek further to see if a <!DOCTYPE> / <html> follows the comments
716
+ const scan = content.slice(0, 2000).toLowerCase();
717
+ return /<!doctype|<html/.test(scan);
718
+ }
719
+ return false;
720
+ }
721
+
722
+ function parseHtmlMetadata(content) {
723
+ // Consume consecutive `<!-- key: value -->` comments at the top of the file,
724
+ // stopping at the first non-comment, non-blank line.
725
+ const fields = {};
726
+ const lines = content.split('\n');
727
+ let bodyStart = 0;
728
+ for (let i = 0; i < lines.length; i++) {
729
+ const trimmed = lines[i].trim();
730
+ if (trimmed === '') {
731
+ bodyStart = i + 1;
732
+ continue;
733
+ }
734
+ const match = trimmed.match(/^<!--\s*([\w-]+):\s*(.+?)\s*-->$/);
735
+ if (!match) break;
736
+ fields[match[1]] = match[2];
737
+ bodyStart = i + 1;
738
+ }
739
+ return { fields, body: lines.slice(bodyStart).join('\n') };
740
+ }
741
+
663
742
  function parseFrontmatter(content) {
743
+ if (isHtmlSpec(content)) return parseHtmlMetadata(content);
664
744
  const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
665
745
  if (!match) return { fields: {}, body: content };
666
746
  const fields = {};
@@ -673,7 +753,34 @@ function parseFrontmatter(content) {
673
753
  return { fields, body: content.slice(match[0].length) };
674
754
  }
675
755
 
756
+ function setHtmlMetadataField(content, key, value) {
757
+ const lines = content.split('\n');
758
+ let lastCommentIndex = -1;
759
+ for (let i = 0; i < lines.length; i++) {
760
+ const trimmed = lines[i].trim();
761
+ if (trimmed === '') continue;
762
+ if (trimmed.startsWith('<!--') && trimmed.endsWith('-->')) {
763
+ lastCommentIndex = i;
764
+ const m = trimmed.match(/^<!--\s*([\w-]+):\s*(.+?)\s*-->$/);
765
+ if (m && m[1] === key) {
766
+ lines[i] = `<!-- ${key}: ${value} -->`;
767
+ return lines.join('\n');
768
+ }
769
+ continue;
770
+ }
771
+ break;
772
+ }
773
+ const newComment = `<!-- ${key}: ${value} -->`;
774
+ if (lastCommentIndex >= 0) {
775
+ lines.splice(lastCommentIndex + 1, 0, newComment);
776
+ } else {
777
+ lines.unshift(newComment);
778
+ }
779
+ return lines.join('\n');
780
+ }
781
+
676
782
  function setFrontmatterField(content, key, value) {
783
+ if (isHtmlSpec(content)) return setHtmlMetadataField(content, key, value);
677
784
  const match = content.match(/^(---\s*\n)([\s\S]*?)(\n---)/);
678
785
  if (!match) {
679
786
  // No frontmatter — create one
@@ -711,10 +818,11 @@ async function collectSavableFiles(cwd) {
711
818
  throw e;
712
819
  }
713
820
 
714
- // Top-level spec files
821
+ // Top-level spec files. Accept the Markdown specs plus the HTML style guide.
715
822
  const topEntries = await readdir(gspecDir);
716
823
  for (const entry of topEntries) {
717
- if (!entry.endsWith('.md') || entry.toLowerCase() === 'readme.md') continue;
824
+ if (entry.toLowerCase() === 'readme.md') continue;
825
+ if (!entry.endsWith('.md') && entry !== 'style.html') continue;
718
826
  const type = GSPEC_TYPE_MAP[entry];
719
827
  if (!type) continue;
720
828
  files.push({
@@ -769,6 +877,8 @@ async function saveSpec(cwd) {
769
877
  }
770
878
 
771
879
  const selected = files[num - 1];
880
+ // Preserve the source file's extension when saving (.md for most specs, .html for style.html).
881
+ const ext = selected.path.endsWith('.html') ? '.html' : '.md';
772
882
 
773
883
  // Read source content and look for an existing name in frontmatter
774
884
  let content = await readFile(selected.path, 'utf-8');
@@ -779,7 +889,7 @@ async function saveSpec(cwd) {
779
889
  let overwriteConfirmed = false;
780
890
 
781
891
  if (existingName) {
782
- const existingPath = join(GSPEC_HOME, selected.type, `${existingName}.md`);
892
+ const existingPath = join(GSPEC_HOME, selected.type, `${existingName}${ext}`);
783
893
  let savedExists = false;
784
894
  try {
785
895
  await stat(existingPath);
@@ -790,7 +900,7 @@ async function saveSpec(cwd) {
790
900
 
791
901
  if (savedExists) {
792
902
  const overwrite = await promptConfirmYes(
793
- chalk.bold(`\n Overwrite existing ~/.gspec/${selected.type}/${existingName}.md? [Y/n]: `)
903
+ chalk.bold(`\n Overwrite existing ~/.gspec/${selected.type}/${existingName}${ext}? [Y/n]: `)
794
904
  );
795
905
  if (overwrite) {
796
906
  name = existingName;
@@ -825,16 +935,16 @@ async function saveSpec(cwd) {
825
935
  }
826
936
  }
827
937
 
828
- // Write to ~/.gspec/{type}/{name}.md
938
+ // Write to ~/.gspec/{type}/{name}{ext}
829
939
  const destDir = join(GSPEC_HOME, selected.type);
830
- const destPath = join(destDir, `${name}.md`);
940
+ const destPath = join(destDir, `${name}${ext}`);
831
941
  await mkdir(destDir, { recursive: true });
832
942
 
833
943
  // Check for conflict unless overwrite was already confirmed above
834
944
  if (!overwriteConfirmed) {
835
945
  try {
836
946
  await stat(destPath);
837
- const overwrite = await promptConfirm(chalk.yellow(`\n ${selected.type}/${name} already exists. Overwrite? [y/N]: `));
947
+ const overwrite = await promptConfirm(chalk.yellow(`\n ${selected.type}/${name}${ext} already exists. Overwrite? [y/N]: `));
838
948
  if (!overwrite) {
839
949
  console.log(chalk.dim('\n Save cancelled.\n'));
840
950
  return;
@@ -848,7 +958,13 @@ async function saveSpec(cwd) {
848
958
  content = content.replace(/- \[x\]/g, '- [ ]');
849
959
 
850
960
  await writeFile(destPath, content, 'utf-8');
851
- console.log(chalk.green(`\n ✓ Saved to ~/.gspec/${selected.type}/${name}.md\n`));
961
+ console.log(chalk.green(`\n ✓ Saved to ~/.gspec/${selected.type}/${name}${ext}\n`));
962
+ }
963
+
964
+ function isSavedSpecFile(type, filename) {
965
+ if (filename.endsWith('.md')) return true;
966
+ if (type === 'styles' && filename.endsWith('.html')) return true;
967
+ return false;
852
968
  }
853
969
 
854
970
  async function listSavedTypes() {
@@ -860,8 +976,7 @@ async function listSavedTypes() {
860
976
  const info = await stat(join(GSPEC_HOME, entry));
861
977
  if (info.isDirectory()) {
862
978
  const files = await readdir(join(GSPEC_HOME, entry));
863
- const mdFiles = files.filter((f) => f.endsWith('.md'));
864
- if (mdFiles.length > 0) types.push(entry);
979
+ if (files.some((f) => isSavedSpecFile(entry, f))) types.push(entry);
865
980
  }
866
981
  } catch { /* skip */ }
867
982
  }
@@ -877,11 +992,11 @@ async function listSavedSpecs(type) {
877
992
  const entries = await readdir(dir);
878
993
  const specs = [];
879
994
  for (const entry of entries) {
880
- if (!entry.endsWith('.md')) continue;
995
+ if (!isSavedSpecFile(type, entry)) continue;
881
996
  const content = await readFile(join(dir, entry), 'utf-8');
882
997
  const { fields } = parseFrontmatter(content);
883
998
  specs.push({
884
- slug: entry.replace(/\.md$/, ''),
999
+ slug: entry.replace(/\.(md|html)$/, ''),
885
1000
  description: fields.description || '',
886
1001
  });
887
1002
  }
@@ -954,25 +1069,20 @@ async function restoreSpec(specPath, cwd) {
954
1069
  }
955
1070
 
956
1071
  async function restoreFile(type, name, cwd) {
957
- const srcPath = join(GSPEC_HOME, type, `${name}.md`);
958
-
959
- try {
960
- await stat(srcPath);
961
- } catch (e) {
962
- if (e.code === 'ENOENT') {
963
- console.error(chalk.red(`\n Not found: ~/.gspec/${type}/${name}.md\n`));
964
- process.exit(1);
965
- }
966
- throw e;
1072
+ const savedFilename = await resolveSavedSpecFilename(type, name);
1073
+ if (!savedFilename) {
1074
+ console.error(chalk.red(`\n Not found: ~/.gspec/${type}/${name}.md\n`));
1075
+ process.exit(1);
967
1076
  }
1077
+ const srcPath = join(GSPEC_HOME, type, savedFilename);
968
1078
 
969
1079
  const gspecDir = join(cwd, 'gspec');
970
1080
  let destPath;
971
1081
 
972
1082
  if (type === 'features') {
973
- destPath = join(gspecDir, 'features', `${name}.md`);
1083
+ destPath = join(gspecDir, 'features', savedFilename);
974
1084
  } else {
975
- const destFile = RESTORE_DEST_MAP[type];
1085
+ const destFile = destFilenameForRestoredSpec(type, savedFilename);
976
1086
  if (!destFile) {
977
1087
  console.error(chalk.red(`\n Unknown spec type: ${type}\n`));
978
1088
  process.exit(1);
@@ -1201,10 +1311,18 @@ async function restorePlaybook(name, cwd) {
1201
1311
  restorations.push({ type: 'features', slug: f });
1202
1312
  }
1203
1313
 
1314
+ // Resolve each restoration to its actual saved filename (styles may be .md or .html)
1315
+ for (const r of restorations) {
1316
+ r.savedFilename = await resolveSavedSpecFilename(r.type, r.slug);
1317
+ }
1318
+
1204
1319
  // Check for existing files
1205
1320
  const existing = [];
1206
1321
  for (const r of restorations) {
1207
- const destFile = r.type === 'features' ? join('features', `${r.slug}.md`) : RESTORE_DEST_MAP[r.type];
1322
+ if (!r.savedFilename) continue;
1323
+ const destFile = r.type === 'features'
1324
+ ? join('features', r.savedFilename)
1325
+ : destFilenameForRestoredSpec(r.type, r.savedFilename);
1208
1326
  const destPath = join(gspecDir, destFile);
1209
1327
  try {
1210
1328
  await stat(destPath);
@@ -1230,18 +1348,15 @@ async function restorePlaybook(name, cwd) {
1230
1348
  // Restore all specs
1231
1349
  const outdated = [];
1232
1350
  for (const r of restorations) {
1233
- const srcFile = join(GSPEC_HOME, r.type, `${r.slug}.md`);
1234
- try {
1235
- await stat(srcFile);
1236
- } catch (e) {
1237
- if (e.code === 'ENOENT') {
1238
- console.log(` ${chalk.yellow('!')} Skipped ${r.type}/${r.slug} — not found in ~/.gspec/`);
1239
- continue;
1240
- }
1241
- throw e;
1351
+ if (!r.savedFilename) {
1352
+ console.log(` ${chalk.yellow('!')} Skipped ${r.type}/${r.slug} — not found in ~/.gspec/`);
1353
+ continue;
1242
1354
  }
1355
+ const srcFile = join(GSPEC_HOME, r.type, r.savedFilename);
1243
1356
 
1244
- const destFile = r.type === 'features' ? join('features', `${r.slug}.md`) : RESTORE_DEST_MAP[r.type];
1357
+ const destFile = r.type === 'features'
1358
+ ? join('features', r.savedFilename)
1359
+ : destFilenameForRestoredSpec(r.type, r.savedFilename);
1245
1360
  const destPath = join(gspecDir, destFile);
1246
1361
  await mkdir(dirname(destPath), { recursive: true });
1247
1362
  const specContent = await readFile(srcFile, 'utf-8');
@@ -4,6 +4,8 @@ Your task is to read all existing gspec specification documents, identify discre
4
4
 
5
5
  This command is designed to be run **after** `gspec-architect` (or at any point when multiple specs exist) and **before** `gspec-implement`, to ensure the implementing agent receives a coherent, conflict-free set of instructions.
6
6
 
7
+ > **Analyze vs. audit.** `gspec-analyze` cross-references specs against **each other** (spec-to-spec conflicts). `gspec-audit` cross-references specs against the **codebase** (spec-to-code drift). If the user's intent is "do my docs still reflect what the code does?", route to `gspec-audit` instead.
8
+
7
9
  You should:
8
10
  - Read and deeply cross-reference all available gspec documents
9
11
  - Identify concrete discrepancies — not style differences or minor wording variations, but substantive contradictions where two specs disagree on a fact, technology, behavior, or requirement
@@ -23,11 +25,12 @@ Read **every** available gspec document in this order:
23
25
 
24
26
  1. `gspec/profile.md` — Product identity, scope, audience, and positioning
25
27
  2. `gspec/stack.md` — Technology choices, frameworks, infrastructure
26
- 3. `gspec/style.md` — Visual design language, tokens, component styling
27
- 4. `gspec/practices.md`Development standards, testing, conventions
28
- 5. `gspec/architecture.md` — Technical blueprint: project structure, data model, API design, environment
29
- 6. `gspec/research.md` — Competitive analysis and feature proposals
30
- 7. `gspec/features/*.md` — Individual feature requirements and dependencies
28
+ 3. `gspec/style.md` **or** `gspec/style.html` — Visual design language, tokens, component styling. Read whichever exists; read both if both are present. For an HTML style guide, the canonical token values are the CSS custom properties defined in the `<style>` block — inspect those when cross-referencing token-related claims
29
+ 4. `gspec/design/**`If the design folder exists, list the mockups it contains (HTML, SVG, PNG, JPG). You do not need to deeply parse images, but note which screens or flows have mockups so you can flag features that reference a screen lacking a mockup, or mockups that depict behavior contradicted by a feature PRD
30
+ 5. `gspec/practices.md` — Development standards, testing, conventions
31
+ 6. `gspec/architecture.md` — Technical blueprint: project structure, data model, API design, environment
32
+ 7. `gspec/research.md` — Competitive analysis and feature proposals
33
+ 8. `gspec/features/*.md` — Individual feature requirements and dependencies
31
34
 
32
35
  If fewer than two spec files exist, inform the user that there is nothing to cross-reference and stop.
33
36
 
@@ -53,8 +56,10 @@ Systematically compare specs against each other. Look for these categories of di
53
56
  - Authentication or authorization requirements differ between specs
54
57
 
55
58
  #### Design & Style Conflicts
56
- - A feature PRD references visual patterns or components that contradict `style.md`
57
- - Architecture's component structure doesn't align with the design system in `style.md`
59
+ - A feature PRD references visual patterns or components that contradict the style guide (`style.md` or `style.html`)
60
+ - Architecture's component structure doesn't align with the design system in the style guide
61
+ - A mockup in `gspec/design/` depicts a layout, color, or component treatment that contradicts the style guide's tokens or patterns
62
+ - A feature PRD describes a screen that has a mockup in `gspec/design/`, but the PRD and mockup disagree on behavior or composition
58
63
 
59
64
  #### Practice & Convention Conflicts
60
65
  - Architecture's file naming, testing approach, or code organization contradicts `practices.md`
@@ -123,7 +128,7 @@ When updating specs to resolve a discrepancy:
123
128
 
124
129
  - **Surgical updates only** — change the minimum text needed to resolve the conflict
125
130
  - **Preserve format and tone** — match the existing document's style, heading structure, and voice
126
- - **Preserve `spec-version` frontmatter** — do not alter or remove it
131
+ - **Preserve `spec-version` metadata** — do not alter or remove it. For Markdown files this is YAML frontmatter (`---\nspec-version: ...\n---`); for HTML style guides it is the first-line comment (`<!-- spec-version: ... -->`). Both must be left intact.
127
132
  - **Do not rewrite sections** — if a one-line change resolves the conflict, make a one-line change
128
133
  - **Do not add changelog annotations** — the git history captures what changed
129
134