Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions __tests__/classifier/ClassificationService.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { ClassificationService, ClassificationContext } from '../../src/classifier/ClassificationService';
import { ClassificationService, ClassificationContext } from '../../src/ui/ClassificationService';
import type { App, TFile, MetadataCache, Vault } from 'obsidian';
import type { FrontmatterField, ProviderConfig, OAuthTokens } from '../../src/types';
import { processAPIRequest } from '../../src/provider';
import { insertToFrontMatter, getFieldValues } from '../../src/lib/frontmatter';
import { getPromptTemplate } from '../../src/provider/prompt';
import { processAPIRequest } from '../../src/ui/provider-api';
import { insertToFrontMatter, getFieldValues } from '../../src/ui/frontmatter';
import { getPromptTemplate } from '../../src/domain/prompt';

// Mock dependencies
vi.mock('../../src/provider', () => ({
vi.mock('../../src/ui/provider-api', () => ({
processAPIRequest: vi.fn(),
}));

vi.mock('../../src/provider/prompt', () => ({
vi.mock('../../src/domain/prompt', () => ({
DEFAULT_SYSTEM_ROLE: 'Test system role',
getPromptTemplate: vi.fn().mockReturnValue('Test prompt'),
}));

vi.mock('../../src/lib/frontmatter', () => ({
vi.mock('../../src/ui/frontmatter', () => ({
getContentWithoutFrontmatter: vi.fn().mockReturnValue('Test content'),
getFieldValues: vi.fn().mockReturnValue(['tag1', 'tag2']),
insertToFrontMatter: vi.fn().mockResolvedValue(undefined),
}));

vi.mock('../../src/settings/components/Notice', () => ({
vi.mock('../../src/ui/settings/components/Notice', () => ({
Notice: {
error: vi.fn(),
success: vi.fn(),
Expand Down
4 changes: 2 additions & 2 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import AutoClassifierPlugin from 'main';
import { App, TFile, createMockTFile } from 'obsidian';
import { DEFAULT_SETTINGS, DEFAULT_FRONTMATTER_SETTING } from '../src/constants';
import type { ProviderConfig, FrontmatterField, OAuthTokens } from 'types';
import { DEFAULT_SETTINGS, DEFAULT_FRONTMATTER_SETTING } from '../src/domain/constants';
import type { ProviderConfig, FrontmatterField, OAuthTokens } from '../src/types/index';
import { Notice as SettingsNotice } from 'settings/components/Notice';
import { processAPIRequest } from 'provider';
import { insertToFrontMatter, getFieldValues } from 'lib/frontmatter';
Expand Down
14 changes: 7 additions & 7 deletions __tests__/provider/auth/oauth.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { Platform, requestUrl } from 'obsidian';
import { CodexOAuth } from '../../../src/provider/auth/oauth';
import type { OAuthTokens } from '../../../src/provider/auth/types';
import { OAuthCallbackServer } from '../../../src/provider/auth/oauth-server';
import { createTokensFromResponse, isTokenExpired } from '../../../src/provider/auth/token-manager';
import { CodexOAuth } from '../../../src/ui/auth/oauth';
import type { OAuthTokens } from '../../../src/types/auth';
import { OAuthCallbackServer } from '../../../src/domain/auth/oauth-server';
import { createTokensFromResponse, isTokenExpired } from '../../../src/domain/auth/token-manager';
import type { Mock } from 'vitest';

// Mock obsidian
Expand All @@ -15,7 +15,7 @@ vi.mock('obsidian', () => ({
}));

// Mock the oauth-server - use a class-like constructor mock
vi.mock('../../../src/provider/auth/oauth-server', () => {
vi.mock('../../../src/domain/auth/oauth-server', () => {
const MockServer = vi.fn(function (this: any) {
this.waitForCallback = vi.fn();
this.stop = vi.fn();
Expand All @@ -24,7 +24,7 @@ vi.mock('../../../src/provider/auth/oauth-server', () => {
});

// Mock pkce
vi.mock('../../../src/provider/auth/pkce', () => ({
vi.mock('../../../src/domain/auth/pkce', () => ({
generatePKCEChallenge: vi.fn().mockResolvedValue({
codeVerifier: 'test-verifier',
codeChallenge: 'test-challenge',
Expand All @@ -33,7 +33,7 @@ vi.mock('../../../src/provider/auth/pkce', () => ({
}));

// Mock token-manager
vi.mock('../../../src/provider/auth/token-manager', () => ({
vi.mock('../../../src/domain/auth/token-manager', () => ({
createTokensFromResponse: vi.fn().mockReturnValue({
accessToken: 'new-access-token',
refreshToken: 'new-refresh-token',
Expand Down
2 changes: 1 addition & 1 deletion __tests__/provider/auth/pkce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
generateCodeChallenge,
generateState,
generatePKCEChallenge,
} from '../../../src/provider/auth/pkce';
} from '../../../src/domain/auth/pkce';

describe('pkce', () => {
describe('generateCodeVerifier', () => {
Expand Down
4 changes: 2 additions & 2 deletions __tests__/provider/auth/token-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
createTokensFromResponse,
getTokenRemainingTime,
formatTokenExpiry,
} from '../../../src/provider/auth/token-manager';
import type { OAuthTokens, TokenResponse } from '../../../src/provider/auth/types';
} from '../../../src/domain/auth/token-manager';
import type { OAuthTokens, TokenResponse } from '../../../src/types/auth';

describe('token-manager', () => {
// Mock Date.now for consistent testing
Expand Down
12 changes: 6 additions & 6 deletions __tests__/settings/ProviderSection.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Mock } from 'vitest';
import { ProviderSection } from '../../src/settings/ProviderSection';
import { ProviderSection } from '../../src/ui/settings/ProviderSection';
import type { App } from 'obsidian';
import type AutoClassifierPlugin from '../../src/main';
import type { ProviderConfig, OAuthTokens } from '../../src/types';
import { formatTokenExpiry, isTokenExpired } from '../../src/provider/auth';
import { Setting } from '../../src/settings/components/Setting';
import { formatTokenExpiry, isTokenExpired } from '../../src/ui/auth';
import { Setting } from '../../src/ui/settings/components/Setting';

// Mock the provider/auth module
vi.mock('../../src/provider/auth', () => ({
vi.mock('../../src/ui/auth', () => ({
formatTokenExpiry: vi.fn((tokens) => {
const remaining = tokens.expiresAt - Math.floor(Date.now() / 1000);
if (remaining <= 0) return 'Expired';
Expand All @@ -22,14 +22,14 @@ vi.mock('../../src/provider/auth', () => ({
}));

// Mock the ProviderModal
vi.mock('../../src/settings/modals/ProviderModal', () => ({
vi.mock('../../src/ui/settings/modals/ProviderModal', () => ({
ProviderModal: vi.fn().mockImplementation(() => ({
open: vi.fn(),
})),
}));

// Mock Setting component
vi.mock('../../src/settings/components/Setting', () => ({
vi.mock('../../src/ui/settings/components/Setting', () => ({
Setting: {
create: vi.fn(),
},
Expand Down
2 changes: 1 addition & 1 deletion __tests__/settings/TagSection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ vi.mock('settings/modals/FrontmatterEditorModal', () => ({
import type { Mock } from 'vitest';
import type { FrontmatterField } from 'types';
import { Tag } from 'settings/TagSection';
import { DEFAULT_TAG_SETTING } from '../../src/constants';
import { DEFAULT_TAG_SETTING } from '../../src/domain/constants';

interface MockPlugin {
app: { vault: { getMarkdownFiles: Mock } };
Expand Down
12 changes: 12 additions & 0 deletions eslint.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ export const baseConfig = tseslint.config(
'no-console': 'error',
},
},
{
files: ['src/domain/**/*.ts', 'src/types/**/*.ts', 'src/utils/**/*.ts'],
ignores: ['**/*.d.ts'],
rules: {
'no-restricted-imports': ['error', {
patterns: [{
group: ['obsidian', 'obsidian/*'],
message: 'domain/, types/, and utils/ layers must not import from obsidian. Move this code to ui/ or inject the dependency.',
}],
}],
},
},
{
files: ['scripts/**/*.{js,mjs}', 'tooling/**/*.{js,mjs}', 'esbuild.config.mjs', 'version-bump.mjs'],
languageOptions: {
Expand Down
51 changes: 28 additions & 23 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,51 +20,56 @@ export default [
unicorn,
},
settings: {
// Module boundary definitions
// Structure: provider/ | classifier/ | settings/ | lib/
// Module boundary definitions — 4-layer architecture
// main → ui → domain → utils/types
'boundaries/elements': [
{
type: 'provider',
pattern: 'src/provider/**',
type: 'main',
pattern: 'src/main.ts',
mode: 'file',
},
{
type: 'ui',
pattern: 'src/ui/**',
mode: 'folder',
},
{
type: 'classifier',
pattern: 'src/classifier/**',
type: 'domain',
pattern: 'src/domain/**',
mode: 'folder',
},
{
type: 'settings',
pattern: 'src/settings/**',
type: 'utils',
pattern: 'src/utils/**',
mode: 'folder',
},
{
type: 'lib',
pattern: 'src/lib/**',
type: 'types',
pattern: 'src/types/**',
mode: 'folder',
},
{
type: 'main',
pattern: 'src/main.ts',
mode: 'file',
type: 'shared',
pattern: 'src/shared/**',
mode: 'folder',
},
],
// Dependency rules between modules
// Dependency rules between layers
'boundaries/rules': [
{
from: 'settings',
disallow: ['provider'],
message: 'settings cannot import provider directly. Use classifier instead.',
from: 'domain',
disallow: ['ui'],
message: 'domain layer must not import from ui layer.',
},
{
from: 'provider',
disallow: ['settings', 'classifier'],
message: 'provider is pure API layer. No UI/business logic dependency.',
from: 'utils',
disallow: ['ui', 'domain'],
message: 'utils layer must not import from ui or domain.',
},
{
from: 'lib',
disallow: ['provider', 'classifier', 'settings'],
message: 'lib is pure utility. No domain module dependency.',
from: 'types',
disallow: ['ui', 'domain', 'utils'],
message: 'types layer must not import from other layers.',
},
],
},
Expand Down
Empty file added src/domain/.gitkeep
Empty file.
16 changes: 16 additions & 0 deletions src/domain/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Domain auth module exports — pure, no obsidian imports
export { CODEX_OAUTH } from './oauth-constants';
export {
isTokenExpired,
formatTokenExpiry,
getTokenRemainingTime,
parseJwtClaims,
extractAccountId,
createTokensFromResponse,
} from './token-manager';
export type {
OAuthTokens,
PKCEChallenge,
TokenResponse,
OAuthCallbackResponse,
} from '../../types/auth';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CODEX_OAUTH } from './oauth-constants';
import type { OAuthCallbackResponse } from './types';
import type { OAuthCallbackResponse } from '../../types/auth';
import { macLogger } from '../../shared/mac-logger';

// Types for Node.js http module (imported dynamically)
Expand Down
2 changes: 1 addition & 1 deletion src/provider/auth/pkce.ts → src/domain/auth/pkce.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PKCEChallenge } from './types';
import type { PKCEChallenge } from '../../types/auth';

/**
* Generate a cryptographically secure random string for PKCE code verifier
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CODEX_OAUTH } from './oauth-constants';
import type { OAuthTokens, TokenResponse } from './types';
import type { OAuthTokens, TokenResponse } from '../../types/auth';

/**
* Parse JWT claims without verification (for extracting account_id)
Expand Down
10 changes: 5 additions & 5 deletions src/constants.ts → src/domain/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// Consolidated Constants - All constants in one place
// ==========================================

import type { FrontmatterField, LinkType, ProviderConfig, AutoClassifierSettings } from './types';
import type { NoticeCatalog } from './shared/plugin-notices';
import type { FrontmatterField, LinkType, ProviderConfig, AutoClassifierSettings } from '../types';
import type { NoticeCatalog } from '../shared/plugin-notices';

// ==========================================
// Common Constants (from api/constants.ts)
// Common Constants
// ==========================================

export const COMMON_CONSTANTS = {
Expand Down Expand Up @@ -152,10 +152,10 @@ export const OLLAMA_STRUCTURE_OUTPUT = {
};

// ==========================================
// Default Settings (from utils/constants.ts)
// Default Settings
// ==========================================

// Prompt template - originally in api/prompt.ts but used by constants
// Prompt template
export const DEFAULT_TASK_TEMPLATE = `<task>
Classify the following content using the provided reference categories.
</task>
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/provider/prompt.ts → src/domain/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DEFAULT_TASK_TEMPLATE } from '../constants';
import { sanitizePromptInput, sanitizeReferenceValues } from '../lib/sanitizer';
import { DEFAULT_TASK_TEMPLATE } from './constants';
import { sanitizePromptInput, sanitizeReferenceValues } from '../utils/sanitizer';

// Constants for system behaviour
export const DEFAULT_SYSTEM_ROLE = `You are a JSON classification assistant. Respond only with a valid JSON object adhering to the specified schema.`;
Expand Down
12 changes: 6 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { TFile } from 'obsidian';
import { Plugin } from 'obsidian';
import { CodexOAuth, isTokenExpired } from './provider/auth';
import { ClassificationService, CommandService } from './classifier';
import { DEFAULT_FRONTMATTER_SETTING, DEFAULT_SETTINGS, NOTICE_CATALOG } from './constants';
import type { AutoClassifierSettings } from './settings';
import { AutoClassifierSettingTab } from './settings';
import type { FrontmatterField, ProviderConfig } from './types';
import { CodexOAuth, isTokenExpired } from './ui/auth';
import { ClassificationService } from './ui/ClassificationService';
import { CommandService } from './ui/CommandService';
import { DEFAULT_FRONTMATTER_SETTING, DEFAULT_SETTINGS, NOTICE_CATALOG } from './domain/constants';
import type { AutoClassifierSettings, FrontmatterField, ProviderConfig } from './types';
import { AutoClassifierSettingTab } from './ui/settings';
import { PluginLogger } from './shared/plugin-logger';
import { PluginNotices } from './shared/plugin-notices';
import { migrateSettings } from './shared/settings-migration';
Expand All @@ -18,7 +18,7 @@
private classificationService: ClassificationService | null = null;
private commandService: CommandService | null = null;

async onload() {

Check warning on line 21 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function

Check warning on line 21 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function
this.logger = new PluginLogger('MAC');

await this.loadSettings();
Expand Down Expand Up @@ -74,7 +74,7 @@
}
}

setupCommand() {

Check warning on line 77 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function

Check warning on line 77 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function
this.commandService = new CommandService(this, {
processFrontmatter: (id) => this.processFrontmatter(id),
processAllFrontmatter: () => this.processAllFrontmatter(),
Expand All @@ -82,7 +82,7 @@
this.commandService.setupCommands(this.settings.frontmatter);
}

registerCommand(name: string, callback: () => Promise<void>) {

Check warning on line 85 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function

Check warning on line 85 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function
this.addCommand({
id: `fetch-frontmatter-${name}`,
name: `Fetch frontmatter: ${name}`,
Expand Down Expand Up @@ -138,7 +138,7 @@
await this.classificationService.classify(file, frontmatter);
}

async loadSettings() {

Check warning on line 141 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function

Check warning on line 141 in src/main.ts

View workflow job for this annotation

GitHub Actions / build

Missing return type on function
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());

// Migrate old frontmatter settings that may be missing count
Expand Down
11 changes: 0 additions & 11 deletions src/provider/auth/index.ts

This file was deleted.

Loading
Loading