@mettlecast/domain-cli 0.2.67 → 0.2.68

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.
@@ -726,6 +726,12 @@ async function checkAllHttpClientsUseKy(projectRoot) {
726
726
  { dir: join(projectRoot, 'domains'), label: 'domains' },
727
727
  { dir: join(projectRoot, 'packages', 'domain-cli', 'src', 'commands'), label: 'CLI commands' },
728
728
  ];
729
+ // Files to skip — these contain raw fetch() in comments, docs, or template
730
+ // literals that generate output code, not actual HTTP calls.
731
+ const skipFiles = new Set([
732
+ 'generate-sdk.ts', // template literal generating SDK client code
733
+ 'doctor.ts', // JSDoc block comments and fix-message strings self-flag
734
+ ]);
729
735
  const violations = [];
730
736
  let totalFiles = 0;
731
737
  for (const { dir, label } of scanDirs) {
@@ -734,6 +740,9 @@ async function checkAllHttpClientsUseKy(projectRoot) {
734
740
  const files = findFiles(dir, /\.ts$/);
735
741
  totalFiles += files.length;
736
742
  for (const file of files) {
743
+ const baseName = file.split(/[/\\]/).pop() ?? '';
744
+ if (skipFiles.has(baseName))
745
+ continue;
737
746
  const content = await readFile(file, 'utf8');
738
747
  const lines = content.split('\n');
739
748
  for (let i = 0; i < lines.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mettlecast/domain-cli",
3
- "version": "0.2.67",
3
+ "version": "0.2.68",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -694,4 +694,173 @@ describe('runDoctor', () => {
694
694
  expect(check?.status).toBe('PASS');
695
695
  });
696
696
  });
697
+
698
+ describe('checkConventionEvidence (W1)', () => {
699
+ async function buildConventionProject(): Promise<string> {
700
+ const dir = tempDir;
701
+ await mkdir(join(dir, 'domains', 'test-domain'), { recursive: true });
702
+ await mkdir(join(dir, '.mc'), { recursive: true });
703
+ await mkdir(join(dir, '.husky'), { recursive: true });
704
+ await writeFile(
705
+ join(dir, '.mc', 'scaffold-config.json'),
706
+ JSON.stringify({ domainIds: ['test-domain'] })
707
+ );
708
+ await writeFile(join(dir, '.husky', 'pre-commit'), '#!/bin/sh');
709
+ return dir;
710
+ }
711
+
712
+ it('PASS when K-node IDs are present in domain CLAUDE.md', async () => {
713
+ await buildConventionProject();
714
+ await writeFile(
715
+ join(tempDir, 'domains', 'test-domain', 'CLAUDE.md'),
716
+ [
717
+ '# Test Domain',
718
+ '',
719
+ 'Conventions:',
720
+ '- `K:exit-gate:domain-complete` — exit gate',
721
+ '- `K:convention:tenancy-trio` — tenancy trio',
722
+ '- `K:convention:no-cross-domain-import` — no cross-domain imports',
723
+ '- `K:convention:rls-per-domain-role` — RLS test',
724
+ '- `K:convention:tier-1-foundations` — tier-1 foundations',
725
+ '- `K:convention:flow-vs-subscriber-rule` — flow vs subscriber',
726
+ '',
727
+ ].join('\n')
728
+ );
729
+
730
+ const report = await runDoctor({ projectRoot: tempDir });
731
+ const check = report.checks.find(c => c.name === 'Conventions have evidence');
732
+ expect(check).toBeDefined();
733
+ expect(check?.status).toBe('PASS');
734
+ });
735
+
736
+ it('WARN when 1-2 K-node IDs are missing from domain files', async () => {
737
+ await buildConventionProject();
738
+ // Only add some K-node IDs
739
+ await writeFile(
740
+ join(tempDir, 'domains', 'test-domain', 'CLAUDE.md'),
741
+ [
742
+ '# Test Domain',
743
+ '`K:exit-gate:domain-complete`',
744
+ '`K:convention:tenancy-trio`',
745
+ '`K:convention:no-cross-domain-import`',
746
+ '`K:convention:rls-per-domain-role`',
747
+ // Missing: tier-1-foundations, flow-vs-subscriber-rule
748
+ ].join('\n')
749
+ );
750
+
751
+ const report = await runDoctor({ projectRoot: tempDir });
752
+ const check = report.checks.find(c => c.name === 'Conventions have evidence');
753
+ expect(check).toBeDefined();
754
+ expect(check?.status).toBe('WARN');
755
+ expect(check?.message).toMatch(/tier-1-foundations/);
756
+ });
757
+
758
+ it('FAIL when more than 2 K-node IDs are missing', async () => {
759
+ await buildConventionProject();
760
+ await writeFile(
761
+ join(tempDir, 'domains', 'test-domain', 'CLAUDE.md'),
762
+ '# Test Domain\nNo convention references here.\n'
763
+ );
764
+
765
+ const report = await runDoctor({ projectRoot: tempDir });
766
+ const check = report.checks.find(c => c.name === 'Conventions have evidence');
767
+ expect(check).toBeDefined();
768
+ expect(check?.status).toBe('FAIL');
769
+ expect(check?.message).toMatch(/6\/6 convention/);
770
+ });
771
+
772
+ it('PASS when K-node IDs are found in root CLAUDE.md', async () => {
773
+ await buildConventionProject();
774
+ await writeFile(
775
+ join(tempDir, 'CLAUDE.md'),
776
+ [
777
+ '# Project',
778
+ '`K:exit-gate:domain-complete`',
779
+ '`K:convention:tenancy-trio`',
780
+ '`K:convention:no-cross-domain-import`',
781
+ '`K:convention:rls-per-domain-role`',
782
+ '`K:convention:tier-1-foundations`',
783
+ '`K:convention:flow-vs-subscriber-rule`',
784
+ ].join('\n')
785
+ );
786
+
787
+ const report = await runDoctor({ projectRoot: tempDir });
788
+ const check = report.checks.find(c => c.name === 'Conventions have evidence');
789
+ expect(check).toBeDefined();
790
+ expect(check?.status).toBe('PASS');
791
+ });
792
+ });
793
+
794
+ describe('checkAllHttpClientsUseKy skipfiles', () => {
795
+ async function buildBaseForCliCheck(): Promise<string> {
796
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
797
+ await mkdir(join(tempDir, 'domains'), { recursive: true });
798
+ await mkdir(join(tempDir, '.husky'), { recursive: true });
799
+ await writeFile(
800
+ join(tempDir, '.mc', 'scaffold-config.json'),
801
+ JSON.stringify({ domainIds: [] })
802
+ );
803
+ await writeFile(join(tempDir, '.husky', 'pre-commit'), '#!/bin/sh');
804
+ return tempDir;
805
+ }
806
+
807
+ it('does not flag fetch() inside generate-sdk.ts template literal', async () => {
808
+ await buildBaseForCliCheck();
809
+ const cliDir = join(tempDir, 'packages', 'domain-cli', 'src', 'commands');
810
+ await mkdir(cliDir, { recursive: true });
811
+ await writeFile(
812
+ join(cliDir, 'generate-sdk.ts'),
813
+ `export function genTemplate() {
814
+ return \`
815
+ const res = await fetch('/api/endpoint', { method: 'GET' });
816
+ if (!res.ok) throw new Error();
817
+ return res.json();
818
+ \`;
819
+ }`
820
+ );
821
+
822
+ const report = await runDoctor({ projectRoot: tempDir });
823
+ const check = report.checks.find(c => c.name === 'All HTTP clients use ky');
824
+ expect(check?.status).toBe('PASS');
825
+ });
826
+
827
+ it('does not flag fetch() in doctor.ts JSDoc comments and message strings', async () => {
828
+ await buildBaseForCliCheck();
829
+ const cliDir = join(tempDir, 'packages', 'domain-cli', 'src', 'commands');
830
+ await mkdir(cliDir, { recursive: true });
831
+ await writeFile(
832
+ join(cliDir, 'doctor.ts'),
833
+ [
834
+ '/**',
835
+ ' * Check W5-1: All HTTP clients use ky (no raw fetch()).',
836
+ ' * Detects raw `fetch(` calls and FAILs.',
837
+ ' * Allows `ctx.fetch(` and `ky.fetch(`.',
838
+ ' */',
839
+ 'export function check() {',
840
+ ' return { message: "No raw fetch() calls found" };',
841
+ '}',
842
+ '',
843
+ ].join('\n')
844
+ );
845
+
846
+ const report = await runDoctor({ projectRoot: tempDir });
847
+ const check = report.checks.find(c => c.name === 'All HTTP clients use ky');
848
+ expect(check?.status).toBe('PASS');
849
+ });
850
+
851
+ it('still flags raw fetch() in non-skipped CLI files', async () => {
852
+ await buildBaseForCliCheck();
853
+ const cliDir = join(tempDir, 'packages', 'domain-cli', 'src', 'commands');
854
+ await mkdir(cliDir, { recursive: true });
855
+ await writeFile(
856
+ join(cliDir, 'some-command.ts'),
857
+ 'export const cmd = () => fetch("https://api.example.com");'
858
+ );
859
+
860
+ const report = await runDoctor({ projectRoot: tempDir });
861
+ const check = report.checks.find(c => c.name === 'All HTTP clients use ky');
862
+ expect(check?.status).toBe('FAIL');
863
+ expect(check?.message).toMatch(/some-command\.ts/);
864
+ });
865
+ });
697
866
  });
@@ -822,6 +822,13 @@ async function checkAllHttpClientsUseKy(projectRoot: string): Promise<DoctorChec
822
822
  { dir: join(projectRoot, 'packages', 'domain-cli', 'src', 'commands'), label: 'CLI commands' },
823
823
  ];
824
824
 
825
+ // Files to skip — these contain raw fetch() in comments, docs, or template
826
+ // literals that generate output code, not actual HTTP calls.
827
+ const skipFiles = new Set([
828
+ 'generate-sdk.ts', // template literal generating SDK client code
829
+ 'doctor.ts', // JSDoc block comments and fix-message strings self-flag
830
+ ]);
831
+
825
832
  const violations: string[] = [];
826
833
  let totalFiles = 0;
827
834
 
@@ -831,6 +838,8 @@ async function checkAllHttpClientsUseKy(projectRoot: string): Promise<DoctorChec
831
838
  totalFiles += files.length;
832
839
 
833
840
  for (const file of files) {
841
+ const baseName = file.split(/[/\\]/).pop() ?? '';
842
+ if (skipFiles.has(baseName)) continue;
834
843
  const content = await readFile(file, 'utf8');
835
844
  const lines = content.split('\n');
836
845
  for (let i = 0; i < lines.length; i++) {