@openuji/speculator-lint 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +13 -168
  2. package/bin/speculator-lint.js +2 -0
  3. package/dist/__tests__/config.test.d.ts +2 -0
  4. package/dist/__tests__/config.test.d.ts.map +1 -0
  5. package/dist/__tests__/config.test.js +55 -0
  6. package/dist/__tests__/config.test.js.map +1 -0
  7. package/dist/__tests__/rules.test.d.ts +2 -0
  8. package/dist/__tests__/rules.test.d.ts.map +1 -0
  9. package/dist/__tests__/rules.test.js +410 -0
  10. package/dist/__tests__/rules.test.js.map +1 -0
  11. package/dist/cli.js +66 -32
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +4 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +8 -4
  16. package/dist/config.js.map +1 -1
  17. package/dist/linter.js +1 -1
  18. package/dist/linter.js.map +1 -1
  19. package/dist/rule-runner.d.ts +1 -1
  20. package/dist/rule-runner.d.ts.map +1 -1
  21. package/dist/rule-runner.js +1 -91
  22. package/dist/rule-runner.js.map +1 -1
  23. package/dist/rules/document/no-duplicate-definition.d.ts +9 -0
  24. package/dist/rules/document/no-duplicate-definition.d.ts.map +1 -0
  25. package/dist/rules/document/no-duplicate-definition.js +59 -0
  26. package/dist/rules/document/no-duplicate-definition.js.map +1 -0
  27. package/dist/rules/index.d.ts +4 -0
  28. package/dist/rules/index.d.ts.map +1 -1
  29. package/dist/rules/index.js +13 -1
  30. package/dist/rules/index.js.map +1 -1
  31. package/dist/rules/reference/no-ambiguous-reference.d.ts +3 -0
  32. package/dist/rules/reference/no-ambiguous-reference.d.ts.map +1 -0
  33. package/dist/rules/reference/no-ambiguous-reference.js +59 -0
  34. package/dist/rules/reference/no-ambiguous-reference.js.map +1 -0
  35. package/dist/rules/reference/no-id-reference.d.ts +3 -0
  36. package/dist/rules/reference/no-id-reference.d.ts.map +1 -0
  37. package/dist/rules/reference/no-id-reference.js +64 -0
  38. package/dist/rules/reference/no-id-reference.js.map +1 -0
  39. package/dist/rules/reference/no-unresolved-reference.d.ts +10 -0
  40. package/dist/rules/reference/no-unresolved-reference.d.ts.map +1 -0
  41. package/dist/rules/reference/no-unresolved-reference.js +73 -0
  42. package/dist/rules/reference/no-unresolved-reference.js.map +1 -0
  43. package/dist/rules/speculator-helpers.d.ts +22 -0
  44. package/dist/rules/speculator-helpers.d.ts.map +1 -0
  45. package/dist/rules/speculator-helpers.js +108 -0
  46. package/dist/rules/speculator-helpers.js.map +1 -0
  47. package/dist/rules/workspace/no-redefinition.d.ts.map +1 -1
  48. package/dist/rules/workspace/no-redefinition.js +32 -31
  49. package/dist/rules/workspace/no-redefinition.js.map +1 -1
  50. package/dist/rules/workspace/no-reverse-dependency.d.ts.map +1 -1
  51. package/dist/rules/workspace/no-reverse-dependency.js +30 -19
  52. package/dist/rules/workspace/no-reverse-dependency.js.map +1 -1
  53. package/dist/speculator-utils.d.ts +20 -0
  54. package/dist/speculator-utils.d.ts.map +1 -0
  55. package/dist/speculator-utils.js +82 -0
  56. package/dist/speculator-utils.js.map +1 -0
  57. package/dist/types.d.ts +66 -102
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js +0 -5
  60. package/dist/types.js.map +1 -1
  61. package/dist/utils.d.ts.map +1 -1
  62. package/dist/utils.js +2 -1
  63. package/dist/utils.js.map +1 -1
  64. package/package.json +11 -4
package/README.md CHANGED
@@ -1,186 +1,31 @@
1
1
  # @openuji/speculator-lint
2
2
 
3
- Standalone linter for [Speculator](../speculator) workspace AST with configurable validation rules.
3
+ **Developer-grade linting for technical specifications.**
4
4
 
5
- ## Installation
5
+ `speculator-lint` helps ensure conceptual consistency and semantic integrity in your technical specifications. It catches errors like redefinitions across spec levels, reverse dependencies, and ambiguous references.
6
6
 
