@openuji/speculator-lint 0.1.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 (43) hide show
  1. package/.speculatorlintrc.example.json +9 -0
  2. package/README.md +186 -0
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +125 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config.d.ts +35 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +96 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +14 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/linter.d.ts +34 -0
  16. package/dist/linter.d.ts.map +1 -0
  17. package/dist/linter.js +78 -0
  18. package/dist/linter.js.map +1 -0
  19. package/dist/rule-runner.d.ts +12 -0
  20. package/dist/rule-runner.d.ts.map +1 -0
  21. package/dist/rule-runner.js +129 -0
  22. package/dist/rule-runner.js.map +1 -0
  23. package/dist/rules/index.d.ts +15 -0
  24. package/dist/rules/index.d.ts.map +1 -0
  25. package/dist/rules/index.js +21 -0
  26. package/dist/rules/index.js.map +1 -0
  27. package/dist/rules/workspace/no-redefinition.d.ts +13 -0
  28. package/dist/rules/workspace/no-redefinition.d.ts.map +1 -0
  29. package/dist/rules/workspace/no-redefinition.js +61 -0
  30. package/dist/rules/workspace/no-redefinition.js.map +1 -0
  31. package/dist/rules/workspace/no-reverse-dependency.d.ts +13 -0
  32. package/dist/rules/workspace/no-reverse-dependency.d.ts.map +1 -0
  33. package/dist/rules/workspace/no-reverse-dependency.js +46 -0
  34. package/dist/rules/workspace/no-reverse-dependency.js.map +1 -0
  35. package/dist/types.d.ts +146 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +7 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/utils.d.ts +9 -0
  40. package/dist/utils.d.ts.map +1 -0
  41. package/dist/utils.js +14 -0
  42. package/dist/utils.js.map +1 -0
  43. package/package.json +46 -0
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": [
3
+ "recommended"
4
+ ],
5
+ "rules": {
6
+ "workspace/no-redefinition": "error",
7
+ "workspace/no-reverse-dependency": "error"
8
+ }
9
+ }
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # @openuji/speculator-lint
2
+
3
+ Standalone linter for [Speculator](../speculator) workspace AST with configurable validation rules.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add -D @openuji/speculator-lint
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### CLI
14
+
15
+ Lint a workspace AST file:
16
+
17
+ ```bash
18
+ speculator-lint workspace.json
19
+ ```
20
+
21
+ With custom configuration:
22
+
23
+ ```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
+ });
51
+
52
+ // Check results
53
+ if (result.hasErrors) {
54
+ for (const diagnostic of result.diagnostics) {
55
+ console.error(diagnostic.message);
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ Create a `.speculatorlintrc.json` file in your project root:
63
+
64
+ ```json
65
+ {
66
+ "extends": ["recommended"],
67
+ "rules": {
68
+ "workspace/no-redefinition": "error",
69
+ "workspace/no-reverse-dependency": "warning"
70
+ }
71
+ }
72
+ ```
73
+
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
185
+
186
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for speculator-lint
4
+ *
5
+ * Usage:
6
+ * speculator-lint <workspace.json> [--config <path>]
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;GAKG"}
package/dist/cli.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for speculator-lint
4
+ *
5
+ * Usage:
6
+ * speculator-lint <workspace.json> [--config <path>]
7
+ */
8
+ import { readFileSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { SpeculatorLinter } from './linter.js';
11
+ import { builtInRules } from './rules/index.js';
12
+ import { loadConfig, loadConfigFromDefaults, recommendedConfig } from './config.js';
13
+ function parseArgs() {
14
+ const args = process.argv.slice(2);
15
+ const result = {
16
+ workspacePath: '',
17
+ };
18
+ for (let i = 0; i < args.length; i++) {
19
+ const arg = args[i];
20
+ if (arg === '--help' || arg === '-h') {
21
+ result.help = true;
22
+ }
23
+ else if (arg === '--config' || arg === '-c') {
24
+ result.configPath = args[++i];
25
+ }
26
+ else if (!arg.startsWith('-')) {
27
+ result.workspacePath = arg;
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+ function showHelp() {
33
+ console.log(`
34
+ speculator-lint - Lint Speculator workspace AST
35
+
36
+ Usage:
37
+ speculator-lint <workspace.json> [options]
38
+
39
+ Options:
40
+ --config, -c <path> Path to configuration file
41
+ --help, -h Show this help message
42
+
43
+ Examples:
44
+ speculator-lint workspace.json
45
+ speculator-lint workspace.json --config .speculatorlintrc.json
46
+ `);
47
+ }
48
+ function formatDiagnostic(diagnostic) {
49
+ const severity = diagnostic.severity.toUpperCase();
50
+ const code = diagnostic.code;
51
+ const file = diagnostic.file || '<unknown>';
52
+ const line = diagnostic.sourcePos?.line || '?';
53
+ const col = diagnostic.sourcePos?.column || '?';
54
+ return `${severity} [${code}] ${file}:${line}:${col}\n ${diagnostic.message}`;
55
+ }
56
+ async function main() {
57
+ const args = parseArgs();
58
+ if (args.help || !args.workspacePath) {
59
+ showHelp();
60
+ process.exit(args.help ? 0 : 1);
61
+ }
62
+ try {
63
+ // Load workspace
64
+ const workspacePath = resolve(args.workspacePath);
65
+ const workspaceJson = readFileSync(workspacePath, 'utf-8');
66
+ const workspace = JSON.parse(workspaceJson);
67
+ // Build document levels map
68
+ const documentLevels = new Map();
69
+ workspace.documents.forEach((doc, index) => {
70
+ const path = doc.sourcePos?.file || '';
71
+ if (path) {
72
+ documentLevels.set(path, index);
73
+ }
74
+ });
75
+ // Load configuration
76
+ let config;
77
+ if (args.configPath) {
78
+ config = loadConfig(args.configPath);
79
+ console.log(`Using configuration from: ${args.configPath}`);
80
+ }
81
+ else {
82
+ const defaultConfigLoaded = loadConfigFromDefaults();
83
+ if (defaultConfigLoaded) {
84
+ config = defaultConfigLoaded;
85
+ console.log('Using configuration from default location');
86
+ }
87
+ else {
88
+ config = recommendedConfig;
89
+ console.log('Using recommended configuration (no config file found)');
90
+ }
91
+ }
92
+ // Create linter
93
+ const linter = new SpeculatorLinter(builtInRules);
94
+ // Run linter
95
+ console.log('\nLinting workspace...\n');
96
+ const result = await linter.lint({
97
+ workspace,
98
+ documentLevels,
99
+ config
100
+ });
101
+ // Output diagnostics
102
+ if (result.diagnostics.length === 0) {
103
+ console.log('✓ No issues found');
104
+ }
105
+ else {
106
+ for (const diagnostic of result.diagnostics) {
107
+ console.log(formatDiagnostic(diagnostic));
108
+ console.log('');
109
+ }
110
+ const errorCount = result.diagnostics.filter(d => d.severity === 'error').length;
111
+ const warningCount = result.diagnostics.filter(d => d.severity === 'warning').length;
112
+ console.log(`Found ${errorCount} error(s), ${warningCount} warning(s)`);
113
+ }
114
+ // Show timing
115
+ console.log(`\nCompleted in ${result.totalTime.toFixed(2)}ms`);
116
+ // Exit with error code if errors found
117
+ process.exit(result.hasErrors ? 1 : 0);
118
+ }
119
+ catch (error) {
120
+ console.error('Error:', error instanceof Error ? error.message : String(error));
121
+ process.exit(1);
122
+ }
123
+ }
124
+ main();
125
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Configuration loader and parser for speculator-lint
3
+ */
4
+ import type { LintConfig, RuleConfigValue } from './types.js';
5
+ /**
6
+ * Default configuration
7
+ */
8
+ export declare const defaultConfig: LintConfig;
9
+ /**
10
+ * Recommended configuration with all built-in rules enabled
11
+ */
12
+ export declare const recommendedConfig: LintConfig;
13
+ /**
14
+ * Load configuration from a file
15
+ * @param configPath Path to config file (relative or absolute)
16
+ * @returns Parsed configuration
17
+ */
18
+ export declare function loadConfig(configPath: string): LintConfig;
19
+ /**
20
+ * Load configuration from default locations
21
+ * @param cwd Current working directory
22
+ * @returns Configuration or null if not found
23
+ */
24
+ export declare function loadConfigFromDefaults(cwd?: string): LintConfig | null;
25
+ /**
26
+ * Get the effective severity for a rule
27
+ * @param ruleConfig Rule configuration value
28
+ * @returns Severity or null if disabled
29
+ */
30
+ export declare function getRuleSeverity(ruleConfig: RuleConfigValue | undefined): 'error' | 'warning' | 'info' | null;
31
+ /**
32
+ * Check if a rule is enabled
33
+ */
34
+ export declare function isRuleEnabled(config: LintConfig, ruleName: string): boolean;
35
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAE3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,UAK/B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAWzD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,MAAsB,GAAG,UAAU,GAAG,IAAI,CAerF;AA0BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAU5G;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG3E"}
package/dist/config.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Configuration loader and parser for speculator-lint
3
+ */
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { resolve } from 'path';
6
+ /**
7
+ * Default configuration
8
+ */
9
+ export const defaultConfig = {
10
+ rules: {}
11
+ };
12
+ /**
13
+ * Recommended configuration with all built-in rules enabled
14
+ */
15
+ export const recommendedConfig = {
16
+ rules: {
17
+ 'workspace/no-redefinition': 'error',
18
+ 'workspace/no-reverse-dependency': 'error'
19
+ }
20
+ };
21
+ /**
22
+ * Load configuration from a file
23
+ * @param configPath Path to config file (relative or absolute)
24
+ * @returns Parsed configuration
25
+ */
26
+ export function loadConfig(configPath) {
27
+ const resolvedPath = resolve(configPath);
28
+ if (!existsSync(resolvedPath)) {
29
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
30
+ }
31
+ const content = readFileSync(resolvedPath, 'utf-8');
32
+ const config = JSON.parse(content);
33
+ return normalizeConfig(config);
34
+ }
35
+ /**
36
+ * Load configuration from default locations
37
+ * @param cwd Current working directory
38
+ * @returns Configuration or null if not found
39
+ */
40
+ export function loadConfigFromDefaults(cwd = process.cwd()) {
41
+ const candidates = [
42
+ '.speculatorlintrc.json',
43
+ '.speculatorlintrc',
44
+ 'speculator-lint.config.json'
45
+ ];
46
+ for (const filename of candidates) {
47
+ const path = resolve(cwd, filename);
48
+ if (existsSync(path)) {
49
+ return loadConfig(path);
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Normalize configuration to ensure consistency
56
+ */
57
+ function normalizeConfig(config) {
58
+ const normalized = {
59
+ ...config,
60
+ rules: { ...config.rules }
61
+ };
62
+ // Handle extends
63
+ if (config.extends) {
64
+ for (const extend of config.extends) {
65
+ if (extend === 'recommended') {
66
+ normalized.rules = {
67
+ ...recommendedConfig.rules,
68
+ ...normalized.rules
69
+ };
70
+ }
71
+ }
72
+ }
73
+ return normalized;
74
+ }
75
+ /**
76
+ * Get the effective severity for a rule
77
+ * @param ruleConfig Rule configuration value
78
+ * @returns Severity or null if disabled
79
+ */
80
+ export function getRuleSeverity(ruleConfig) {
81
+ if (!ruleConfig || ruleConfig === 'off') {
82
+ return null;
83
+ }
84
+ if (Array.isArray(ruleConfig)) {
85
+ return ruleConfig[0];
86
+ }
87
+ return ruleConfig;
88
+ }
89
+ /**
90
+ * Check if a rule is enabled
91
+ */
92
+ export function isRuleEnabled(config, ruleName) {
93
+ const ruleConfig = config.rules?.[ruleName];
94
+ return getRuleSeverity(ruleConfig) !== null;
95
+ }
96
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAG/B;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAe;IACrC,KAAK,EAAE,EAAE;CACZ,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAe;IACzC,KAAK,EAAE;QACH,2BAA2B,EAAE,OAAO;QACpC,iCAAiC,EAAE,OAAO;KAC7C;CACJ,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB;IACzC,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,iCAAiC,YAAY,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;IAEjD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC9D,MAAM,UAAU,GAAG;QACf,wBAAwB;QACxB,mBAAmB;QACnB,6BAA6B;KAChC,CAAC;IAEF,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACpC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,MAAkB;IACvC,MAAM,UAAU,GAAe;QAC3B,GAAG,MAAM;QACT,KAAK,EAAE,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE;KAC7B,CAAC;IAEF,iBAAiB;IACjB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;gBAC3B,UAAU,CAAC,KAAK,GAAG;oBACf,GAAG,iBAAiB,CAAC,KAAK;oBAC1B,GAAG,UAAU,CAAC,KAAK;iBACtB,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,UAAU,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,UAAuC;IACnE,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,UAAU,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAkB,EAAE,QAAgB;IAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,eAAe,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;AAChD,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @openuji/speculator-lint
3
+ *
4
+ * Standalone linter for Speculator workspace AST
5
+ */
6
+ export { SpeculatorLinter } from './linter.js';
7
+ export type { LintRule, LintContext, LintVisitor, LintDiagnostic, LintResult, LintOptions, LintConfig, RuleMetadata, RuleResult, Severity, RuleCategory } from './types.js';
8
+ export { loadConfig, loadConfigFromDefaults, defaultConfig, recommendedConfig } from './config.js';
9
+ export { builtInRules, getRuleByName, noRedefinitionRule, noReverseDependencyRule } from './rules/index.js';
10
+ export { normalizeTerm } from './utils.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,YAAY,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,UAAU,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,YAAY,EACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EACH,UAAU,EACV,sBAAsB,EACtB,aAAa,EACb,iBAAiB,EACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACH,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EAC1B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @openuji/speculator-lint
3
+ *
4
+ * Standalone linter for Speculator workspace AST
5
+ */
6
+ // Main linter class
7
+ export { SpeculatorLinter } from './linter.js';
8
+ // Configuration utilities
9
+ export { loadConfig, loadConfigFromDefaults, defaultConfig, recommendedConfig } from './config.js';
10
+ // Built-in rules
11
+ export { builtInRules, getRuleByName, noRedefinitionRule, noReverseDependencyRule } from './rules/index.js';
12
+ // Utilities for custom rules
13
+ export { normalizeTerm } from './utils.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAoB;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB/C,0BAA0B;AAC1B,OAAO,EACH,UAAU,EACV,sBAAsB,EACtB,aAAa,EACb,iBAAiB,EACpB,MAAM,aAAa,CAAC;AAErB,iBAAiB;AACjB,OAAO,EACH,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EAC1B,MAAM,kBAAkB,CAAC;AAE1B,6BAA6B;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SpeculatorLinter - Main linter class
3
+ */
4
+ import type { LintRule, LintOptions, LintResult } from './types.js';
5
+ /**
6
+ * Main linter class
7
+ */
8
+ export declare class SpeculatorLinter {
9
+ private rules;
10
+ /**
11
+ * Create a new linter instance
12
+ * @param rules Array of lint rules to use
13
+ */
14
+ constructor(rules: LintRule[]);
15
+ /**
16
+ * Lint a workspace
17
+ * @param options Lint options
18
+ * @returns Lint result with diagnostics
19
+ */
20
+ lint(options: LintOptions): Promise<LintResult>;
21
+ /**
22
+ * Check if a rule is enabled in the configuration
23
+ */
24
+ private isRuleEnabled;
25
+ /**
26
+ * Get the configured severity for a rule
27
+ */
28
+ private getConfiguredSeverity;
29
+ /**
30
+ * Get available rules
31
+ */
32
+ getRules(): LintRule[];
33
+ }
34
+ //# sourceMappingURL=linter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,QAAQ,EACR,WAAW,EACX,UAAU,EAGb,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,qBAAa,gBAAgB;IACzB,OAAO,CAAC,KAAK,CAAoC;IAEjD;;;OAGG;gBACS,KAAK,EAAE,QAAQ,EAAE;IAM7B;;;;OAIG;IACG,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAwCrD;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;OAEG;IACH,QAAQ,IAAI,QAAQ,EAAE;CAGzB"}
package/dist/linter.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * SpeculatorLinter - Main linter class
3
+ */
4
+ import { runRule } from './rule-runner.js';
5
+ import { getRuleSeverity } from './config.js';
6
+ /**
7
+ * Main linter class
8
+ */
9
+ export class SpeculatorLinter {
10
+ rules = new Map();
11
+ /**
12
+ * Create a new linter instance
13
+ * @param rules Array of lint rules to use
14
+ */
15
+ constructor(rules) {
16
+ for (const rule of rules) {
17
+ this.rules.set(rule.meta.name, rule);
18
+ }
19
+ }
20
+ /**
21
+ * Lint a workspace
22
+ * @param options Lint options
23
+ * @returns Lint result with diagnostics
24
+ */
25
+ async lint(options) {
26
+ const startTime = performance.now();
27
+ const config = options.config || { rules: {} };
28
+ const ruleResults = new Map();
29
+ const allDiagnostics = [];
30
+ // Run each enabled rule
31
+ for (const [ruleName, rule] of this.rules) {
32
+ // Check if rule is enabled in config
33
+ if (!this.isRuleEnabled(config, ruleName)) {
34
+ continue;
35
+ }
36
+ // Get effective severity from config
37
+ const configuredSeverity = this.getConfiguredSeverity(config, ruleName);
38
+ // Run the rule
39
+ const result = await runRule(rule, options.workspace, options.documentLevels);
40
+ // Override severity if configured
41
+ if (configuredSeverity && configuredSeverity !== rule.meta.severity) {
42
+ for (const diagnostic of result.diagnostics) {
43
+ diagnostic.severity = configuredSeverity;
44
+ }
45
+ }
46
+ ruleResults.set(ruleName, result);
47
+ allDiagnostics.push(...result.diagnostics);
48
+ }
49
+ const endTime = performance.now();
50
+ return {
51
+ diagnostics: allDiagnostics,
52
+ hasErrors: allDiagnostics.some(d => d.severity === 'error'),
53
+ ruleResults,
54
+ totalTime: endTime - startTime
55
+ };
56
+ }
57
+ /**
58
+ * Check if a rule is enabled in the configuration
59
+ */
60
+ isRuleEnabled(config, ruleName) {
61
+ const ruleConfig = config.rules?.[ruleName];
62
+ const severity = getRuleSeverity(ruleConfig);
63
+ return severity !== null;
64
+ }
65
+ /**
66
+ * Get the configured severity for a rule
67
+ */
68
+ getConfiguredSeverity(config, ruleName) {
69
+ return getRuleSeverity(config.rules?.[ruleName]);
70
+ }
71
+ /**
72
+ * Get available rules
73
+ */
74
+ getRules() {
75
+ return Array.from(this.rules.values());
76
+ }
77
+ }
78
+ //# sourceMappingURL=linter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.js","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,OAAO,gBAAgB;IACjB,KAAK,GAA0B,IAAI,GAAG,EAAE,CAAC;IAEjD;;;OAGG;IACH,YAAY,KAAiB;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,OAAoB;QAC3B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC/C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,cAAc,GAAqB,EAAE,CAAC;QAE5C,wBAAwB;QACxB,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACxC,qCAAqC;YACrC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACxC,SAAS;YACb,CAAC;YAED,qCAAqC;YACrC,MAAM,kBAAkB,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAExE,eAAe;YACf,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;YAE9E,kCAAkC;YAClC,IAAI,kBAAkB,IAAI,kBAAkB,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClE,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;oBAC1C,UAAU,CAAC,QAAQ,GAAG,kBAAkB,CAAC;gBAC7C,CAAC;YACL,CAAC;YAED,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAClC,cAAc,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAElC,OAAO;YACH,WAAW,EAAE,cAAc;YAC3B,SAAS,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;YAC3D,WAAW;YACX,SAAS,EAAE,OAAO,GAAG,SAAS;SACjC,CAAC;IACN,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,MAAkB,EAAE,QAAgB;QACtD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;QAC7C,OAAO,QAAQ,KAAK,IAAI,CAAC;IAC7B,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,MAAkB,EAAE,QAAgB;QAC9D,OAAO,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACH,QAAQ;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3C,CAAC;CACJ"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Rule execution engine
3
+ *
4
+ * Handles visiting AST nodes and invoking rule visitors
5
+ */
6
+ import type { Workspace } from '@openuji/speculator';
7
+ import type { LintRule, RuleResult } from './types.js';
8
+ /**
9
+ * Run a single rule against a workspace
10
+ */
11
+ export declare function runRule(rule: LintRule, workspace: Workspace, documentLevels: Map<string, number>): Promise<RuleResult>;
12
+ //# sourceMappingURL=rule-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-runner.d.ts","sourceRoot":"","sources":["../src/rule-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACR,SAAS,EAIZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACR,QAAQ,EAGR,UAAU,EACb,MAAM,YAAY,CAAC;AAGpB;;GAEG;AACH,wBAAsB,OAAO,CACzB,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,OAAO,CAAC,UAAU,CAAC,CA8DrB"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Rule execution engine
3
+ *
4
+ * Handles visiting AST nodes and invoking rule visitors
5
+ */
6
+ import { normalizeTerm } from './utils.js';
7
+ /**
8
+ * Run a single rule against a workspace
9
+ */
10
+ export async function runRule(rule, workspace, documentLevels) {
11
+ const startTime = performance.now();
12
+ const diagnostics = [];
13
+ // Build global definition index for quick lookups
14
+ const globalDefIndex = buildDefinitionIndex(workspace);
15
+ // Process each document
16
+ for (const document of workspace.documents) {
17
+ const level = documentLevels.get(document.sourcePos?.file || '') ?? 0;
18
+ // Create context for this document
19
+ const context = {
20
+ workspace,
21
+ documentLevels,
22
+ document,
23
+ level,
24
+ report: (diagnostic) => {
25
+ diagnostics.push({
26
+ code: rule.meta.code,
27
+ severity: rule.meta.severity,
28
+ ...diagnostic
29
+ });
30
+ }
31
+ };
32
+ // Create visitor
33
+ const visitor = rule.create(context);
34
+ // Visit document
35
+ if (visitor.onDocument) {
36
+ visitor.onDocument(document);
37
+ }
38
+ // Visit definitions
39
+ if (visitor.onDefinition) {
40
+ const docDefs = document.indexes?.definitions || [];
41
+ for (const entry of docDefs) {
42
+ // Get all entries for this term across workspace
43
+ const allEntries = globalDefIndex.get(normalizeTerm(entry.term)) || [];
44
+ visitor.onDefinition(entry, allEntries);
45
+ }
46
+ }
47
+ // Visit references
48
+ if (visitor.onReference) {
49
+ const references = collectReferences(document);
50
+ for (const ref of references) {
51
+ // Try to resolve the reference
52
+ const target = resolveReference(ref, globalDefIndex);
53
+ visitor.onReference(ref, target);
54
+ }
55
+ }
56
+ }
57
+ const endTime = performance.now();
58
+ return {
59
+ ruleName: rule.meta.name,
60
+ diagnostics,
61
+ executionTime: endTime - startTime
62
+ };
63
+ }
64
+ /**
65
+ * Build a global definition index from workspace
66
+ */
67
+ function buildDefinitionIndex(workspace) {
68
+ const index = new Map();
69
+ for (const doc of workspace.documents) {
70
+ const definitions = doc.indexes?.definitions || [];
71
+ for (const entry of definitions) {
72
+ const linkTexts = entry.linkTexts || [entry.term];
73
+ const termsToProcess = new Set([entry.term, ...linkTexts]);
74
+ for (const term of termsToProcess) {
75
+ const key = normalizeTerm(term);
76
+ const existing = index.get(key) || [];
77
+ existing.push(entry);
78
+ index.set(key, existing);
79
+ }
80
+ }
81
+ }
82
+ return index;
83
+ }
84
+ /**
85
+ * Collect all references from a document
86
+ */
87
+ function collectReferences(document) {
88
+ const references = [];
89
+ // Walk through nodes recursively
90
+ function walkNode(node) {
91
+ if (!node || typeof node !== 'object')
92
+ return;
93
+ // Check if this is a reference node
94
+ if ('type' in node && node.type === 'reference') {
95
+ references.push(node);
96
+ }
97
+ // Walk children array (for inline elements, blocks, sections)
98
+ if ('children' in node && Array.isArray(node.children)) {
99
+ for (const child of node.children) {
100
+ walkNode(child);
101
+ }
102
+ }
103
+ }
104
+ // Document has children array containing Section and Block nodes
105
+ if (document.children) {
106
+ for (const child of document.children) {
107
+ walkNode(child);
108
+ }
109
+ }
110
+ return references;
111
+ }
112
+ /**
113
+ * Resolve a reference to its target definition
114
+ */
115
+ function resolveReference(ref, index) {
116
+ const candidateTerms = 'candidateTerms' in ref && Array.isArray(ref.candidateTerms)
117
+ ? ref.candidateTerms
118
+ : [ref.targetTerm];
119
+ for (const term of candidateTerms) {
120
+ const key = normalizeTerm(term);
121
+ const entries = index.get(key);
122
+ if (entries && entries.length > 0) {
123
+ // Return first match (simplified resolution)
124
+ return entries[0];
125
+ }
126
+ }
127
+ return null;
128
+ }
129
+ //# sourceMappingURL=rule-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-runner.js","sourceRoot":"","sources":["../src/rule-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Built-in lint rules
3
+ */
4
+ export { noRedefinitionRule } from './workspace/no-redefinition.js';
5
+ export { noReverseDependencyRule } from './workspace/no-reverse-dependency.js';
6
+ import type { LintRule } from '../types.js';
7
+ /**
8
+ * All built-in rules
9
+ */
10
+ export declare const builtInRules: LintRule[];
11
+ /**
12
+ * Get a rule by name
13
+ */
14
+ export declare function getRuleByName(name: string): LintRule | undefined;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAI/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,EAGlC,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAEhE"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Built-in lint rules
3
+ */
4
+ export { noRedefinitionRule } from './workspace/no-redefinition.js';
5
+ export { noReverseDependencyRule } from './workspace/no-reverse-dependency.js';
6
+ import { noRedefinitionRule } from './workspace/no-redefinition.js';
7
+ import { noReverseDependencyRule } from './workspace/no-reverse-dependency.js';
8
+ /**
9
+ * All built-in rules
10
+ */
11
+ export const builtInRules = [
12
+ noRedefinitionRule,
13
+ noReverseDependencyRule
14
+ ];
15
+ /**
16
+ * Get a rule by name
17
+ */
18
+ export function getRuleByName(name) {
19
+ return builtInRules.find(r => r.meta.name === name);
20
+ }
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAE/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAG/E;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAe;IACpC,kBAAkB;IAClB,uBAAuB;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * workspace/no-redefinition rule
3
+ *
4
+ * Ensures that lower-level specs do not redefine concepts from higher-level specs.
5
+ *
6
+ * Rule Logic:
7
+ * - When a term is defined in multiple documents, compare their levels
8
+ * - If a lower-level document (higher number) redefines a term from a higher-level document,
9
+ * report an error
10
+ */
11
+ import type { LintRule } from '../../types.js';
12
+ export declare const noRedefinitionRule: LintRule;
13
+ //# sourceMappingURL=no-redefinition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-redefinition.d.ts","sourceRoot":"","sources":["../../../src/rules/workspace/no-redefinition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,gBAAgB,CAAC;AAE5D,eAAO,MAAM,kBAAkB,EAAE,QAuDhC,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * workspace/no-redefinition rule
3
+ *
4
+ * Ensures that lower-level specs do not redefine concepts from higher-level specs.
5
+ *
6
+ * Rule Logic:
7
+ * - When a term is defined in multiple documents, compare their levels
8
+ * - If a lower-level document (higher number) redefines a term from a higher-level document,
9
+ * report an error
10
+ */
11
+ export const noRedefinitionRule = {
12
+ meta: {
13
+ name: 'workspace/no-redefinition',
14
+ code: 'no-redefinition',
15
+ severity: 'error',
16
+ description: 'Lower-level specs MUST NOT redefine concepts from higher-level specs',
17
+ category: 'workspace'
18
+ },
19
+ create(context) {
20
+ return {
21
+ onDefinition(entry, allEntriesForTerm) {
22
+ // If this is the first definition of this term, no issue
23
+ if (allEntriesForTerm.length === 0) {
24
+ return;
25
+ }
26
+ // Find the highest-level (lowest number) definition
27
+ let highestEntry = allEntriesForTerm[0];
28
+ let highestLevel = Infinity;
29
+ for (const existingEntry of allEntriesForTerm) {
30
+ const docPath = existingEntry.sourcePos?.file;
31
+ if (!docPath)
32
+ continue;
33
+ const level = context.documentLevels.get(docPath) ?? 0;
34
+ if (level < highestLevel) {
35
+ highestLevel = level;
36
+ highestEntry = existingEntry;
37
+ }
38
+ }
39
+ // Check if current entry is from a lower level
40
+ const currentDocPath = entry.sourcePos?.file;
41
+ if (!currentDocPath)
42
+ return;
43
+ const currentLevel = context.documentLevels.get(currentDocPath) ?? 0;
44
+ const higherDocPath = highestEntry.sourcePos?.file;
45
+ // If current level is lower (higher number) than the highest definition
46
+ if (currentLevel > highestLevel && higherDocPath) {
47
+ // Skip if this is the same document (shouldn't happen, but just in case)
48
+ if (currentDocPath === higherDocPath) {
49
+ return;
50
+ }
51
+ context.report({
52
+ message: `Lower-level spec "${currentDocPath}" redefines concept "${entry.term}" already defined in higher-level spec "${higherDocPath}"`,
53
+ file: currentDocPath,
54
+ sourcePos: entry.sourcePos
55
+ });
56
+ }
57
+ }
58
+ };
59
+ }
60
+ };
61
+ //# sourceMappingURL=no-redefinition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-redefinition.js","sourceRoot":"","sources":["../../../src/rules/workspace/no-redefinition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,CAAC,MAAM,kBAAkB,GAAa;IACxC,IAAI,EAAE;QACF,IAAI,EAAE,2BAA2B;QACjC,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,sEAAsE;QACnF,QAAQ,EAAE,WAAW;KACxB;IAED,MAAM,CAAC,OAAoB;QACvB,OAAO;YACH,YAAY,CAAC,KAAK,EAAE,iBAAiB;gBACjC,yDAAyD;gBACzD,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACjC,OAAO;gBACX,CAAC;gBAED,oDAAoD;gBACpD,IAAI,YAAY,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBACxC,IAAI,YAAY,GAAG,QAAQ,CAAC;gBAE5B,KAAK,MAAM,aAAa,IAAI,iBAAiB,EAAE,CAAC;oBAC5C,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC;oBAC9C,IAAI,CAAC,OAAO;wBAAE,SAAS;oBAEvB,MAAM,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;oBACvD,IAAI,KAAK,GAAG,YAAY,EAAE,CAAC;wBACvB,YAAY,GAAG,KAAK,CAAC;wBACrB,YAAY,GAAG,aAAa,CAAC;oBACjC,CAAC;gBACL,CAAC;gBAED,+CAA+C;gBAC/C,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC;gBAC7C,IAAI,CAAC,cAAc;oBAAE,OAAO;gBAE5B,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;gBACrE,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC;gBAEnD,wEAAwE;gBACxE,IAAI,YAAY,GAAG,YAAY,IAAI,aAAa,EAAE,CAAC;oBAC/C,yEAAyE;oBACzE,IAAI,cAAc,KAAK,aAAa,EAAE,CAAC;wBACnC,OAAO;oBACX,CAAC;oBAED,OAAO,CAAC,MAAM,CAAC;wBACX,OAAO,EAAE,qBAAqB,cAAc,wBAAwB,KAAK,CAAC,IAAI,2CAA2C,aAAa,GAAG;wBACzI,IAAI,EAAE,cAAc;wBACpB,SAAS,EAAE,KAAK,CAAC,SAAS;qBAC7B,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * workspace/no-reverse-dependency rule
3
+ *
4
+ * Ensures that higher-level specs do not depend on (reference) lower-level specs.
5
+ *
6
+ * Rule Logic:
7
+ * - When a reference is resolved to a definition, compare document levels
8
+ * - If the source document has a lower level (higher priority, lower number)
9
+ * than the target document, report an error
10
+ */
11
+ import type { LintRule } from '../../types.js';
12
+ export declare const noReverseDependencyRule: LintRule;
13
+ //# sourceMappingURL=no-reverse-dependency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-reverse-dependency.d.ts","sourceRoot":"","sources":["../../../src/rules/workspace/no-reverse-dependency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,gBAAgB,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAE,QAuCrC,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * workspace/no-reverse-dependency rule
3
+ *
4
+ * Ensures that higher-level specs do not depend on (reference) lower-level specs.
5
+ *
6
+ * Rule Logic:
7
+ * - When a reference is resolved to a definition, compare document levels
8
+ * - If the source document has a lower level (higher priority, lower number)
9
+ * than the target document, report an error
10
+ */
11
+ export const noReverseDependencyRule = {
12
+ meta: {
13
+ name: 'workspace/no-reverse-dependency',
14
+ code: 'no-reverse-dependency',
15
+ severity: 'error',
16
+ description: 'Higher-level specs MUST NOT depend on lower-level specs',
17
+ category: 'workspace'
18
+ },
19
+ create(context) {
20
+ return {
21
+ onReference(ref, target) {
22
+ // If reference is not resolved, nothing to check
23
+ if (!target) {
24
+ return;
25
+ }
26
+ const sourceDocPath = context.document.sourcePos?.file;
27
+ const targetDocPath = target.sourcePos?.file;
28
+ if (!sourceDocPath || !targetDocPath) {
29
+ return;
30
+ }
31
+ // Get levels
32
+ const sourceLevel = context.level;
33
+ const targetLevel = context.documentLevels.get(targetDocPath) ?? 0;
34
+ // If source is higher level (lower number) than target, that's a violation
35
+ if (sourceLevel < targetLevel) {
36
+ context.report({
37
+ message: `Higher-level spec "${sourceDocPath}" (level ${sourceLevel}) depends on lower-level spec "${targetDocPath}" (level ${targetLevel}) via term "${target.term}"`,
38
+ file: sourceDocPath,
39
+ sourcePos: ref.sourcePos
40
+ });
41
+ }
42
+ }
43
+ };
44
+ }
45
+ };
46
+ //# sourceMappingURL=no-reverse-dependency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-reverse-dependency.js","sourceRoot":"","sources":["../../../src/rules/workspace/no-reverse-dependency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,CAAC,MAAM,uBAAuB,GAAa;IAC7C,IAAI,EAAE;QACF,IAAI,EAAE,iCAAiC;QACvC,IAAI,EAAE,uBAAuB;QAC7B,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,yDAAyD;QACtE,QAAQ,EAAE,WAAW;KACxB;IAED,MAAM,CAAC,OAAoB;QACvB,OAAO;YACH,WAAW,CAAC,GAAG,EAAE,MAAM;gBACnB,iDAAiD;gBACjD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACV,OAAO;gBACX,CAAC;gBAED,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;gBACvD,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC;gBAE7C,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnC,OAAO;gBACX,CAAC;gBAED,aAAa;gBACb,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;gBAClC,MAAM,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAEnE,2EAA2E;gBAC3E,IAAI,WAAW,GAAG,WAAW,EAAE,CAAC;oBAC5B,OAAO,CAAC,MAAM,CAAC;wBACX,OAAO,EAAE,sBAAsB,aAAa,YAAY,WAAW,kCAAkC,aAAa,YAAY,WAAW,eAAe,MAAM,CAAC,IAAI,GAAG;wBACtK,IAAI,EAAE,aAAa;wBACnB,SAAS,EAAE,GAAG,CAAC,SAAS;qBAC3B,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Core types for @openuji/speculator-lint
3
+ *
4
+ * Defines the rule-based linting architecture for Speculator workspace AST.
5
+ */
6
+ import type { Workspace, Document, IndexDefinitionEntry, InlineReference, SourcePos } from '@openuji/speculator';
7
+ /**
8
+ * Diagnostic severity levels
9
+ */
10
+ export type Severity = 'error' | 'warning' | 'info';
11
+ /**
12
+ * Rule category for organization
13
+ */
14
+ export type RuleCategory = 'workspace' | 'document' | 'reference' | 'custom';
15
+ /**
16
+ * Diagnostic produced by a lint rule
17
+ */
18
+ export interface LintDiagnostic {
19
+ /** Rule code (e.g., 'no-redefinition') */
20
+ code: string;
21
+ /** Severity level */
22
+ severity: Severity;
23
+ /** Diagnostic message */
24
+ message: string;
25
+ /** File where the issue was found */
26
+ file?: string;
27
+ /** Source position in the file */
28
+ sourcePos?: SourcePos;
29
+ }
30
+ /**
31
+ * Context provided to lint rules
32
+ */
33
+ export interface LintContext {
34
+ /** Current workspace being linted */
35
+ readonly workspace: Workspace;
36
+ /** Map of document path -> level (0 is highest) */
37
+ readonly documentLevels: Map<string, number>;
38
+ /** Current document being processed */
39
+ readonly document: Document;
40
+ /** Current document's level */
41
+ readonly level: number;
42
+ /**
43
+ * Report a diagnostic
44
+ */
45
+ report(diagnostic: Omit<LintDiagnostic, 'code' | 'severity'>): void;
46
+ }
47
+ /**
48
+ * Visitor pattern for AST traversal
49
+ * Rules implement the hooks they're interested in
50
+ */
51
+ export interface LintVisitor {
52
+ /**
53
+ * Called for each definition in the document
54
+ * @param entry The definition entry
55
+ * @param allEntriesForTerm All entries for this normalized term across the workspace
56
+ */
57
+ onDefinition?(entry: IndexDefinitionEntry, allEntriesForTerm: IndexDefinitionEntry[]): void;
58
+ /**
59
+ * Called for each reference in the document
60
+ * @param ref The reference node
61
+ * @param target The resolved target definition (null if unresolved)
62
+ */
63
+ onReference?(ref: InlineReference, target: IndexDefinitionEntry | null): void;
64
+ /**
65
+ * Called once per document before visiting nodes
66
+ * @param doc The document
67
+ */
68
+ onDocument?(doc: Document): void;
69
+ }
70
+ /**
71
+ * Rule metadata
72
+ */
73
+ export interface RuleMetadata {
74
+ /** Unique rule name (e.g., 'no-redefinition') */
75
+ name: string;
76
+ /** Diagnostic code used in reports */
77
+ code: string;
78
+ /** Default severity */
79
+ severity: Severity;
80
+ /** Human-readable description */
81
+ description: string;
82
+ /** Rule category */
83
+ category: RuleCategory;
84
+ }
85
+ /**
86
+ * Lint rule interface
87
+ */
88
+ export interface LintRule {
89
+ /** Rule metadata */
90
+ meta: RuleMetadata;
91
+ /**
92
+ * Create a visitor for this rule
93
+ * @param context Lint context
94
+ * @returns Visitor implementation
95
+ */
96
+ create(context: LintContext): LintVisitor;
97
+ }
98
+ /**
99
+ * Configuration for a single rule
100
+ */
101
+ export type RuleConfigValue = 'off' | 'error' | 'warning' | 'info' | [Severity, Record<string, unknown>?];
102
+ /**
103
+ * Linter configuration
104
+ */
105
+ export interface LintConfig {
106
+ /** Rule configurations by rule name */
107
+ rules?: Record<string, RuleConfigValue>;
108
+ /** Configurations to extend */
109
+ extends?: string[];
110
+ }
111
+ /**
112
+ * Result from a single rule execution
113
+ */
114
+ export interface RuleResult {
115
+ /** Rule name */
116
+ ruleName: string;
117
+ /** Diagnostics produced by this rule */
118
+ diagnostics: LintDiagnostic[];
119
+ /** Execution time in milliseconds */
120
+ executionTime: number;
121
+ }
122
+ /**
123
+ * Overall lint result
124
+ */
125
+ export interface LintResult {
126
+ /** All diagnostics from all rules */
127
+ diagnostics: LintDiagnostic[];
128
+ /** Quick check for errors */
129
+ hasErrors: boolean;
130
+ /** Results per rule */
131
+ ruleResults: Map<string, RuleResult>;
132
+ /** Total execution time */
133
+ totalTime: number;
134
+ }
135
+ /**
136
+ * Options for running the linter
137
+ */
138
+ export interface LintOptions {
139
+ /** Workspace to lint */
140
+ workspace: Workspace;
141
+ /** Document level mapping */
142
+ documentLevels: Map<string, number>;
143
+ /** Optional configuration override */
144
+ config?: LintConfig;
145
+ }
146
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Core types for @openuji/speculator-lint
3
+ *
4
+ * Defines the rule-based linting architecture for Speculator workspace AST.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Utility functions for speculator-lint
3
+ */
4
+ /**
5
+ * Normalize a term for consistent lookup
6
+ * Mirrors the logic from @openuji/speculator
7
+ */
8
+ export declare function normalizeTerm(term: string): string;
9
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlD"}
package/dist/utils.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Utility functions for speculator-lint
3
+ */
4
+ /**
5
+ * Normalize a term for consistent lookup
6
+ * Mirrors the logic from @openuji/speculator
7
+ */
8
+ export function normalizeTerm(term) {
9
+ return term
10
+ .toLowerCase()
11
+ .trim()
12
+ .replace(/\s+/g, ' ');
13
+ }
14
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,OAAO,IAAI;SACN,WAAW,EAAE;SACb,IAAI,EAAE;SACN,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@openuji/speculator-lint",
3
+ "version": "0.1.0",
4
+ "description": "Standalone linter for Speculator workspace AST with configurable validation rules",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "speculator-lint": "./dist/cli.js"
10
+ },
11
+ "imports": {
12
+ "#src/*": "./dist/*.js"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^20.10.0",
16
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
17
+ "@typescript-eslint/parser": "^7.0.0",
18
+ "eslint": "^8.57.0",
19
+ "typescript": "^5.3.0",
20
+ "vitest": "^1.0.0"
21
+ },
22
+ "dependencies": {
23
+ "@openuji/speculator": "0.5.1"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ ".speculatorlintrc.example.json"
28
+ ],
29
+ "keywords": [
30
+ "linter",
31
+ "ast",
32
+ "specification",
33
+ "validation",
34
+ "speculator"
35
+ ],
36
+ "license": "MIT",
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "dev": "tsc --watch",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "lint": "eslint src --ext .ts",
43
+ "lint:fix": "eslint src --ext .ts --fix",
44
+ "typecheck": "tsc --noEmit"
45
+ }
46
+ }