@boyingliu01/xp-gate 0.31.0 → 0.31.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/adapter-common.sh +41 -3
  2. package/adapters/gate-3.sh +8 -1
  3. package/adapters/gate-8.sh +67 -6
  4. package/adapters/go.sh +18 -1
  5. package/adapters/iac.sh +18 -1
  6. package/adapters/powershell.sh +110 -7
  7. package/adapters/python.sh +18 -4
  8. package/gate-3.sh +10 -1
  9. package/gate-8.sh +67 -6
  10. package/hooks/adapter-common.sh +41 -3
  11. package/hooks/gate-3.sh +10 -1
  12. package/hooks/gate-8.sh +67 -6
  13. package/hooks/lib/coverage-total.sh +20 -5
  14. package/hooks/lib/gate-5c-alignment.sh +145 -0
  15. package/hooks/lib/jscpd-run.sh +40 -1
  16. package/hooks/lib/native-path.sh +106 -0
  17. package/hooks/lib/prepush-score.py +143 -0
  18. package/hooks/lib/sprint-gate-report.sh +23 -0
  19. package/hooks/lib/toolchain-path.sh +370 -0
  20. package/hooks/lib/validate-code-walkthrough.cjs +224 -23
  21. package/hooks/pre-commit +371 -163
  22. package/hooks/pre-push +192 -74
  23. package/hooks/sprint-gate.sh +14 -3
  24. package/lib/__tests__/check-alignment-cli.test.mjs +20 -3
  25. package/lib/__tests__/check-alignment-reasons.test.mjs +91 -0
  26. package/lib/__tests__/doctor-delphi.test.js +46 -0
  27. package/lib/__tests__/doctor-exec-retry.test.js +20 -10
  28. package/lib/__tests__/doctor-hook-signatures.test.js +65 -0
  29. package/lib/__tests__/doctor-path-equivalence.test.js +12 -1
  30. package/lib/__tests__/doctor.test.js +7 -1
  31. package/lib/__tests__/hook-health-path.test.js +9 -1
  32. package/lib/__tests__/issue-566-sync-hooks-backup.test.js +221 -0
  33. package/lib/__tests__/issue-583-hook-deploy-log.test.js +121 -0
  34. package/lib/__tests__/issue-586-principles-cmdline.test.js +183 -0
  35. package/lib/__tests__/issue-589-check-all-gate3.test.js +190 -0
  36. package/lib/__tests__/issue-590-archlint-vacuous-pass.test.js +138 -0
  37. package/lib/__tests__/shared-paths-lazy-home.test.js +67 -0
  38. package/lib/arch.js +261 -44
  39. package/lib/check-alignment-runner.cjs +23 -0
  40. package/lib/check.js +4 -1
  41. package/lib/doctor.js +194 -49
  42. package/lib/gate-runner.js +265 -58
  43. package/lib/mattpocock-license-verdict.json +1 -1
  44. package/lib/principles.js +228 -6
  45. package/lib/shared-paths.js +53 -19
  46. package/mock-policy/AGENTS.md +3 -3
  47. package/mutation/AGENTS.md +3 -3
  48. package/mutation/__tests__/gremlins-runner.test.ts +3 -3
  49. package/package.json +1 -1
  50. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  51. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  52. package/plugins/claude-code/skills/delphi-review/SKILL.md +50 -0
  53. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  54. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  55. package/plugins/dsh/package.json +1 -1
  56. package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
  57. package/plugins/dsh/skills/delphi-review/SKILL.md +50 -0
  58. package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
  59. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
  60. package/plugins/dsh/src/delphi-config-env-diagnostics.test.ts +210 -0
  61. package/plugins/dsh/src/delphi-config.ts +151 -9
  62. package/plugins/opencode/package.json +1 -1
  63. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  64. package/plugins/opencode/skills/delphi-review/SKILL.md +50 -0
  65. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  66. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  67. package/plugins/qoder/plugin.json +1 -1
  68. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  69. package/plugins/qoder/skills/delphi-review/SKILL.md +50 -0
  70. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  71. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  72. package/principles/AGENTS.md +3 -3
  73. package/principles/__tests__/issue-587-java-member-count.test.ts +279 -0
  74. package/principles/__tests__/issue-588-lsp-java-false-positives.test.ts +213 -0
  75. package/principles/adapters/java.ts +127 -11
  76. package/principles/rules/__tests__/solid/issue-588-lsp-base-contract.test.ts +252 -0
  77. package/principles/rules/__tests__/solid/lsp.test.ts +26 -22
  78. package/principles/rules/solid/lsp.ts +348 -23
  79. package/scripts/__tests__/sync-package-content.test.js +32 -1
  80. package/scripts/sync-package-content.js +6 -1
  81. package/skills/delphi-review/AGENTS.md +3 -3
  82. package/skills/delphi-review/SKILL.md +50 -0
  83. package/skills/sprint-flow/AGENTS.md +3 -3
  84. package/skills/test-specification-alignment/AGENTS.md +3 -3
  85. package/sprint-gate.sh +14 -3
