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.
- package/README.md +20 -9
- package/bin/gspec.js +158 -43
- package/commands/gspec.analyze.md +13 -8
- package/commands/gspec.audit.md +202 -0
- package/commands/gspec.implement.md +10 -7
- package/commands/gspec.migrate.md +29 -15
- package/commands/gspec.profile.md +55 -35
- package/commands/gspec.style.md +64 -12
- package/dist/antigravity/gspec-analyze/SKILL.md +14 -9
- package/dist/antigravity/gspec-audit/SKILL.md +206 -0
- package/dist/antigravity/gspec-implement/SKILL.md +11 -8
- package/dist/antigravity/gspec-migrate/SKILL.md +29 -15
- package/dist/antigravity/gspec-profile/SKILL.md +55 -35
- package/dist/antigravity/gspec-style/SKILL.md +65 -13
- package/dist/claude/gspec-analyze/SKILL.md +14 -9
- package/dist/claude/gspec-audit/SKILL.md +207 -0
- package/dist/claude/gspec-implement/SKILL.md +11 -8
- package/dist/claude/gspec-migrate/SKILL.md +29 -15
- package/dist/claude/gspec-profile/SKILL.md +55 -35
- package/dist/claude/gspec-style/SKILL.md +65 -13
- package/dist/codex/gspec-analyze/SKILL.md +14 -9
- package/dist/codex/gspec-audit/SKILL.md +206 -0
- package/dist/codex/gspec-implement/SKILL.md +11 -8
- package/dist/codex/gspec-migrate/SKILL.md +29 -15
- package/dist/codex/gspec-profile/SKILL.md +55 -35
- package/dist/codex/gspec-style/SKILL.md +65 -13
- package/dist/cursor/gspec-analyze.mdc +14 -9
- package/dist/cursor/gspec-audit.mdc +205 -0
- package/dist/cursor/gspec-implement.mdc +11 -8
- package/dist/cursor/gspec-migrate.mdc +29 -15
- package/dist/cursor/gspec-profile.mdc +55 -35
- package/dist/cursor/gspec-style.mdc +65 -13
- package/dist/opencode/gspec-analyze/SKILL.md +14 -9
- package/dist/opencode/gspec-audit/SKILL.md +206 -0
- package/dist/opencode/gspec-implement/SKILL.md +11 -8
- package/dist/opencode/gspec-migrate/SKILL.md +29 -15
- package/dist/opencode/gspec-profile/SKILL.md +55 -35
- package/dist/opencode/gspec-style/SKILL.md +65 -13
- package/package.json +1 -1
- 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-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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,
|
|
280
|
-
dest: join(gspecDir,
|
|
281
|
-
label: `gspec/${
|
|
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,
|
|
289
|
-
dest: join(gspecDir, 'features',
|
|
290
|
-
label: `gspec/features/${
|
|
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 (
|
|
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}
|
|
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}
|
|
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}
|
|
938
|
+
// Write to ~/.gspec/{type}/{name}{ext}
|
|
829
939
|
const destDir = join(GSPEC_HOME, selected.type);
|
|
830
|
-
const destPath = join(destDir, `${name}
|
|
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}
|
|
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
|
-
|
|
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
|
|
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
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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',
|
|
1083
|
+
destPath = join(gspecDir, 'features', savedFilename);
|
|
974
1084
|
} else {
|
|
975
|
-
const destFile =
|
|
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
|
-
|
|
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
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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'
|
|
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/
|
|
28
|
-
5. `gspec/
|
|
29
|
-
6. `gspec/
|
|
30
|
-
7. `gspec/
|
|
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
|
|
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`
|
|
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
|
|