specweave 0.32.0 → 0.32.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/CLAUDE.md +176 -2
- package/README.md +22 -0
- package/bin/specweave.js +18 -1
- package/dist/src/cli/commands/cache.d.ts +17 -0
- package/dist/src/cli/commands/cache.d.ts.map +1 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/cache.js.map +1 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/plan/increment-detector.js +2 -2
- package/dist/src/cli/commands/plan/increment-detector.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js.map +1 -1
- package/dist/src/cli/commands/sync-specs.js +2 -2
- package/dist/src/cli/commands/sync-specs.js.map +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js.map +1 -1
- package/dist/src/cli/workers/living-docs-worker.js +66 -1
- package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
- package/dist/src/config/types.d.ts +203 -1208
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.js +5 -2
- package/dist/src/core/discrepancy/increment-generator.js.map +1 -1
- package/dist/src/core/increment/duplicate-detector.js +2 -2
- package/dist/src/core/increment/duplicate-detector.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +24 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +59 -2
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/increment/increment-status.js +2 -2
- package/dist/src/core/increment/increment-status.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +85 -0
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +102 -4
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/metadata-validator.js +1 -1
- package/dist/src/core/increment/metadata-validator.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +3 -3
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +247 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts +15 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +138 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts +24 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js +198 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts +17 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js +241 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts +28 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js +197 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +154 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts +42 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js +343 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +146 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js +7 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js.map +1 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts +5 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +36 -2
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/sync/spec-increment-mapper.js +3 -3
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +25 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +135 -5
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +33 -140
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +30 -27
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +11 -34
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +15 -82
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +38 -93
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +4 -42
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/types/dashboard-cache.d.ts +181 -0
- package/dist/src/types/dashboard-cache.d.ts.map +1 -0
- package/dist/src/types/dashboard-cache.js +65 -0
- package/dist/src/types/dashboard-cache.js.map +1 -0
- package/dist/src/utils/docs-validator.d.ts +131 -0
- package/dist/src/utils/docs-validator.d.ts.map +1 -0
- package/dist/src/utils/docs-validator.js +529 -0
- package/dist/src/utils/docs-validator.js.map +1 -0
- package/dist/src/utils/feature-id-collision.js +1 -1
- package/dist/src/utils/feature-id-collision.js.map +1 -1
- package/dist/src/utils/html-to-mdx.d.ts +1 -0
- package/dist/src/utils/html-to-mdx.d.ts.map +1 -1
- package/dist/src/utils/html-to-mdx.js +43 -5
- package/dist/src/utils/html-to-mdx.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +10 -7
- package/plugins/specweave/commands/specweave-archive-features.md +5 -7
- package/plugins/specweave/commands/specweave-archive.md +2 -1
- package/plugins/specweave/commands/specweave-do.md +35 -1
- package/plugins/specweave/commands/specweave-done.md +96 -0
- package/plugins/specweave/commands/specweave-import-external.md +45 -18
- package/plugins/specweave/commands/specweave-increment.md +331 -33
- package/plugins/specweave/commands/specweave-jobs.md +2 -2
- package/plugins/specweave/commands/specweave-progress.md +4 -4
- package/plugins/specweave/commands/specweave-restore-feature.md +5 -4
- package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
- package/plugins/specweave/commands/specweave-sync-specs.md +216 -322
- package/plugins/specweave/commands/specweave-validate-features.md +13 -8
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/hooks.json +33 -4
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/lib/common-setup.sh +375 -0
- package/plugins/specweave/hooks/lib/crash-prevention.sh +336 -0
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-task-completion.sh +4 -23
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +1 -6
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +8 -37
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh +2 -11
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/universal/dispatcher.mjs +135 -42
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +183 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +140 -38
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +12 -0
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +89 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +211 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +163 -0
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +26 -28
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +50 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js +2 -2
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js.map +1 -1
- package/plugins/specweave/scripts/README.md +166 -0
- package/plugins/specweave/scripts/cleanup-state.sh +142 -0
- package/plugins/specweave/scripts/force-kill.sh +142 -0
- package/plugins/specweave/scripts/jobs.js +171 -0
- package/plugins/specweave/scripts/progress.js +170 -0
- package/plugins/specweave/scripts/read-costs.sh +132 -0
- package/plugins/specweave/scripts/read-jobs.sh +324 -0
- package/plugins/specweave/scripts/read-progress.sh +185 -0
- package/plugins/specweave/scripts/read-status.sh +146 -0
- package/plugins/specweave/scripts/read-workflow.sh +173 -0
- package/plugins/specweave/scripts/rebuild-dashboard-cache.sh +327 -0
- package/plugins/specweave/scripts/session-watchdog.sh +192 -0
- package/plugins/specweave/scripts/status.js +154 -0
- package/plugins/specweave/scripts/update-dashboard-cache.sh +281 -0
- package/plugins/specweave/skills/increment-planner/SKILL.md +333 -24
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +17 -9
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +6 -2
- package/plugins/specweave/skills/instant-status/SKILL.md +70 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-docs/commands/build.md +32 -4
- package/plugins/specweave-docs/commands/preview.md +43 -1
- package/plugins/specweave-docs/commands/validate.md +250 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1262 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1254 -0
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
- package/plugins/specweave/hooks/post-edit-spec.sh +0 -265
- package/plugins/specweave/hooks/post-write-spec.sh +0 -267
- package/plugins/specweave/hooks/pre-edit-spec.sh +0 -151
- package/plugins/specweave/hooks/pre-write-spec.sh +0 -151
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Pre-Render Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates markdown/MDX files BEFORE Docusaurus server starts.
|
|
5
|
+
* Catches issues proactively so users don't see cryptic webpack errors.
|
|
6
|
+
*
|
|
7
|
+
* Validates:
|
|
8
|
+
* 1. YAML frontmatter syntax
|
|
9
|
+
* 2. MDX/JSX compatibility (unquoted attrs, self-closing tags)
|
|
10
|
+
* 3. Broken internal links
|
|
11
|
+
* 4. Duplicate files/routes
|
|
12
|
+
* 5. Invalid file names
|
|
13
|
+
*
|
|
14
|
+
* @module utils/docs-validator
|
|
15
|
+
*/
|
|
16
|
+
import { Logger } from './logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* Issue severity levels
|
|
19
|
+
*/
|
|
20
|
+
export type IssueSeverity = 'error' | 'warning' | 'info';
|
|
21
|
+
/**
|
|
22
|
+
* Single validation issue
|
|
23
|
+
*/
|
|
24
|
+
export interface ValidationIssue {
|
|
25
|
+
file: string;
|
|
26
|
+
line?: number;
|
|
27
|
+
severity: IssueSeverity;
|
|
28
|
+
type: string;
|
|
29
|
+
message: string;
|
|
30
|
+
autoFixable: boolean;
|
|
31
|
+
fix?: () => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validation result summary
|
|
35
|
+
*/
|
|
36
|
+
export interface ValidationResult {
|
|
37
|
+
valid: boolean;
|
|
38
|
+
totalFiles: number;
|
|
39
|
+
errors: number;
|
|
40
|
+
warnings: number;
|
|
41
|
+
infos: number;
|
|
42
|
+
issues: ValidationIssue[];
|
|
43
|
+
fixedCount: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validator options
|
|
47
|
+
*/
|
|
48
|
+
export interface DocsValidatorOptions {
|
|
49
|
+
docsPath: string;
|
|
50
|
+
autoFix?: boolean;
|
|
51
|
+
logger?: Logger;
|
|
52
|
+
/** File extensions to validate */
|
|
53
|
+
extensions?: string[];
|
|
54
|
+
/** Paths to ignore (relative to docsPath) */
|
|
55
|
+
ignorePaths?: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Documentation Pre-Render Validator
|
|
59
|
+
*
|
|
60
|
+
* Use before starting Docusaurus to catch issues early.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const validator = new DocsValidator({
|
|
65
|
+
* docsPath: '.specweave/docs/internal',
|
|
66
|
+
* autoFix: true
|
|
67
|
+
* });
|
|
68
|
+
* const result = await validator.validate();
|
|
69
|
+
* if (!result.valid) {
|
|
70
|
+
* console.log('Fix issues before starting docs server');
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare class DocsValidator {
|
|
75
|
+
private docsPath;
|
|
76
|
+
private autoFix;
|
|
77
|
+
private logger;
|
|
78
|
+
private extensions;
|
|
79
|
+
private ignorePaths;
|
|
80
|
+
private issues;
|
|
81
|
+
private fixedCount;
|
|
82
|
+
constructor(options: DocsValidatorOptions);
|
|
83
|
+
/**
|
|
84
|
+
* Run all validations
|
|
85
|
+
*/
|
|
86
|
+
validate(): Promise<ValidationResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Collect all markdown files recursively
|
|
89
|
+
*/
|
|
90
|
+
private collectFiles;
|
|
91
|
+
/**
|
|
92
|
+
* Validate a single file
|
|
93
|
+
*/
|
|
94
|
+
private validateFile;
|
|
95
|
+
/**
|
|
96
|
+
* Validate YAML frontmatter syntax
|
|
97
|
+
*/
|
|
98
|
+
private validateFrontmatter;
|
|
99
|
+
/**
|
|
100
|
+
* Validate MDX/JSX compatibility
|
|
101
|
+
*/
|
|
102
|
+
private validateMdxContent;
|
|
103
|
+
/**
|
|
104
|
+
* Check for duplicate routes (same normalized path)
|
|
105
|
+
*/
|
|
106
|
+
private checkDuplicateRoutes;
|
|
107
|
+
/**
|
|
108
|
+
* Validate file naming conventions
|
|
109
|
+
*/
|
|
110
|
+
private validateFileName;
|
|
111
|
+
/**
|
|
112
|
+
* Validate internal links between documents
|
|
113
|
+
*/
|
|
114
|
+
private validateInternalLinks;
|
|
115
|
+
/**
|
|
116
|
+
* Build the validation result
|
|
117
|
+
*/
|
|
118
|
+
private buildResult;
|
|
119
|
+
/**
|
|
120
|
+
* Format validation result for display
|
|
121
|
+
*/
|
|
122
|
+
static formatResult(result: ValidationResult): string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Quick validation function for scripts
|
|
126
|
+
*/
|
|
127
|
+
export declare function validateDocs(docsPath: string, options?: {
|
|
128
|
+
autoFix?: boolean;
|
|
129
|
+
logger?: Logger;
|
|
130
|
+
}): Promise<ValidationResult>;
|
|
131
|
+
//# sourceMappingURL=docs-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-validator.d.ts","sourceRoot":"","sources":["../../../src/utils/docs-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAC;AAGpD;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAW;IAC7B,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,UAAU,CAAK;gBAEX,OAAO,EAAE,oBAAoB;IAQzC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAkC3C;;OAEG;IACH,OAAO,CAAC,YAAY;IAuBpB;;OAEG;YACW,YAAY;IAiC1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwE3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA2D1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAuB5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;OAEG;YACW,qBAAqB;IAmGnC;;OAEG;IACH,OAAO,CAAC,WAAW;IA8BnB;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM;CA6GtD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,gBAAgB,CAAC,CAO3B"}
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Pre-Render Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates markdown/MDX files BEFORE Docusaurus server starts.
|
|
5
|
+
* Catches issues proactively so users don't see cryptic webpack errors.
|
|
6
|
+
*
|
|
7
|
+
* Validates:
|
|
8
|
+
* 1. YAML frontmatter syntax
|
|
9
|
+
* 2. MDX/JSX compatibility (unquoted attrs, self-closing tags)
|
|
10
|
+
* 3. Broken internal links
|
|
11
|
+
* 4. Duplicate files/routes
|
|
12
|
+
* 5. Invalid file names
|
|
13
|
+
*
|
|
14
|
+
* @module utils/docs-validator
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import { consoleLogger } from './logger.js';
|
|
19
|
+
import { sanitizeHtmlForMdx, validateMdxCompatibility } from './html-to-mdx.js';
|
|
20
|
+
/**
|
|
21
|
+
* Documentation Pre-Render Validator
|
|
22
|
+
*
|
|
23
|
+
* Use before starting Docusaurus to catch issues early.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const validator = new DocsValidator({
|
|
28
|
+
* docsPath: '.specweave/docs/internal',
|
|
29
|
+
* autoFix: true
|
|
30
|
+
* });
|
|
31
|
+
* const result = await validator.validate();
|
|
32
|
+
* if (!result.valid) {
|
|
33
|
+
* console.log('Fix issues before starting docs server');
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class DocsValidator {
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.issues = [];
|
|
40
|
+
this.fixedCount = 0;
|
|
41
|
+
this.docsPath = path.resolve(options.docsPath);
|
|
42
|
+
this.autoFix = options.autoFix ?? false;
|
|
43
|
+
this.logger = options.logger ?? consoleLogger;
|
|
44
|
+
this.extensions = options.extensions ?? ['.md', '.mdx'];
|
|
45
|
+
this.ignorePaths = options.ignorePaths ?? ['node_modules', '.git', '_archive'];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run all validations
|
|
49
|
+
*/
|
|
50
|
+
async validate() {
|
|
51
|
+
this.issues = [];
|
|
52
|
+
this.fixedCount = 0;
|
|
53
|
+
// Check docs path exists
|
|
54
|
+
if (!fs.existsSync(this.docsPath)) {
|
|
55
|
+
this.issues.push({
|
|
56
|
+
file: this.docsPath,
|
|
57
|
+
severity: 'error',
|
|
58
|
+
type: 'missing_docs_path',
|
|
59
|
+
message: `Documentation path does not exist: ${this.docsPath}`,
|
|
60
|
+
autoFixable: false,
|
|
61
|
+
});
|
|
62
|
+
return this.buildResult();
|
|
63
|
+
}
|
|
64
|
+
// Collect all markdown files
|
|
65
|
+
const files = this.collectFiles(this.docsPath);
|
|
66
|
+
this.logger.info(`Validating ${files.length} documentation files...`);
|
|
67
|
+
// Track seen routes for duplicate detection
|
|
68
|
+
const seenRoutes = new Map();
|
|
69
|
+
// Validate each file
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
await this.validateFile(file, seenRoutes);
|
|
72
|
+
}
|
|
73
|
+
// Check for broken internal links
|
|
74
|
+
await this.validateInternalLinks(files);
|
|
75
|
+
return this.buildResult();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Collect all markdown files recursively
|
|
79
|
+
*/
|
|
80
|
+
collectFiles(dir) {
|
|
81
|
+
const files = [];
|
|
82
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const fullPath = path.join(dir, entry.name);
|
|
85
|
+
const relativePath = path.relative(this.docsPath, fullPath);
|
|
86
|
+
// Skip ignored paths
|
|
87
|
+
if (this.ignorePaths.some((p) => relativePath.startsWith(p) || entry.name === p)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
files.push(...this.collectFiles(fullPath));
|
|
92
|
+
}
|
|
93
|
+
else if (this.extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
94
|
+
files.push(fullPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return files;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Validate a single file
|
|
101
|
+
*/
|
|
102
|
+
async validateFile(filePath, seenRoutes) {
|
|
103
|
+
const relativePath = path.relative(this.docsPath, filePath);
|
|
104
|
+
let content;
|
|
105
|
+
try {
|
|
106
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
this.issues.push({
|
|
110
|
+
file: relativePath,
|
|
111
|
+
severity: 'error',
|
|
112
|
+
type: 'read_error',
|
|
113
|
+
message: `Cannot read file: ${err.message}`,
|
|
114
|
+
autoFixable: false,
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// 1. Validate YAML frontmatter
|
|
119
|
+
this.validateFrontmatter(relativePath, content, filePath);
|
|
120
|
+
// 2. Validate MDX compatibility
|
|
121
|
+
this.validateMdxContent(relativePath, content, filePath);
|
|
122
|
+
// 3. Check for duplicate routes
|
|
123
|
+
this.checkDuplicateRoutes(relativePath, seenRoutes);
|
|
124
|
+
// 4. Validate file naming
|
|
125
|
+
this.validateFileName(relativePath);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate YAML frontmatter syntax
|
|
129
|
+
*/
|
|
130
|
+
validateFrontmatter(relativePath, content, filePath) {
|
|
131
|
+
// Check if file has frontmatter
|
|
132
|
+
if (!content.startsWith('---')) {
|
|
133
|
+
// No frontmatter is OK for some files
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Find closing ---
|
|
137
|
+
const endMatch = content.indexOf('\n---', 3);
|
|
138
|
+
if (endMatch === -1) {
|
|
139
|
+
this.issues.push({
|
|
140
|
+
file: relativePath,
|
|
141
|
+
severity: 'error',
|
|
142
|
+
type: 'invalid_frontmatter',
|
|
143
|
+
message: 'YAML frontmatter not closed (missing closing ---)',
|
|
144
|
+
autoFixable: false,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const frontmatter = content.substring(4, endMatch);
|
|
149
|
+
// Check for common YAML issues
|
|
150
|
+
const lines = frontmatter.split('\n');
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
const line = lines[i];
|
|
153
|
+
const lineNum = i + 2; // +2 for opening --- and 0-index
|
|
154
|
+
// Check for unquoted colons in values (common issue)
|
|
155
|
+
// e.g., title: Feature: Something → should be title: "Feature: Something"
|
|
156
|
+
const colonMatch = line.match(/^(\s*)([a-zA-Z_-]+):\s*([^"'\n]*:[^"'\n]*)$/);
|
|
157
|
+
if (colonMatch && !line.includes('#')) {
|
|
158
|
+
const [, indent, key, value] = colonMatch;
|
|
159
|
+
this.issues.push({
|
|
160
|
+
file: relativePath,
|
|
161
|
+
line: lineNum,
|
|
162
|
+
severity: 'error',
|
|
163
|
+
type: 'yaml_unquoted_colon',
|
|
164
|
+
message: `Unquoted colon in YAML value: ${key}: ${value.trim()} (wrap in quotes)`,
|
|
165
|
+
autoFixable: true,
|
|
166
|
+
fix: () => {
|
|
167
|
+
const fixedLine = `${indent}${key}: "${value.trim()}"`;
|
|
168
|
+
const newContent = content.replace(line, fixedLine);
|
|
169
|
+
fs.writeFileSync(filePath, newContent);
|
|
170
|
+
this.fixedCount++;
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// Check for tabs (YAML doesn't allow tabs for indentation)
|
|
175
|
+
if (line.includes('\t')) {
|
|
176
|
+
this.issues.push({
|
|
177
|
+
file: relativePath,
|
|
178
|
+
line: lineNum,
|
|
179
|
+
severity: 'error',
|
|
180
|
+
type: 'yaml_tabs',
|
|
181
|
+
message: 'YAML frontmatter contains tabs (use spaces)',
|
|
182
|
+
autoFixable: true,
|
|
183
|
+
fix: () => {
|
|
184
|
+
const newContent = content.replace(/\t/g, ' ');
|
|
185
|
+
fs.writeFileSync(filePath, newContent);
|
|
186
|
+
this.fixedCount++;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Validate MDX/JSX compatibility
|
|
194
|
+
*/
|
|
195
|
+
validateMdxContent(relativePath, content, filePath) {
|
|
196
|
+
const mdxIssues = validateMdxCompatibility(content);
|
|
197
|
+
for (const issue of mdxIssues) {
|
|
198
|
+
this.issues.push({
|
|
199
|
+
file: relativePath,
|
|
200
|
+
severity: 'error',
|
|
201
|
+
type: 'mdx_compatibility',
|
|
202
|
+
message: issue,
|
|
203
|
+
autoFixable: true,
|
|
204
|
+
fix: () => {
|
|
205
|
+
const fixedContent = sanitizeHtmlForMdx(content);
|
|
206
|
+
fs.writeFileSync(filePath, fixedContent);
|
|
207
|
+
this.fixedCount++;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// Check for JSX-in-markdown issues
|
|
212
|
+
// Curly braces without escape can break MDX
|
|
213
|
+
const curlyBraceMatch = content.match(/(?<!\\)\{[^}]*\}/g);
|
|
214
|
+
if (curlyBraceMatch) {
|
|
215
|
+
for (const match of curlyBraceMatch) {
|
|
216
|
+
// Skip valid code blocks and inline code
|
|
217
|
+
if (content.includes('```') || content.includes('`' + match + '`')) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// Check if it's a valid JSX expression or needs escaping
|
|
221
|
+
if (!match.match(/^\{[a-zA-Z_$][a-zA-Z0-9_$]*\}$/)) {
|
|
222
|
+
// Not a simple variable reference, might cause issues
|
|
223
|
+
// Only warn, don't error - some valid cases
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Check for common problematic patterns
|
|
228
|
+
const problematicPatterns = [
|
|
229
|
+
{ pattern: /<script/i, message: 'Script tags not allowed in MDX' },
|
|
230
|
+
{ pattern: /<style(?!\s)/i, message: 'Style tags may cause issues (use CSS modules)' },
|
|
231
|
+
{ pattern: /<!--(?!.*-->)/s, message: 'Unclosed HTML comment' },
|
|
232
|
+
];
|
|
233
|
+
for (const { pattern, message } of problematicPatterns) {
|
|
234
|
+
if (pattern.test(content)) {
|
|
235
|
+
this.issues.push({
|
|
236
|
+
file: relativePath,
|
|
237
|
+
severity: 'warning',
|
|
238
|
+
type: 'mdx_problematic_content',
|
|
239
|
+
message,
|
|
240
|
+
autoFixable: false,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check for duplicate routes (same normalized path)
|
|
247
|
+
*/
|
|
248
|
+
checkDuplicateRoutes(relativePath, seenRoutes) {
|
|
249
|
+
// Normalize route: remove extension, lowercase, normalize separators
|
|
250
|
+
const route = relativePath
|
|
251
|
+
.replace(/\.(md|mdx)$/, '')
|
|
252
|
+
.toLowerCase()
|
|
253
|
+
.replace(/\\/g, '/');
|
|
254
|
+
if (seenRoutes.has(route)) {
|
|
255
|
+
this.issues.push({
|
|
256
|
+
file: relativePath,
|
|
257
|
+
severity: 'error',
|
|
258
|
+
type: 'duplicate_route',
|
|
259
|
+
message: `Duplicate route: conflicts with ${seenRoutes.get(route)}`,
|
|
260
|
+
autoFixable: false,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
seenRoutes.set(route, relativePath);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Validate file naming conventions
|
|
269
|
+
*/
|
|
270
|
+
validateFileName(relativePath) {
|
|
271
|
+
const fileName = path.basename(relativePath);
|
|
272
|
+
// Check for spaces in filename
|
|
273
|
+
if (fileName.includes(' ')) {
|
|
274
|
+
this.issues.push({
|
|
275
|
+
file: relativePath,
|
|
276
|
+
severity: 'warning',
|
|
277
|
+
type: 'filename_spaces',
|
|
278
|
+
message: 'Filename contains spaces (may cause URL issues)',
|
|
279
|
+
autoFixable: false,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Check for special characters
|
|
283
|
+
if (/[<>:"|?*]/.test(fileName)) {
|
|
284
|
+
this.issues.push({
|
|
285
|
+
file: relativePath,
|
|
286
|
+
severity: 'error',
|
|
287
|
+
type: 'filename_invalid_chars',
|
|
288
|
+
message: 'Filename contains invalid characters',
|
|
289
|
+
autoFixable: false,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Check for very long filenames
|
|
293
|
+
if (fileName.length > 100) {
|
|
294
|
+
this.issues.push({
|
|
295
|
+
file: relativePath,
|
|
296
|
+
severity: 'warning',
|
|
297
|
+
type: 'filename_too_long',
|
|
298
|
+
message: `Filename too long (${fileName.length} chars, max recommended: 100)`,
|
|
299
|
+
autoFixable: false,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Validate internal links between documents
|
|
305
|
+
*/
|
|
306
|
+
async validateInternalLinks(files) {
|
|
307
|
+
const fileSet = new Set(files.map((f) => path.relative(this.docsPath, f).replace(/\\/g, '/')));
|
|
308
|
+
for (const file of files) {
|
|
309
|
+
const relativePath = path.relative(this.docsPath, file).replace(/\\/g, '/');
|
|
310
|
+
let content;
|
|
311
|
+
try {
|
|
312
|
+
content = fs.readFileSync(file, 'utf-8');
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
// Find markdown links: [text](path)
|
|
318
|
+
const linkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
319
|
+
let match;
|
|
320
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
321
|
+
const [, , linkPath] = match;
|
|
322
|
+
// Skip external links
|
|
323
|
+
if (linkPath.startsWith('http://') || linkPath.startsWith('https://')) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
// Skip anchors
|
|
327
|
+
if (linkPath.startsWith('#')) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
// Skip mailto and tel
|
|
331
|
+
if (linkPath.startsWith('mailto:') || linkPath.startsWith('tel:')) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
// Resolve relative path
|
|
335
|
+
const linkPathClean = linkPath.split('#')[0].split('?')[0];
|
|
336
|
+
if (!linkPathClean)
|
|
337
|
+
continue;
|
|
338
|
+
const fileDir = path.dirname(relativePath);
|
|
339
|
+
let resolvedPath;
|
|
340
|
+
if (linkPathClean.startsWith('/')) {
|
|
341
|
+
resolvedPath = linkPathClean.substring(1);
|
|
342
|
+
}
|
|
343
|
+
else if (linkPathClean.startsWith('./')) {
|
|
344
|
+
resolvedPath = path.join(fileDir, linkPathClean.substring(2));
|
|
345
|
+
}
|
|
346
|
+
else if (linkPathClean.startsWith('../')) {
|
|
347
|
+
resolvedPath = path.join(fileDir, linkPathClean);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
resolvedPath = path.join(fileDir, linkPathClean);
|
|
351
|
+
}
|
|
352
|
+
// Normalize
|
|
353
|
+
resolvedPath = resolvedPath.replace(/\\/g, '/');
|
|
354
|
+
// Check if link points to existing file
|
|
355
|
+
const possiblePaths = [
|
|
356
|
+
resolvedPath,
|
|
357
|
+
resolvedPath + '.md',
|
|
358
|
+
resolvedPath + '.mdx',
|
|
359
|
+
resolvedPath + '/index.md',
|
|
360
|
+
resolvedPath + '/README.md',
|
|
361
|
+
];
|
|
362
|
+
const exists = possiblePaths.some((p) => fileSet.has(p) || fileSet.has(p.replace(/\\/g, '/')));
|
|
363
|
+
// Check if it's an external file (outside docs)
|
|
364
|
+
const fullResolvedPath = path.resolve(this.docsPath, resolvedPath);
|
|
365
|
+
const externalExists = fs.existsSync(fullResolvedPath) ||
|
|
366
|
+
fs.existsSync(fullResolvedPath + '.md');
|
|
367
|
+
if (!exists && !externalExists) {
|
|
368
|
+
// Broken links are warnings, not errors - Docusaurus is configured with
|
|
369
|
+
// onBrokenLinks: 'warn' and won't crash, just show console warnings
|
|
370
|
+
// Check if it might be pointing outside docs intentionally
|
|
371
|
+
const isOutsideDocsRef = linkPathClean.includes('CLAUDE.md') ||
|
|
372
|
+
linkPathClean.includes('README.md') ||
|
|
373
|
+
linkPathClean.includes('increments/') ||
|
|
374
|
+
linkPathClean.includes('_archive/') ||
|
|
375
|
+
linkPathClean.includes('_features/') ||
|
|
376
|
+
linkPathClean.includes('plugins/') ||
|
|
377
|
+
linkPathClean.includes('src/');
|
|
378
|
+
this.issues.push({
|
|
379
|
+
file: relativePath,
|
|
380
|
+
severity: 'warning', // Always warning - Docusaurus won't crash on broken links
|
|
381
|
+
type: 'broken_link',
|
|
382
|
+
message: `Broken link: ${linkPath}${isOutsideDocsRef ? ' (external reference)' : ''}`,
|
|
383
|
+
autoFixable: false,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Build the validation result
|
|
391
|
+
*/
|
|
392
|
+
buildResult() {
|
|
393
|
+
// Apply auto-fixes if enabled
|
|
394
|
+
if (this.autoFix) {
|
|
395
|
+
for (const issue of this.issues) {
|
|
396
|
+
if (issue.autoFixable && issue.fix) {
|
|
397
|
+
try {
|
|
398
|
+
issue.fix();
|
|
399
|
+
this.logger.info(`✓ Fixed: ${issue.file} - ${issue.message}`);
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
this.logger.warn(`Failed to fix ${issue.file}: ${err.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const errors = this.issues.filter((i) => i.severity === 'error').length;
|
|
408
|
+
const warnings = this.issues.filter((i) => i.severity === 'warning').length;
|
|
409
|
+
const infos = this.issues.filter((i) => i.severity === 'info').length;
|
|
410
|
+
return {
|
|
411
|
+
valid: errors === 0,
|
|
412
|
+
totalFiles: this.issues.length > 0 ? this.issues.length : 0,
|
|
413
|
+
errors,
|
|
414
|
+
warnings,
|
|
415
|
+
infos,
|
|
416
|
+
issues: this.issues,
|
|
417
|
+
fixedCount: this.fixedCount,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Format validation result for display
|
|
422
|
+
*/
|
|
423
|
+
static formatResult(result) {
|
|
424
|
+
const lines = [];
|
|
425
|
+
lines.push('');
|
|
426
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
427
|
+
lines.push(' DOCUMENTATION VALIDATION REPORT ');
|
|
428
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
429
|
+
lines.push('');
|
|
430
|
+
if (result.valid) {
|
|
431
|
+
lines.push('✅ Documentation is valid and ready for preview/build');
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
lines.push('❌ Documentation has issues that need to be fixed');
|
|
435
|
+
}
|
|
436
|
+
lines.push('');
|
|
437
|
+
lines.push(` Errors: ${result.errors}`);
|
|
438
|
+
lines.push(` Warnings: ${result.warnings}`);
|
|
439
|
+
lines.push(` Info: ${result.infos}`);
|
|
440
|
+
if (result.fixedCount > 0) {
|
|
441
|
+
lines.push(` Fixed: ${result.fixedCount} (auto-fix applied)`);
|
|
442
|
+
}
|
|
443
|
+
lines.push('');
|
|
444
|
+
// Group issues by file
|
|
445
|
+
const issuesByFile = new Map();
|
|
446
|
+
for (const issue of result.issues) {
|
|
447
|
+
const existing = issuesByFile.get(issue.file) ?? [];
|
|
448
|
+
existing.push(issue);
|
|
449
|
+
issuesByFile.set(issue.file, existing);
|
|
450
|
+
}
|
|
451
|
+
// Show errors first
|
|
452
|
+
const errorFiles = Array.from(issuesByFile.entries())
|
|
453
|
+
.filter(([, issues]) => issues.some((i) => i.severity === 'error'));
|
|
454
|
+
if (errorFiles.length > 0) {
|
|
455
|
+
lines.push('ERRORS (must fix):');
|
|
456
|
+
lines.push('───────────────────────────────────────────────────────────────');
|
|
457
|
+
for (const [file, issues] of errorFiles) {
|
|
458
|
+
const errors = issues.filter((i) => i.severity === 'error');
|
|
459
|
+
lines.push(` 📄 ${file}`);
|
|
460
|
+
for (const issue of errors) {
|
|
461
|
+
const line = issue.line ? `:${issue.line}` : '';
|
|
462
|
+
const fix = issue.autoFixable ? ' [auto-fixable]' : '';
|
|
463
|
+
lines.push(` ❌ ${issue.type}${line}: ${issue.message}${fix}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
lines.push('');
|
|
467
|
+
}
|
|
468
|
+
// Show warnings
|
|
469
|
+
const warningFiles = Array.from(issuesByFile.entries())
|
|
470
|
+
.filter(([, issues]) => issues.some((i) => i.severity === 'warning'));
|
|
471
|
+
if (warningFiles.length > 0 && warningFiles.length <= 10) {
|
|
472
|
+
lines.push('WARNINGS (recommended to fix):');
|
|
473
|
+
lines.push('───────────────────────────────────────────────────────────────');
|
|
474
|
+
for (const [file, issues] of warningFiles) {
|
|
475
|
+
const warnings = issues.filter((i) => i.severity === 'warning');
|
|
476
|
+
lines.push(` 📄 ${file}`);
|
|
477
|
+
for (const issue of warnings) {
|
|
478
|
+
lines.push(` ⚠️ ${issue.message}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
lines.push('');
|
|
482
|
+
}
|
|
483
|
+
else if (warningFiles.length > 10) {
|
|
484
|
+
lines.push(`WARNINGS: ${result.warnings} (showing summary only)`);
|
|
485
|
+
lines.push('───────────────────────────────────────────────────────────────');
|
|
486
|
+
lines.push(` Run with --verbose to see all warnings`);
|
|
487
|
+
lines.push('');
|
|
488
|
+
}
|
|
489
|
+
// Suggestions
|
|
490
|
+
if (!result.valid) {
|
|
491
|
+
lines.push('QUICK FIXES:');
|
|
492
|
+
lines.push('───────────────────────────────────────────────────────────────');
|
|
493
|
+
lines.push(' 1. Run validation with auto-fix:');
|
|
494
|
+
lines.push(' /specweave-docs:validate --fix');
|
|
495
|
+
lines.push('');
|
|
496
|
+
lines.push(' 2. Or fix manually:');
|
|
497
|
+
const hasYamlIssues = result.issues.some((i) => i.type.startsWith('yaml_'));
|
|
498
|
+
const hasMdxIssues = result.issues.some((i) => i.type.startsWith('mdx_'));
|
|
499
|
+
const hasBrokenLinks = result.issues.some((i) => i.type === 'broken_link');
|
|
500
|
+
if (hasYamlIssues) {
|
|
501
|
+
lines.push(' • YAML: Wrap values with colons in quotes');
|
|
502
|
+
lines.push(' title: "Feature: Auth" (not title: Feature: Auth)');
|
|
503
|
+
}
|
|
504
|
+
if (hasMdxIssues) {
|
|
505
|
+
lines.push(' • MDX: Quote HTML attributes, close self-closing tags');
|
|
506
|
+
lines.push(' target="_blank" (not target=_blank)');
|
|
507
|
+
lines.push(' <br /> (not <br>)');
|
|
508
|
+
}
|
|
509
|
+
if (hasBrokenLinks) {
|
|
510
|
+
lines.push(' • Links: Update or remove broken internal links');
|
|
511
|
+
}
|
|
512
|
+
lines.push('');
|
|
513
|
+
}
|
|
514
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
515
|
+
return lines.join('\n');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Quick validation function for scripts
|
|
520
|
+
*/
|
|
521
|
+
export async function validateDocs(docsPath, options) {
|
|
522
|
+
const validator = new DocsValidator({
|
|
523
|
+
docsPath,
|
|
524
|
+
autoFix: options?.autoFix,
|
|
525
|
+
logger: options?.logger,
|
|
526
|
+
});
|
|
527
|
+
return validator.validate();
|
|
528
|
+
}
|
|
529
|
+
//# sourceMappingURL=docs-validator.js.map
|