7
- ```bash
8
- pnpm add -D @openuji/speculator-lint
9
- ```
10
-
11
- ## Usage
7
+ ## 📖 Documentation
12
8
 
13
- ### CLI
14
-
15
- Lint a workspace AST file:
16
-
17
- ```bash
18
- speculator-lint workspace.json
19
- ```
9
+ For full documentation on available rules, configuration presets, and extensibility, please visit the [Speculator Documentation](https://speculator.pages.dev/qa/linter).
20
10
 
21
- With custom configuration:
11
+ ## 🚀 Quick Start
22
12
 
23
13
  ```bash
24
- speculator-lint workspace.json --config .speculatorlintrc.json
25
- ```
26
-
27
- ### Programmatic API
28
-
29
- ```typescript
30
- import { readFileSync } from 'fs';
31
- import { SpeculatorLinter, builtInRules, recommendedConfig } from '@openuji/speculator-lint';
32
-
33
- // Load workspace AST
34
- const workspace = JSON.parse(readFileSync('workspace.json', 'utf-8'));
35
-
36
- // Build document levels map
37
- const documentLevels = new Map();
38
- workspace.documents.forEach((doc, index) => {
39
- documentLevels.set(doc.sourcePos?.file || '', index);
40
- });
41
-
42
- // Create linter with built-in rules
43
- const linter = new SpeculatorLinter(builtInRules);
44
-
45
- // Run linter
46
- const result = await linter.lint({
47
- workspace,
48
- documentLevels,
49
- config: recommendedConfig
50
- });
14
+ # Install
15
+ pnpm add -D @openuji/speculator-lint
51
16
 
52
- // Check results
53
- if (result.hasErrors) {
54
- for (const diagnostic of result.diagnostics) {
55
- console.error(diagnostic.message);
56
- }
57
- }
17
+ # Lint your workspace
18
+ speculator-lint workspace.json
58
19
  ```
59
20
 
60
- ## Configuration
61
-
62
- Create a `.speculatorlintrc.json` file in your project root:
21
+ ### Basic Config (`.speculatorlintrc.json`)
63
22
 
64
23
  ```json
65
24
  {
66
- "extends": ["recommended"],
67
- "rules": {
68
- "workspace/no-redefinition": "error",
69
- "workspace/no-reverse-dependency": "warning"
70
- }
25
+ "extends": ["recommended"]
71
26
  }
72
27
  ```
73
28
 
74
- ### Rule Configuration
75
-
76
- Rules can be configured with:
77
- - `"off"` - Disable the rule
78
- - `"error"` - Report as error
79
- - `"warning"` - Report as warning
80
- - `"info"` - Report as info
81
-
82
- ### Extends
83
-
84
- Use `"extends": ["recommended"]` to enable all built-in rules with their default severities.
85
-
86
- ## Built-in Rules
87
-
88
- ### workspace/no-redefinition
89
-
90
- Ensures that lower-level specs do not redefine concepts from higher-level specs.
91
-
92
- **Rationale:** In a hierarchical specification system, higher-level specs define the core vocabulary. Lower-level specs should extend, not override these definitions.
93
-
94
- **Example violation:**
95
-
96
- ```
97
- core.md (level 0): defines "User"
98
- extension.md (level 1): defines "User" again ← ERROR
99
- ```
100
-
101
- ### workspace/no-reverse-dependency
102
-
103
- Ensures that higher-level specs do not depend on (reference) lower-level specs.
104
-
105
- **Rationale:** Dependencies should flow downward in the hierarchy. Higher-level specs should be self-contained and not rely on lower-level implementation details.
106
-
107
- **Example violation:**
108
-
109
- ```
110
- core.md (level 0): references "DetailedConfig"
111
- extension.md (level 1): defines "DetailedConfig" ← ERROR
112
- ```
113
-
114
- ## Creating Custom Rules
115
-
116
- You can create custom rules by implementing the `LintRule` interface:
117
-
118
- ```typescript
119
- import type { LintRule } from '@openuji/speculator-lint';
120
-
121
- export const myCustomRule: LintRule = {
122
- meta: {
123
- name: 'my-custom-rule',
124
- code: 'my-custom-rule',
125
- severity: 'warning',
126
- description: 'My custom validation rule',
127
- category: 'custom'
128
- },
129
-
130
- create(context) {
131
- return {
132
- // Called for each definition
133
- onDefinition(entry, allEntriesForTerm) {
134
- // Your validation logic
135
- if (/* some condition */) {
136
- context.report({
137
- message: 'Issue found',
138
- file: entry.sourcePos?.file,
139
- sourcePos: entry.sourcePos
140
- });
141
- }
142
- },
143
-
144
- // Called for each reference
145
- onReference(ref, target) {
146
- // Your validation logic
147
- },
148
-
149
- // Called once per document
150
- onDocument(doc) {
151
- // Your validation logic
152
- }
153
- };
154
- }
155
- };
156
-
157
- // Use with linter
158
- const linter = new SpeculatorLinter([...builtInRules, myCustomRule]);
159
- ```
160
-
161
- ## API Reference
162
-
163
- ### SpeculatorLinter
164
-
165
- Main linter class.
166
-
167
- ```typescript
168
- class SpeculatorLinter {
169
- constructor(rules: LintRule[]);
170
- lint(options: LintOptions): Promise<LintResult>;
171
- getRules(): LintRule[];
172
- }
173
- ```
174
-
175
- ### Types
176
-
177
- - `LintRule` - Rule interface
178
- - `LintContext` - Context provided to rules
179
- - `LintVisitor` - Visitor pattern for AST traversal
180
- - `LintDiagnostic` - Diagnostic output
181
- - `LintResult` - Lint result with diagnostics
182
- - `LintConfig` - Configuration schema
183
-
184
- ## License
29
+ ---
185
30
 
186
- MIT
31
+ Part of the [Speculator](https://github.com/openuji/speculator) ecosystem.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/cli.js';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { normalizeConfig, recommendedConfig } from '../config.js';
3
+ describe('Configuration System', () => {
4
+ it('should return the same config if no extends are present', () => {
5
+ const config = {
6
+ rules: {
7
+ 'document/no-duplicate-definition': 'warning'
8
+ }
9
+ };
10
+ const normalized = normalizeConfig(config);
11
+ expect(normalized.rules).toEqual(config.rules);
12
+ });
13
+ it('should merge with recommended config when extends includes "recommended"', () => {
14
+ const config = {
15
+ extends: ['recommended'],
16
+ rules: {}
17
+ };
18
+ const normalized = normalizeConfig(config);
19
+ // Should have all recommended rules
20
+ expect(normalized.rules).toEqual(recommendedConfig.rules);
21
+ });
22
+ it('should allow overriding recommended rules', () => {
23
+ const config = {
24
+ extends: ['recommended'],
25
+ rules: {
26
+ 'reference/no-id-reference': 'error'
27
+ }
28
+ };
29
+ const normalized = normalizeConfig(config);
30
+ // Should have the override
31
+ expect(normalized.rules['reference/no-id-reference']).toBe('error');
32
+ // Should still have other recommended rules
33
+ expect(normalized.rules['workspace/no-redefinition']).toBe('error');
34
+ expect(normalized.rules['reference/no-ambiguous-reference']).toBe('warning');
35
+ });
36
+ it('should allow disabling recommended rules', () => {
37
+ const config = {
38
+ extends: ['recommended'],
39
+ rules: {
40
+ 'workspace/no-redefinition': 'off'
41
+ }
42
+ };
43
+ const normalized = normalizeConfig(config);
44
+ expect(normalized.rules['workspace/no-redefinition']).toBe('off');
45
+ });
46
+ it('should handle multiple extends if added in future (currently only "recommended")', () => {
47
+ const config = {
48
+ extends: ['recommended', 'unknown-preset'],
49
+ rules: {}
50
+ };
51
+ const normalized = normalizeConfig(config);
52
+ expect(normalized.rules).toEqual(recommendedConfig.rules);
53
+ });
54
+ });
55
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGlE,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAe;YACvB,KAAK,EAAE;gBACH,kCAAkC,EAAE,SAAS;aAChD;SACJ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAChF,MAAM,MAAM,GAAe;YACvB,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,KAAK,EAAE,EAAE;SACZ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAE3C,oCAAoC;QACpC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAe;YACvB,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,KAAK,EAAE;gBACH,2BAA2B,EAAE,OAAO;aACvC;SACJ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAE3C,2BAA2B;QAC3B,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEpE,4CAA4C;QAC5C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAe;YACvB,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,KAAK,EAAE;gBACH,2BAA2B,EAAE,KAAK;aACrC;SACJ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAE3C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QACvF,MAAM,MAAM,GAAe;YACxB,OAAO,EAAE,CAAC,aAAa,EAAE,gBAAgB,CAAC;YAC1C,KAAK,EAAE,EAAE;SACZ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rules.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/rules.test.ts"],"names":[],"mappings":""}