@@ -0,0 +1,252 @@
1
+ /**
2
+ * @test REQ-618
3
+ * @intent The LSP rule must still DETECT a real violation when the base contract is
4
+ * available in the same file, so that #588's "stay silent when undetermined" fix
5
+ * does not degenerate into "never report anything".
6
+ * @covers AC-618-04, AC-618-05
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { lspRule } from '../../solid/lsp';
10
+
11
+ /** Adapter stub exposing exactly what the rule consumes. */
12
+ function adapterFor(classes: Array<{ name?: string; line?: number; code?: string }>) {
13
+ return {
14
+ detectLanguage: () => 'java',
15
+ parseAST: () => undefined,
16
+ extractFunctions: () => [],
17
+ extractClasses: () => classes,
18
+ countLines: () => 0,
19
+ };
20
+ }
21
+
22
+ /**
23
+ * A base class and a derived class in ONE file.
24
+ *
25
+ * `handle(DataSource ds)` is the base contract; the derived class may honour it or break
26
+ * it. Both classes are visible, so the rule has everything it needs to decide — which is
27
+ * exactly the case #588's fix must NOT silence.
28
+ */
29
+ const BASE_WITH_ONE_PARAM = `
30
+ class BaseHandler {
31
+ public void handle(DataSource ds) { }
32
+ }
33
+ `;
34
+
35
+ /** Line where the derived class starts in the two-class fixtures below. */
36
+ const DERIVED_CLASS_LINE = 4;
37
+
38
+ /** Line where the derived TypeScript class starts in the two-line-header fixtures. */
39
+ const TS_DERIVED_CLASS_LINE = 3;
40
+
41
+ describe('solid.lsp detects a violation when the base contract IS available', () => {
42
+ it('reports a derived parameter type the base method does not accept', () => {
43
+ const violations = lspRule.check('Handlers.java', adapterFor([
44
+ { name: 'BaseHandler', line: 1, code: BASE_WITH_ONE_PARAM },
45
+ {
46
+ name: 'CsvHandler',
47
+ line: DERIVED_CLASS_LINE,
48
+ code: `class CsvHandler extends BaseHandler {
49
+ public void handle(FileChannel channel) { }
50
+ }`,
51
+ },
52
+ ]) as never);
53
+
54
+ // The point of the whole rule: an override that narrows or changes the parameter type
55
+ // breaks substitutability, and here both sides are visible so the claim is earned.
56
+ expect(violations.length).toBeGreaterThan(0);
57
+ expect(violations[0].ruleId).toBe('solid.lsp');
58
+ expect(violations[0].message).toContain('FileChannel');
59
+ });
60
+
61
+ it('stays silent when the override keeps the base parameter type', () => {
62
+ const violations = lspRule.check('Handlers.java', adapterFor([
63
+ { name: 'BaseHandler', line: 1, code: BASE_WITH_ONE_PARAM },
64
+ {
65
+ name: 'CsvHandler',
66
+ line: DERIVED_CLASS_LINE,
67
+ code: `class CsvHandler extends BaseHandler {
68
+ public void handle(DataSource ds) { }
69
+ }`,
70
+ },
71
+ ]) as never);
72
+
73
+ expect(violations).toHaveLength(0);
74
+ });
75
+
76
+ it('stays silent when the base class is NOT in the file', () => {
77
+ // The control for the two cases above: same derived code, no base declaration
78
+ // available. Without this, a rule that simply reported every non-primitive parameter
79
+ // would pass the first case and fail only by accident elsewhere.
80
+ const violations = lspRule.check('Handlers.java', adapterFor([
81
+ {
82
+ name: 'CsvHandler',
83
+ line: 1,
84
+ code: `class CsvHandler extends BaseHandler {
85
+ public void handle(FileChannel channel) { }
86
+ }`,
87
+ },
88
+ ]) as never);
89
+
90
+ expect(violations).toHaveLength(0);
91
+ });
92
+
93
+ it('stays silent when the base method itself is not declared', () => {
94
+ // Base visible, but it does not declare `handle` at all — an inherited method from
95
+ // further up, which this analysis cannot see. Not a violation, not verifiable.
96
+ const baseWithoutHandle = `class BaseHandler { public void close() { } }`;
97
+ const violations = lspRule.check('Handlers.java', adapterFor([
98
+ { name: 'BaseHandler', line: 1, code: baseWithoutHandle },
99
+ {
100
+ name: 'CsvHandler',
101
+ line: 2,
102
+ code: `class CsvHandler extends BaseHandler {
103
+ public void handle(FileChannel channel) { }
104
+ }`,
105
+ },
106
+ ]) as never);
107
+
108
+ expect(violations).toHaveLength(0);
109
+ });
110
+
111
+ it('resolves a base class referenced through a qualified name', () => {
112
+ // `extends com.acme.BaseHandler` is ordinary Java; a plain string compare would miss
113
+ // the base and silently downgrade a decidable case to silence.
114
+ const violations = lspRule.check('Handlers.java', adapterFor([
115
+ { name: 'BaseHandler', line: 1, code: BASE_WITH_ONE_PARAM },
116
+ {
117
+ name: 'CsvHandler',
118
+ line: 2,
119
+ code: `class CsvHandler extends com.acme.BaseHandler {
120
+ public void handle(FileChannel channel) { }
121
+ }`,
122
+ },
123
+ ]) as never);
124
+
125
+ expect(violations.length).toBeGreaterThan(0);
126
+ });
127
+
128
+ it('treats a parameter type the base method also declares as compatible', () => {
129
+ // The positive half of the base comparison: this is the branch that returns `true`,
130
+ // i.e. "I have a base contract AND it accepts this type". Without it the only
131
+ // observable outcome of the base path would be a violation, and a rule that reported
132
+ // every override with a resolvable base would still pass every other case here.
133
+ const base = `class BaseHandler { public void handle(DataSource ds) { } }`;
134
+ const violations = lspRule.check('Handlers.java', adapterFor([
135
+ { name: 'BaseHandler', line: 1, code: base },
136
+ {
137
+ name: 'CsvHandler',
138
+ line: 2,
139
+ code: `class CsvHandler extends BaseHandler {
140
+ public void handle(DataSource ds) { }
141
+ }`,
142
+ },
143
+ ]) as never);
144
+
145
+ expect(violations).toHaveLength(0);
146
+ });
147
+
148
+ it('handles a generic and an array parameter type without crashing', () => {
149
+ // Exercises `normaliseTypeName`'s stripping of `<...>`, `[]` and `?`. Those suffixes
150
+ // are the reason `Map<String, Item>` does not get compared as the literal string
151
+ // "Map<String, Item>", and the array/optional forms are ordinary in both languages.
152
+ const base = `class BaseHandler { public void handle(Map<String, Item> index) { } }`;
153
+ const violations = lspRule.check('Handlers.java', adapterFor([
154
+ { name: 'BaseHandler', line: 1, code: base },
155
+ {
156
+ name: 'CsvHandler',
157
+ line: 2,
158
+ code: `class CsvHandler extends BaseHandler {
159
+ public void handle(Map<String, Item> index) { }
160
+ }`,
161
+ },
162
+ ]) as never);
163
+
164
+ expect(violations).toHaveLength(0);
165
+ });
166
+
167
+ it('reads TypeScript `name: Type` parameters, not Java ones', () => {
168
+ // Every other case here runs the Java path. The rule is shared by both languages, and
169
+ // #588's whole lesson is that a parser which only works for one of them silently
170
+ // mangles the other — so the TypeScript path needs its own case, exercising
171
+ // `extractTsParamType` rather than `extractJavaParamType`.
172
+ const tsAdapter = { ...adapterFor([]), detectLanguage: () => 'typescript' };
173
+ const base = `class BaseHandler {
174
+ handle(ds: DataSource): void {}
175
+ }`;
176
+ const derived = `class CsvHandler extends BaseHandler {
177
+ handle(channel: FileChannel): void {}
178
+ }`;
179
+ const violations = lspRule.check('handlers.ts', {
180
+ ...tsAdapter,
181
+ extractClasses: () => [
182
+ { name: 'BaseHandler', line: 1, code: base },
183
+ { name: 'CsvHandler', line: TS_DERIVED_CLASS_LINE, code: derived },
184
+ ],
185
+ } as never);
186
+
187
+ expect(violations.length).toBeGreaterThan(0);
188
+ expect(violations[0].message).toContain('FileChannel');
189
+ });
190
+
191
+ it('stays silent for a TypeScript override that matches the base', () => {
192
+ const tsAdapter = { ...adapterFor([]), detectLanguage: () => 'typescript' };
193
+ const base = `class BaseHandler {
194
+ handle(ds: DataSource): void {}
195
+ }`;
196
+ const derived = `class CsvHandler extends BaseHandler {
197
+ handle(ds: DataSource): void {}
198
+ }`;
199
+ const violations = lspRule.check('handlers.ts', {
200
+ ...tsAdapter,
201
+ extractClasses: () => [
202
+ { name: 'BaseHandler', line: 1, code: base },
203
+ { name: 'CsvHandler', line: TS_DERIVED_CLASS_LINE, code: derived },
204
+ ],
205
+ } as never);
206
+
207
+ expect(violations).toHaveLength(0);
208
+ });
209
+
210
+ it('treats an adapter without detectLanguage as non-Java rather than crashing', () => {
211
+ // Older or third-party adapters may not implement `detectLanguage`. The rule must
212
+ // fall back to the `name: Type` reading instead of throwing on an absent method —
213
+ // a crash here would surface as a Gate 4 rule error, not as silence.
214
+ const adapterWithoutLanguage = {
215
+ parseAST: () => undefined,
216
+ extractFunctions: () => [],
217
+ countLines: () => 0,
218
+ extractClasses: () => [
219
+ { name: 'BaseHandler', line: 1, code: `class BaseHandler {\n handle(ds: DataSource): void {}\n}` },
220
+ {
221
+ name: 'CsvHandler',
222
+ line: TS_DERIVED_CLASS_LINE,
223
+ code: `class CsvHandler extends BaseHandler {\n handle(channel: FileChannel): void {}\n}`,
224
+ },
225
+ ],
226
+ };
227
+
228
+ const violations = lspRule.check('handlers.ts', adapterWithoutLanguage as never);
229
+
230
+ expect(violations.length).toBeGreaterThan(0);
231
+ });
232
+
233
+ it('ignores a TypeScript parameter with no type annotation', () => {
234
+ // `handle(id)` has no `: Type`, so `extractTsParamType` returns null and the parameter
235
+ // contributes nothing. Without this the missing-annotation path is untested after the
236
+ // #588 rewrite, and a regex change could start inventing types out of parameter names.
237
+ const tsAdapter = {
238
+ ...adapterFor([]),
239
+ detectLanguage: () => 'typescript',
240
+ extractClasses: () => [
241
+ { name: 'BaseHandler', line: 1, code: `class BaseHandler {\n handle(id): void {}\n}` },
242
+ {
243
+ name: 'CsvHandler',
244
+ line: TS_DERIVED_CLASS_LINE,
245
+ code: `class CsvHandler extends BaseHandler {\n handle(id): void {}\n}`,
246
+ },
247
+ ],
248
+ };
249
+
250
+ expect(lspRule.check('handlers.ts', tsAdapter as never)).toHaveLength(0);
251
+ });
252
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { lspRule } from '../../solid/lsp';
2
+ import { lspRule, buildViolation } from '../../solid/lsp';
3
3
 
4
4
  const mockAdapter = {
5
5
  detectLanguage: () => 'typescript',
@@ -10,7 +10,12 @@ const mockAdapter = {
10
10
  };
11
11
 
12
12
  describe('lsp.ts - Liskov Substitution Principle Rule', () => {
13
- it('should detect parameter type changes in override', () => {
13
+ it('stays silent on a parameter type it cannot resolve against the base contract', () => {
14
+ // #588 changed what this rule is allowed to claim. `UserId` is neither a primitive nor
15
+ // a prefix of the class name, so a single-file analysis has no way to know what the
16
+ // BASE class declares — and "cannot determine" must not be reported as a violation.
17
+ // The previous expectation here asserted the defect: it read an unknown type as proof
18
+ // of incompatibility, which on Java flagged every class that has `extends`.
14
19
  const mockAdapterWithViolation = {
15
20
  ...mockAdapter,
16
21
  extractClasses: () => [{
@@ -19,11 +24,10 @@ describe('lsp.ts - Liskov Substitution Principle Rule', () => {
19
24
  code: `class DerivedRepo extends BaseRepo { findById(id: UserId): Entity {} }`
20
25
  }]
21
26
  };
22
-
27
+
23
28
  const violations = lspRule.check('test.ts', mockAdapterWithViolation as never);
24
-
25
- expect(violations.length).toBeGreaterThan(0);
26
- expect(violations[0].ruleId).toBe('solid.lsp');
29
+
30
+ expect(violations).toHaveLength(0);
27
31
  });
28
32
 
29
33
  it('should pass for proper override', () => {
@@ -152,8 +156,10 @@ class DerivedRepo extends BaseRepo {
152
156
  expect(violations).toHaveLength(0);
153
157
  });
154
158
 
155
- it('reports violation with className undefined (className && ... false branch)', () => {
156
- // cls.name undefined → className && ... short-circuits to false → incompatible
159
+ it('stays silent even when the class name is missing (no name is not evidence)', () => {
160
+ // This used to report, on the reasoning that `className && ...` short-circuits to
161
+ // false. Under #588 that is precisely the defect: an ABSENT name is one more thing the
162
+ // analysis does not know, so it cannot justify a verdict.
157
163
  const adapter = {
158
164
  ...mockAdapter,
159
165
  extractClasses: () => [{
@@ -162,21 +168,19 @@ class DerivedRepo extends BaseRepo {
162
168
  }],
163
169
  };
164
170
  const violations = lspRule.check('test.ts', adapter as never);
165
- expect(violations.length).toBeGreaterThan(0);
166
- expect(violations[0].ruleId).toBe('solid.lsp');
171
+ expect(violations).toHaveLength(0);
167
172
  });
168
173
 
169
- it('falls back to line 0 when cls.line is missing', () => {
170
- const adapter = {
171
- ...mockAdapter,
172
- extractClasses: () => [{
173
- name: 'D',
174
- // no line
175
- code: `class D extends B { foo(id: CustomId) {} }`,
176
- }],
177
- };
178
- const violations = lspRule.check('test.ts', adapter as never);
179
- expect(violations.length).toBeGreaterThan(0);
180
- expect(violations[0].line).toBe(0);
174
+ it('falls back to line 0 when a violation is built for a class with no line', () => {
175
+ // Asserted on the builder directly. After #588 `lspRule.check` can no longer reach
176
+ // this code path for an unknown type — that is the fix — so a case going through
177
+ // `check` would pass without exercising the `?? 0` fallback at all. A control that
178
+ // cannot fail proves nothing, which is why the import is explicit.
179
+ const violation = buildViolation('test.ts', { name: 'D' }, 'CustomId');
180
+ expect(violation.line).toBe(0);
181
+ expect(violation.ruleId).toBe('solid.lsp');
182
+ // And the line is honoured when present, so the fallback is not always taken.
183
+ const withLine = buildViolation('test.ts', { name: 'D', line: 42 }, 'CustomId');
184
+ expect(withLine.line).toBe(42);
181
185
  });
182
186
  });