@boyingliu01/xp-gate 0.19.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/gate-4.sh +83 -18
- package/adapters/gate-8.sh +49 -5
- package/adapters/iac.sh +1 -1
- package/adapters/typescript.sh +15 -60
- package/bin/xp-gate.js +15 -16
- package/gate-10.sh +35 -2
- package/gate-4.sh +110 -21
- package/gate-8.sh +49 -5
- package/hooks/gate-10.sh +35 -2
- package/hooks/gate-4.sh +110 -21
- package/hooks/gate-8.sh +49 -5
- package/hooks/lib/jscpd-run.sh +63 -0
- package/hooks/lib/sprint-gate-report.sh +58 -0
- package/hooks/lib/test-failure.sh +152 -0
- package/hooks/lib/typecheck.sh +141 -0
- package/hooks/lib/validate-code-walkthrough.cjs +43 -5
- package/hooks/post-merge +100 -0
- package/hooks/pre-commit +389 -162
- package/hooks/pre-push +75 -19
- package/lib/__tests__/bootstrap.test.js +23 -41
- package/lib/__tests__/check-version.test.js +8 -1
- package/lib/__tests__/detect-deps.test.js +16 -0
- package/lib/__tests__/doctor-drift-round2.test.js +212 -0
- package/lib/__tests__/doctor-executed-surface.test.js +209 -0
- package/lib/__tests__/doctor-hook-drift.test.js +329 -0
- package/lib/__tests__/doctor-machine-mutation.test.js +116 -0
- package/lib/__tests__/doctor-module-drift.test.js +206 -0
- package/lib/__tests__/doctor-module-resolution-parity.test.js +314 -0
- package/lib/__tests__/doctor-path-equivalence.test.js +59 -0
- package/lib/__tests__/doctor-repair-boundary.test.js +52 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +105 -0
- package/lib/__tests__/doctor.test.js +155 -0
- package/lib/__tests__/gate-runner.test.js +19 -3
- package/lib/__tests__/helpers/lib-sandbox.cjs +55 -0
- package/lib/__tests__/init.test.js +44 -17
- package/lib/__tests__/install-skill-full-dir.test.js +151 -0
- package/lib/__tests__/install-skill.test.js +51 -132
- package/lib/__tests__/language-tools-detect.test.js +158 -0
- package/lib/__tests__/phase-transition.test.js +15 -1
- package/lib/__tests__/qoder-delphi-agents.test.js +285 -0
- package/lib/__tests__/shared-paths.test.js +20 -0
- package/lib/__tests__/shared-utils.test.js +69 -0
- package/lib/__tests__/skill-flags.test.js +48 -0
- package/lib/__tests__/ui-detector.test.ts +6 -2
- package/lib/__tests__/uninstall.test.js +20 -0
- package/lib/__tests__/update-hooks.test.js +12 -0
- package/lib/__tests__/update-skill.test.js +7 -4
- package/lib/detect-deps.js +3 -20
- package/lib/doctor-tui.js +22 -5
- package/lib/doctor.js +1178 -31
- package/lib/init.js +184 -33
- package/lib/install-cmd.js +7 -4
- package/lib/install-skill.js +38 -95
- package/lib/language-tools.js +83 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-paths.js +5 -2
- package/lib/shared-utils.js +47 -3
- package/lib/uninstall.js +12 -0
- package/lib/update-hooks.js +4 -4
- package/lib/update-skill.js +6 -7
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/mutation/__tests__/gate-m.test.ts +66 -14
- package/mutation/__tests__/mutmut-runner.test.ts +91 -42
- package/mutation/__tests__/test-filter-parity.test.ts +37 -0
- package/mutation/gate-m.ts +21 -8
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/delphi-review/SKILL.md +98 -14
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/README.md +169 -4
- package/plugins/dsh/dsh-overlay.patch.yml +67 -0
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/prepack.cjs +24 -19
- package/plugins/dsh/setup-dsh.ps1 +160 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/delphi-review/SKILL.md +98 -14
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/dsh/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/src/command.test.ts +66 -2
- package/plugins/dsh/src/command.ts +36 -14
- package/plugins/dsh/src/contract.test.ts +31 -1
- package/plugins/dsh/src/delphi-config.test.ts +190 -0
- package/plugins/dsh/src/delphi-config.ts +223 -0
- package/plugins/dsh/src/delphi-run.test.ts +181 -0
- package/plugins/dsh/src/delphi-run.ts +129 -0
- package/plugins/dsh/src/delphi.test.ts +222 -0
- package/plugins/dsh/src/delphi.ts +247 -0
- package/plugins/dsh/src/dialect.test.ts +59 -0
- package/plugins/dsh/src/dialect.ts +40 -0
- package/plugins/dsh/src/index.ts +70 -4
- package/plugins/dsh/src/skills.test.ts +26 -19
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/delphi-review/SKILL.md +98 -14
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/opencode/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/agents/delphi-architecture.md +1 -1
- package/plugins/qoder/agents/delphi-feasibility.md +1 -1
- package/plugins/qoder/agents/delphi-technical.md +1 -1
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/delphi-review/SKILL.md +98 -14
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/qoder/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +9 -8
- package/principles/__tests__/adapter-metrics.test.ts +292 -0
- package/principles/__tests__/analyzer.test.ts +29 -6
- package/principles/__tests__/baseline-prune-timeout.test.ts +71 -0
- package/principles/__tests__/baseline-prune.test.ts +338 -0
- package/principles/__tests__/boy-scout-init-baseline.test.ts +167 -0
- package/principles/__tests__/boy-scout-integration.test.ts +97 -6
- package/principles/__tests__/boy-scout.test.ts +253 -7
- package/principles/__tests__/config-enforcement.test.ts +469 -0
- package/principles/__tests__/config-git-timeout.test.ts +66 -0
- package/principles/__tests__/direct-execution.test.ts +187 -0
- package/principles/__tests__/index.test.ts +16 -3
- package/principles/__tests__/warnings-baseline-budgets.test.ts +74 -0
- package/principles/adapters/typescript.ts +613 -13
- package/principles/analyzer.ts +22 -0
- package/principles/baseline-prune.ts +217 -0
- package/principles/boy-scout.ts +481 -51
- package/principles/config.ts +362 -54
- package/principles/direct-execution.ts +60 -0
- package/principles/index.ts +40 -17
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +16 -10
- package/principles/rules/clean-code/code-duplication.ts +8 -8
- package/principles/rules/clean-code/deep-nesting.ts +8 -8
- package/principles/rules/clean-code/god-class.ts +8 -8
- package/principles/rules/clean-code/large-file.ts +15 -11
- package/principles/rules/clean-code/long-function.ts +8 -8
- package/principles/rules/clean-code/magic-numbers.ts +10 -7
- package/principles/rules/clean-code/many-exports.ts +7 -7
- package/principles/rules/clean-code/missing-error-handling.ts +5 -5
- package/principles/rules/clean-code/too-many-params.ts +8 -8
- package/principles/rules/clean-code/unused-imports.ts +5 -5
- package/principles/rules/solid/dip.ts +18 -8
- package/principles/rules/solid/isp.ts +9 -8
- package/principles/rules/solid/lsp.ts +6 -6
- package/principles/rules/solid/ocp.ts +6 -5
- package/principles/rules/solid/srp.ts +7 -8
- package/principles/types.ts +15 -0
- package/scripts/__tests__/sync-package-content.test.js +28 -0
- package/scripts/delphi-external-review.cjs +193 -10
- package/scripts/sync-package-content.js +7 -4
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/delphi-review/SKILL.md +98 -14
- package/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/skills/delphi-review/references/requirements.md +6 -6
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +4 -4
- package/lib/__tests__/download-skill.test.js +0 -281
- package/lib/download-skill.js +0 -96
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const deepNestingRule: Rule = {
|
|
7
5
|
id: 'clean-code.deep-nesting',
|
|
8
6
|
name: 'Deep Nesting Rule',
|
|
9
|
-
threshold:
|
|
10
|
-
severity:
|
|
7
|
+
threshold: 4,
|
|
8
|
+
severity: 'warning',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['deep-nesting'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
interface TypedAdapter {
|
|
@@ -19,15 +19,15 @@ export const deepNestingRule: Rule = {
|
|
|
19
19
|
const functions = typedAdapter.extractFunctions?.() || [];
|
|
20
20
|
|
|
21
21
|
for (const func of functions) {
|
|
22
|
-
if (func.nestingDepth && func.nestingDepth > (
|
|
22
|
+
if (func.nestingDepth && func.nestingDepth > (settings.threshold ?? 0)) {
|
|
23
23
|
violations.push({
|
|
24
24
|
file,
|
|
25
25
|
line: func.startLine ?? func.line ?? 1,
|
|
26
26
|
ruleId: 'clean-code.deep-nesting',
|
|
27
27
|
message: `Function "${func.name}" has deep nesting: ${func.nestingDepth} levels (maximum: ${
|
|
28
|
-
|
|
28
|
+
settings.threshold
|
|
29
29
|
})`,
|
|
30
|
-
severity:
|
|
30
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const godClassRule: Rule = {
|
|
7
5
|
id: 'clean-code.god-class',
|
|
8
6
|
name: 'God Class Rule',
|
|
9
|
-
threshold:
|
|
10
|
-
severity:
|
|
7
|
+
threshold: 15,
|
|
8
|
+
severity: 'warning',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['god-class'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
interface ClassObject {
|
|
@@ -35,15 +35,15 @@ export const godClassRule: Rule = {
|
|
|
35
35
|
return true;
|
|
36
36
|
}).length;
|
|
37
37
|
|
|
38
|
-
if (methodCount > (
|
|
38
|
+
if (methodCount > (settings.threshold ?? 0)) {
|
|
39
39
|
violations.push({
|
|
40
40
|
file,
|
|
41
41
|
line: cls.line,
|
|
42
42
|
ruleId: 'clean-code.god-class',
|
|
43
43
|
message: `Class "${cls.name}" has too many methods: ${methodCount} (maximum: ${
|
|
44
|
-
|
|
44
|
+
settings.threshold
|
|
45
45
|
})`,
|
|
46
|
-
severity:
|
|
46
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const largeFileRule: Rule = {
|
|
7
5
|
id: 'clean-code.large-file',
|
|
8
6
|
name: 'Large File Rule',
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// Static field mirrors the built-in default. The ENFORCED value comes from
|
|
8
|
+
// getActiveConfig() inside check(), so `.principlesrc` is honoured (#457).
|
|
9
|
+
threshold: 1150,
|
|
10
|
+
severity: 'warning',
|
|
11
11
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
12
|
const violations: Violation[] = [];
|
|
13
|
-
|
|
13
|
+
// Read the ACTIVE config per invocation. Snapshotting it at module load was
|
|
14
|
+
// #457: a project's `.principlesrc` threshold was parsed but never applied.
|
|
15
|
+
const settings = getActiveConfig().rules['clean-code']['large-file'];
|
|
16
|
+
const threshold = settings.threshold ?? 500;
|
|
17
|
+
|
|
14
18
|
try {
|
|
15
19
|
interface TypedAdapter {
|
|
16
20
|
countLines?: (file: string) => number | undefined;
|
|
17
21
|
}
|
|
18
22
|
const typedAdapter = adapter as TypedAdapter;
|
|
19
23
|
const lineCount = typedAdapter.countLines?.(file) ?? 0;
|
|
20
|
-
|
|
21
|
-
if (lineCount >
|
|
24
|
+
|
|
25
|
+
if (lineCount > threshold) {
|
|
22
26
|
violations.push({
|
|
23
27
|
file,
|
|
24
28
|
line: 1,
|
|
25
29
|
ruleId: 'clean-code.large-file',
|
|
26
|
-
message: `File is too large: ${lineCount} lines (maximum: ${
|
|
27
|
-
severity:
|
|
30
|
+
message: `File is too large: ${lineCount} lines (maximum: ${threshold})`,
|
|
31
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
28
32
|
});
|
|
29
33
|
}
|
|
30
34
|
} catch { }
|
|
31
|
-
|
|
35
|
+
|
|
32
36
|
return violations;
|
|
33
37
|
}
|
|
34
38
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const longFunctionRule: Rule = {
|
|
7
5
|
id: 'clean-code.long-function',
|
|
8
6
|
name: 'Long Function Rule',
|
|
9
|
-
threshold:
|
|
10
|
-
severity:
|
|
7
|
+
threshold: 50,
|
|
8
|
+
severity: 'warning',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['long-function'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
interface FunctionObj {
|
|
@@ -28,15 +28,15 @@ export const longFunctionRule: Rule = {
|
|
|
28
28
|
for (const func of functions) {
|
|
29
29
|
const { name, startLine, length } = func;
|
|
30
30
|
|
|
31
|
-
if (length > (
|
|
31
|
+
if (length > (settings.threshold ?? 0)) {
|
|
32
32
|
violations.push({
|
|
33
33
|
file,
|
|
34
34
|
line: startLine ?? 1,
|
|
35
35
|
ruleId: 'clean-code.long-function',
|
|
36
36
|
message: `Function "${name}" is too long: ${length} lines (maximum: ${
|
|
37
|
-
|
|
37
|
+
settings.threshold
|
|
38
38
|
})`,
|
|
39
|
-
severity:
|
|
39
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const EXCLUDED_NUMBERS = config.rules['clean-code']['magic-numbers'].exclude || [0, 1, -1, 2, 10, 100, 1000, 60, 24, 7, 30, 365, 256, 1024];
|
|
4
|
+
const DEFAULT_EXCLUDED_NUMBERS = [0, 1, -1, 2, 10, 100, 1000, 60, 24, 7, 30, 365, 256, 1024];
|
|
7
5
|
|
|
8
6
|
export const magicNumbersRule: Rule = {
|
|
9
7
|
id: 'clean-code.magic-numbers',
|
|
10
8
|
name: 'Magic Numbers Rule',
|
|
9
|
+
// Not config-derived: this rule reports every non-excluded literal, so the
|
|
10
|
+
// static field is an informational placeholder rather than an enforced bound.
|
|
11
11
|
threshold: 10,
|
|
12
|
-
severity:
|
|
12
|
+
severity: 'info',
|
|
13
13
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
14
14
|
const violations: Violation[] = [];
|
|
15
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
16
|
+
const settings = getActiveConfig().rules['clean-code']['magic-numbers'];
|
|
15
17
|
|
|
16
18
|
try {
|
|
17
19
|
interface NumberObject {
|
|
@@ -37,9 +39,10 @@ export const magicNumbersRule: Rule = {
|
|
|
37
39
|
}
|
|
38
40
|
} catch { }
|
|
39
41
|
|
|
42
|
+
const excludedNumbers = settings.exclude ?? DEFAULT_EXCLUDED_NUMBERS;
|
|
40
43
|
const filteredNumbers = magicNumbers.filter(numObj => {
|
|
41
44
|
const numValue = numObj.value;
|
|
42
|
-
return !
|
|
45
|
+
return !excludedNumbers.includes(numValue);
|
|
43
46
|
});
|
|
44
47
|
|
|
45
48
|
filteredNumbers.forEach(numObj => {
|
|
@@ -48,7 +51,7 @@ export const magicNumbersRule: Rule = {
|
|
|
48
51
|
line: numObj.line,
|
|
49
52
|
ruleId: 'clean-code.magic-numbers',
|
|
50
53
|
message: `Potential magic number detected: ${numObj.value}. Consider using a named constant instead.`,
|
|
51
|
-
severity:
|
|
54
|
+
severity: (settings.severity as Severity) ?? 'info'
|
|
52
55
|
});
|
|
53
56
|
});
|
|
54
57
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const manyExportsRule: Rule = {
|
|
7
5
|
id: 'clean-code.many-exports',
|
|
8
6
|
name: 'Many Exports Rule',
|
|
9
|
-
threshold:
|
|
10
|
-
severity:
|
|
7
|
+
threshold: 10,
|
|
8
|
+
severity: 'warning',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['many-exports'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
interface ExportObj {
|
|
@@ -22,7 +22,7 @@ export const manyExportsRule: Rule = {
|
|
|
22
22
|
|
|
23
23
|
const typedAdapter = adapter as TypedAdapter;
|
|
24
24
|
const exports = typedAdapter.extractExports ? typedAdapter.extractExports() : [];
|
|
25
|
-
const threshold =
|
|
25
|
+
const threshold = settings.threshold ?? 10;
|
|
26
26
|
|
|
27
27
|
if (exports && exports.length > threshold) {
|
|
28
28
|
violations.push({
|
|
@@ -30,7 +30,7 @@ export const manyExportsRule: Rule = {
|
|
|
30
30
|
line: exports[0]?.line || 1,
|
|
31
31
|
ruleId: 'clean-code.many-exports',
|
|
32
32
|
message: `Module has too many exports: ${exports.length} (maximum: ${threshold}). Consider splitting into focused sub-modules.`,
|
|
33
|
-
severity:
|
|
33
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
} catch { }
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const missingErrorHandlingRule: Rule = {
|
|
7
5
|
id: 'clean-code.missing-error-handling',
|
|
8
6
|
name: 'Missing Error Handling Rule',
|
|
9
7
|
threshold: 1,
|
|
10
|
-
severity:
|
|
8
|
+
severity: 'warning',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['missing-error-handling'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; ioOperations?: string[]; hasTryCatch?: boolean;}> | undefined };
|
|
@@ -22,7 +22,7 @@ export const missingErrorHandlingRule: Rule = {
|
|
|
22
22
|
line: func.startLine ?? func.line ?? 1,
|
|
23
23
|
ruleId: 'clean-code.missing-error-handling',
|
|
24
24
|
message: `Function "${func.name}" has IO operations (${func.ioOperations.join(', ')}) without error handling`,
|
|
25
|
-
severity:
|
|
25
|
+
severity: (settings.severity as Severity) ?? 'warning'
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const tooManyParamsRule: Rule = {
|
|
7
5
|
id: 'clean-code.too-many-params',
|
|
8
6
|
name: 'Too Many Parameters Rule',
|
|
9
|
-
threshold:
|
|
10
|
-
severity:
|
|
7
|
+
threshold: 7,
|
|
8
|
+
severity: 'info',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['too-many-params'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; paramCount?: number;}> | undefined };
|
|
16
16
|
const functions = typedAdapter.extractFunctions?.() || [];
|
|
17
17
|
|
|
18
18
|
for (const func of functions) {
|
|
19
|
-
if (func.paramCount && func.paramCount > (
|
|
19
|
+
if (func.paramCount && func.paramCount > (settings.threshold ?? 0)) {
|
|
20
20
|
violations.push({
|
|
21
21
|
file,
|
|
22
22
|
line: func.startLine ?? func.line ?? 1,
|
|
23
23
|
ruleId: 'clean-code.too-many-params',
|
|
24
24
|
message: `Function "${func.name}" has too many parameters: ${func.paramCount} (maximum: ${
|
|
25
|
-
|
|
25
|
+
settings.threshold
|
|
26
26
|
})`,
|
|
27
|
-
severity:
|
|
27
|
+
severity: (settings.severity as Severity) ?? 'info'
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const unusedImportsRule: Rule = {
|
|
7
5
|
id: 'clean-code.unused-imports',
|
|
8
6
|
name: 'Unused Imports Rule',
|
|
9
7
|
threshold: 1,
|
|
10
|
-
severity:
|
|
8
|
+
severity: 'info',
|
|
11
9
|
check: (file: string, adapter: unknown): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['clean-code']['unused-imports'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
const typedAdapter = adapter as { imports?: Array<{name?: string; line?: number; used?: boolean; type?: string;}> | undefined };
|
|
@@ -22,7 +22,7 @@ export const unusedImportsRule: Rule = {
|
|
|
22
22
|
line: imp.line ?? 1,
|
|
23
23
|
ruleId: 'clean-code.unused-imports',
|
|
24
24
|
message: `Unused import "${imp.name}" - consider removing`,
|
|
25
|
-
severity:
|
|
25
|
+
severity: (settings.severity as Severity) ?? 'info'
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { Rule, Violation } from '../../types';
|
|
2
|
-
import {
|
|
1
|
+
import { Rule, Violation, Severity } from '../../types';
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const { severity } = config.rules['solid']['dip'];
|
|
6
|
-
const EXCLUDED_CLASSES = config.rules['solid']['dip'].exclude || [
|
|
4
|
+
const DEFAULT_EXCLUDED_CLASSES = [
|
|
7
5
|
'Date', 'Map', 'Set', 'Error', 'Array', 'Object', 'Promise'
|
|
8
6
|
];
|
|
9
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Read the excluded list from the ACTIVE config on each call.
|
|
10
|
+
*
|
|
11
|
+
* A module-load snapshot was #457: a project's `.principlesrc` override was
|
|
12
|
+
* parsed but never consulted.
|
|
13
|
+
*/
|
|
14
|
+
function excludedClasses(): (string | number)[] {
|
|
15
|
+
return getActiveConfig().rules['solid']['dip'].exclude ?? DEFAULT_EXCLUDED_CLASSES;
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
function shouldSkipInstantiation(className: string): boolean {
|
|
11
|
-
if (
|
|
19
|
+
if (excludedClasses().includes(className)) return true;
|
|
12
20
|
if (className.endsWith('Factory') || className.endsWith('Builder')) return true;
|
|
13
21
|
return false;
|
|
14
22
|
}
|
|
@@ -17,9 +25,11 @@ export const dipRule: Rule = {
|
|
|
17
25
|
id: 'solid.dip',
|
|
18
26
|
name: 'Dependency Inversion Principle Rule',
|
|
19
27
|
threshold: 0,
|
|
20
|
-
severity:
|
|
28
|
+
severity: 'warning',
|
|
21
29
|
check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
|
|
22
30
|
const violations: Violation[] = [];
|
|
31
|
+
const settings = getActiveConfig().rules['solid']['dip'];
|
|
32
|
+
const severity = (settings.severity as Severity) ?? 'warning';
|
|
23
33
|
|
|
24
34
|
try {
|
|
25
35
|
for (const cls_any of adapter.extractClasses() || []) {
|
|
@@ -36,7 +46,7 @@ export const dipRule: Rule = {
|
|
|
36
46
|
file,
|
|
37
47
|
line: cls.line || 0,
|
|
38
48
|
ruleId: 'solid.dip',
|
|
39
|
-
severity
|
|
49
|
+
severity,
|
|
40
50
|
message: `Direct instantiation detected: new ${className}(). Prefer dependency injection for flexibility.`,
|
|
41
51
|
});
|
|
42
52
|
}
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import { Rule, Violation } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const ispRule: Rule = {
|
|
7
5
|
id: 'solid.isp',
|
|
8
6
|
name: 'Interface Segregation Principle Rule',
|
|
9
|
-
threshold:
|
|
10
|
-
severity:
|
|
7
|
+
threshold: 10,
|
|
8
|
+
severity: 'info',
|
|
11
9
|
check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['solid']['isp'];
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
const typedAdapter = adapter as { extractInterfaces?: () => Array<{name?: string; line?: number; methodCount?: number;}> | undefined };
|
|
16
16
|
const interfaces = typedAdapter.extractInterfaces?.() || [];
|
|
17
|
+
const maxMethods = settings.methodThreshold ?? 10;
|
|
17
18
|
|
|
18
19
|
for (const iface of interfaces) {
|
|
19
20
|
const methodCount = iface.methodCount || 0;
|
|
20
21
|
|
|
21
|
-
if (methodCount >
|
|
22
|
+
if (methodCount > maxMethods) {
|
|
22
23
|
violations.push({
|
|
23
24
|
file,
|
|
24
25
|
line: iface.line ?? 1,
|
|
25
26
|
ruleId: 'solid.isp',
|
|
26
27
|
message: `Interface "${iface.name}" has too many methods: ${methodCount} (maximum: ${
|
|
27
|
-
|
|
28
|
+
maxMethods
|
|
28
29
|
}). Consider splitting into focused interfaces.`,
|
|
29
|
-
severity:
|
|
30
|
+
severity: (settings.severity as "error" | "warning" | "info") ?? 'info'
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Rule, Violation, Adapter } from '../../types';
|
|
2
|
-
import {
|
|
1
|
+
import { Rule, Violation, Adapter, Severity } from '../../types';
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
3
3
|
|
|
4
|
-
const config = getDefaultConfig();
|
|
5
|
-
const severity = config.rules['solid']['lsp'].severity as 'error' | 'warning' | 'info';
|
|
6
4
|
const PRIMITIVE_TYPES = new Set([
|
|
7
5
|
'string', 'number', 'boolean', 'any', 'void', 'null', 'undefined', 'Object', 'Array',
|
|
8
6
|
]);
|
|
@@ -37,12 +35,14 @@ function collectMethodParamTypes(methodSignature: string): string[] {
|
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
function buildViolation(file: string, cls: ClassInfo, paramType: string): Violation {
|
|
38
|
+
// Read the ACTIVE severity here rather than at module load (#457).
|
|
39
|
+
const settings = getActiveConfig().rules['solid']['lsp'];
|
|
40
40
|
return {
|
|
41
41
|
file,
|
|
42
42
|
line: cls.line ?? 0,
|
|
43
43
|
ruleId: 'solid.lsp',
|
|
44
44
|
message: `Possible LSP violation in "${cls.name}". Parameter type "${paramType}" may not be compatible with base class contract.`,
|
|
45
|
-
severity,
|
|
45
|
+
severity: (settings.severity as Severity) ?? 'info',
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -67,7 +67,7 @@ export const lspRule: Rule = {
|
|
|
67
67
|
id: 'solid.lsp',
|
|
68
68
|
name: 'Liskov Substitution Principle Rule',
|
|
69
69
|
threshold: 0,
|
|
70
|
-
severity,
|
|
70
|
+
severity: 'info',
|
|
71
71
|
check: (file: string, adapter: Adapter): Violation[] => {
|
|
72
72
|
try {
|
|
73
73
|
const classes = (adapter.extractClasses() || []) as ClassInfo[];
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Rule, Violation } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
5
3
|
|
|
6
4
|
export const ocpRule: Rule = {
|
|
7
5
|
id: 'solid.ocp',
|
|
8
6
|
name: 'Open/Closed Principle Rule',
|
|
9
7
|
threshold: 0,
|
|
10
|
-
severity:
|
|
8
|
+
severity: 'info',
|
|
11
9
|
check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
|
|
12
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['solid']['ocp'];
|
|
13
|
+
const severity = (settings.severity as "error" | "warning" | "info") ?? 'info';
|
|
13
14
|
|
|
14
15
|
try {
|
|
15
16
|
const classes = adapter.extractClasses() || [];
|
|
@@ -28,7 +29,7 @@ export const ocpRule: Rule = {
|
|
|
28
29
|
line: cls.line ?? 1,
|
|
29
30
|
ruleId: 'solid.ocp',
|
|
30
31
|
message: `Possible modification of base class "${baseClass}" while extending. Extension should not require modifying the base.`,
|
|
31
|
-
severity:
|
|
32
|
+
severity: severity
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { Rule, Violation } from '../../types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const config = getDefaultConfig();
|
|
5
|
-
const { methodThreshold, severity } = config.rules['solid']['srp'];
|
|
2
|
+
import { getActiveConfig } from '../../config';
|
|
6
3
|
|
|
7
4
|
export const srpRule: Rule = {
|
|
8
5
|
id: 'solid.srp',
|
|
9
6
|
name: 'Single Responsibility Principle Rule',
|
|
10
|
-
threshold:
|
|
11
|
-
severity:
|
|
7
|
+
threshold: 15,
|
|
8
|
+
severity: 'warning',
|
|
12
9
|
check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
|
|
13
10
|
const violations: Violation[] = [];
|
|
11
|
+
// Read the ACTIVE config per invocation; a module-load snapshot was #457.
|
|
12
|
+
const settings = getActiveConfig().rules['solid']['srp'];
|
|
14
13
|
|
|
15
14
|
try {
|
|
16
15
|
const classes = adapter.extractClasses() || [];
|
|
17
|
-
const maxMethods = methodThreshold as number;
|
|
18
|
-
const sev = severity as
|
|
16
|
+
const maxMethods = settings.methodThreshold as number;
|
|
17
|
+
const sev = (settings.severity as 'error' | 'warning' | 'info') ?? 'warning';
|
|
19
18
|
|
|
20
19
|
for (const raw of classes) {
|
|
21
20
|
const cls = raw as { code?: string; line?: number; name?: string; methodCount?: number; imports?: Record<string, unknown> };
|
package/principles/types.ts
CHANGED
|
@@ -3,7 +3,22 @@ export type Severity = 'error' | 'warning' | 'info';
|
|
|
3
3
|
export interface Rule {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
|
6
|
+
/**
|
|
7
|
+
* The rule's BUILT-IN default threshold.
|
|
8
|
+
*
|
|
9
|
+
* @deprecated This is NOT necessarily the value being enforced. Since #457 the
|
|
10
|
+
* effective threshold comes from the active `.principlesrc` and is read inside
|
|
11
|
+
* `check()`. Reading this field to decide whether a violation should occur will
|
|
12
|
+
* disagree with the checker whenever the project overrides the threshold. Use
|
|
13
|
+
* `getActiveConfig().rules[group][rule].threshold` for the enforced value.
|
|
14
|
+
*/
|
|
6
15
|
threshold: number;
|
|
16
|
+
/**
|
|
17
|
+
* The rule's BUILT-IN default severity.
|
|
18
|
+
*
|
|
19
|
+
* @deprecated See {@link Rule.threshold}: the enforced severity comes from the
|
|
20
|
+
* active config. Individual `Violation.severity` values are authoritative.
|
|
21
|
+
*/
|
|
7
22
|
severity: Severity;
|
|
8
23
|
check: (file: string, adapter: Adapter) => Violation[];
|
|
9
24
|
}
|
|
@@ -588,3 +588,31 @@ describe('sync-package-content subprocess invocation', () => {
|
|
|
588
588
|
expect(Object.keys(pkgJsonAfter.dependencies || {}).length).toBe(0, 'dependencies should remain empty');
|
|
589
589
|
}, 10000);
|
|
590
590
|
});
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* @test REQ-TDD-005 load-time-no-sync
|
|
594
|
+
* @intent Requiring sync-package-content must not sync the repo — only executing it as a
|
|
595
|
+
* script may. The top-level main() used to run on every require(), so a test that
|
|
596
|
+
* merely imported checkDocsDrift regenerated the whole npm-package mirror mid-run
|
|
597
|
+
* and tripped vitest-worktree-guard on CI (missing mirror of an uncommitted
|
|
598
|
+
* canonical file was (re)created as a new untracked path).
|
|
599
|
+
* @covers AC-TDD-005-04
|
|
600
|
+
*/
|
|
601
|
+
describe('load-time behavior', () => {
|
|
602
|
+
it('require() performs no filesystem sync; main() runs only when executed directly', () => {
|
|
603
|
+
const modulePath = require.resolve('../sync-package-content');
|
|
604
|
+
delete require.cache[modulePath];
|
|
605
|
+
const copies = [];
|
|
606
|
+
const originalCopy = fs.copyFileSync;
|
|
607
|
+
fs.copyFileSync = (...args) => { copies.push(args); };
|
|
608
|
+
try {
|
|
609
|
+
require('../sync-package-content');
|
|
610
|
+
} finally {
|
|
611
|
+
fs.copyFileSync = originalCopy;
|
|
612
|
+
}
|
|
613
|
+
expect(copies, `require() must not copy; got ${copies.length} copies`).toHaveLength(0);
|
|
614
|
+
const mod = require.cache[modulePath].exports;
|
|
615
|
+
expect(typeof mod.checkDocsDrift).toBe('function');
|
|
616
|
+
expect(typeof mod.checkAdapterDrift).toBe('function');
|
|
617
|
+
});
|
|
618
|
+
});
|