mirror of
https://github.com/certd/certd.git
synced 2026-08-02 11:04:49 +08:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0e0bb51ce | ||
|
|
1dc5a19c45 | ||
|
|
04aa9041e8 | ||
|
|
0d86c84b28 | ||
|
|
6605669113 | ||
|
|
4b8747b5da | ||
|
|
2ba4132c50 | ||
|
|
49dc2796cd | ||
|
|
1e07d69932 | ||
|
|
aabf73a736 | ||
|
|
c9be2293ab |
@@ -0,0 +1,133 @@
|
|||||||
|
```markdown
|
||||||
|
# certd Development Patterns
|
||||||
|
|
||||||
|
> Auto-generated skill from repository analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This skill teaches the core development patterns and workflows for the `certd` TypeScript codebase. It covers coding conventions, file organization, commit patterns, and detailed step-by-step instructions for extending user preferences—a common feature workflow. The repository is structured for modularity, with clear separation between UI and backend logic, and emphasizes maintainable, convention-driven development.
|
||||||
|
|
||||||
|
## Coding Conventions
|
||||||
|
|
||||||
|
### File Naming
|
||||||
|
|
||||||
|
- **CamelCase** is used for file names.
|
||||||
|
- Example: `userPreferences.ts`, `preferencesDrawer.vue`
|
||||||
|
|
||||||
|
### Import Style
|
||||||
|
|
||||||
|
- **Absolute imports** are preferred.
|
||||||
|
- Example:
|
||||||
|
```typescript
|
||||||
|
import { getUserPreferences } from 'packages/ui/certd-client/src/vben/layouts/widgets/preferences/api';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export Style
|
||||||
|
|
||||||
|
- **Named exports** are used throughout the codebase.
|
||||||
|
- Example:
|
||||||
|
```typescript
|
||||||
|
export function getUserPreferences() { ... }
|
||||||
|
export const PREFERENCE_KEYS = [ ... ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit Patterns
|
||||||
|
|
||||||
|
- **Conventional commits** are used, with the `feat` prefix for new features.
|
||||||
|
- Example:
|
||||||
|
```
|
||||||
|
feat: add account sync to preferences
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
### Extend Preferences Feature
|
||||||
|
|
||||||
|
**Trigger:** When someone wants to add or enhance a user preference feature (e.g., import/export, sync to account).
|
||||||
|
**Command:** `/extend-preferences`
|
||||||
|
|
||||||
|
Follow these steps to extend user preferences functionality:
|
||||||
|
|
||||||
|
1. **Update localization files**
|
||||||
|
Add or modify strings in:
|
||||||
|
- `packages/ui/certd-client/src/locales/langs/en-US/preferences.ts`
|
||||||
|
- `packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts`
|
||||||
|
```typescript
|
||||||
|
// en-US/preferences.ts
|
||||||
|
export default {
|
||||||
|
sync: "Sync Preferences",
|
||||||
|
import: "Import Preferences",
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Modify or add Vue components for preferences UI**
|
||||||
|
Update or create components such as:
|
||||||
|
- `preferences-drawer.vue`
|
||||||
|
- `account-sync.ts`
|
||||||
|
```vue
|
||||||
|
<!-- preferences-drawer.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button @click="syncPreferences">{{ $t('preferences.sync') }}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Update or add supporting icon definitions**
|
||||||
|
Edit:
|
||||||
|
- `packages/ui/certd-client/src/vben/icons/lucide.ts`
|
||||||
|
```typescript
|
||||||
|
export const SyncIcon = { /* icon definition */ };
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Implement or update store logic for settings**
|
||||||
|
Update:
|
||||||
|
- `packages/ui/certd-client/src/store/settings/index.tsx`
|
||||||
|
```typescript
|
||||||
|
export function syncPreferencesToAccount() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Add or update backend API/controller for user preferences**
|
||||||
|
Edit or add:
|
||||||
|
- `packages/ui/certd-client/src/vben/layouts/widgets/preferences/api.ts`
|
||||||
|
- `packages/ui/certd-server/src/controller/user/mine/user-preferences.ts`
|
||||||
|
```typescript
|
||||||
|
// user-preferences.ts
|
||||||
|
export async function updateUserPreferences(req, res) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Write or update backend tests for new preference logic**
|
||||||
|
Add or update:
|
||||||
|
- `packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts`
|
||||||
|
```typescript
|
||||||
|
test('should sync preferences', async () => { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Update backend models if necessary**
|
||||||
|
Edit:
|
||||||
|
- `packages/ui/certd-server/src/modules/mine/service/models.ts`
|
||||||
|
```typescript
|
||||||
|
export interface UserPreferences { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Patterns
|
||||||
|
|
||||||
|
- **Test files** follow the `*.test.*` naming convention.
|
||||||
|
- Example: `user-preferences.test.ts`
|
||||||
|
- **Testing framework** is not explicitly detected, but tests are written in TypeScript and likely use a standard Node.js testing library (e.g., Jest or Mocha).
|
||||||
|
- **Test Example:**
|
||||||
|
```typescript
|
||||||
|
test('should update preferences', async () => {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|----------------------|--------------------------------------------------------------|
|
||||||
|
| /extend-preferences | Guide to extend or enhance user preference functionality |
|
||||||
|
```
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Certd"
|
||||||
|
short_description: "Repo-specific patterns and workflows for certd"
|
||||||
|
default_prompt: "Use the certd repo skill to follow existing architecture, testing, and workflow conventions."
|
||||||
|
policy:
|
||||||
|
allow_implicit_invocation: true
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
{
|
||||||
|
"version": "1.3",
|
||||||
|
"schemaVersion": "1.0",
|
||||||
|
"generatedBy": "ecc-tools",
|
||||||
|
"generatedAt": "2026-07-30T02:07:44.046Z",
|
||||||
|
"repo": "https://github.com/certd/certd",
|
||||||
|
"referenceSetReadiness": {
|
||||||
|
"score": 0,
|
||||||
|
"present": 0,
|
||||||
|
"total": 7,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "deep-analyzer-corpus",
|
||||||
|
"label": "Deep analyzer corpus",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Add analyzer fixture, golden, benchmark, or reference-set files that can catch analyzer regressions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rag-evaluator",
|
||||||
|
"label": "RAG/evaluator comparison",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Add retrieval or evaluator reference-set comparison fixtures with expected ranking behavior."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pr-salvage",
|
||||||
|
"label": "PR salvage/review corpus",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Add stale-PR, review-thread, reopen-flow, or salvage reference cases for queue cleanup automation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "discussion-triage",
|
||||||
|
"label": "Discussion triage corpus",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Add public discussion triage fixtures, golden cases, or reference sets for informational, answered, and no-response classifications."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "harness-compatibility",
|
||||||
|
"label": "Harness compatibility",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Add cross-harness, adapter-compliance, or harness-audit evidence for Claude, Codex, OpenCode, Zed, dmux, and agent surfaces."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "security-evidence",
|
||||||
|
"label": "Security evidence",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Attach security evidence such as SBOMs, SARIF, audit reports, or AgentShield evidence packs."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ci-failure-mode",
|
||||||
|
"label": "CI failure-mode evidence",
|
||||||
|
"status": "missing",
|
||||||
|
"evidence": [],
|
||||||
|
"recommendation": "Add captured CI failure logs, dry-run fixtures, or troubleshooting docs for common workflow failure modes."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"requested": "core",
|
||||||
|
"recommended": "core",
|
||||||
|
"effective": "core",
|
||||||
|
"requestedAlias": "core",
|
||||||
|
"recommendedAlias": "core",
|
||||||
|
"effectiveAlias": "core"
|
||||||
|
},
|
||||||
|
"requestedProfile": "core",
|
||||||
|
"profile": "core",
|
||||||
|
"recommendedProfile": "core",
|
||||||
|
"effectiveProfile": "core",
|
||||||
|
"tier": "free",
|
||||||
|
"requestedComponents": [
|
||||||
|
"repo-baseline"
|
||||||
|
],
|
||||||
|
"selectedComponents": [
|
||||||
|
"repo-baseline"
|
||||||
|
],
|
||||||
|
"requestedAddComponents": [],
|
||||||
|
"requestedRemoveComponents": [],
|
||||||
|
"blockedRemovalComponents": [],
|
||||||
|
"tierFilteredComponents": [],
|
||||||
|
"requestedRootPackages": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"selectedRootPackages": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"requestedPackages": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"requestedAddPackages": [],
|
||||||
|
"requestedRemovePackages": [],
|
||||||
|
"selectedPackages": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"packages": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"blockedRemovalPackages": [],
|
||||||
|
"tierFilteredRootPackages": [],
|
||||||
|
"tierFilteredPackages": [],
|
||||||
|
"conflictingPackages": [],
|
||||||
|
"dependencyGraph": {
|
||||||
|
"runtime-core": []
|
||||||
|
},
|
||||||
|
"resolutionOrder": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"requestedModules": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"selectedModules": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"modules": [
|
||||||
|
"runtime-core"
|
||||||
|
],
|
||||||
|
"managedFiles": [
|
||||||
|
".claude/skills/certd/SKILL.md",
|
||||||
|
".agents/skills/certd/SKILL.md",
|
||||||
|
".agents/skills/certd/agents/openai.yaml",
|
||||||
|
".claude/identity.json",
|
||||||
|
".codex/config.toml",
|
||||||
|
".codex/AGENTS.md",
|
||||||
|
".codex/agents/explorer.toml",
|
||||||
|
".codex/agents/reviewer.toml",
|
||||||
|
".codex/agents/docs-researcher.toml",
|
||||||
|
".claude/homunculus/instincts/inherited/certd-instincts.yaml"
|
||||||
|
],
|
||||||
|
"packageFiles": {
|
||||||
|
"runtime-core": [
|
||||||
|
".claude/skills/certd/SKILL.md",
|
||||||
|
".agents/skills/certd/SKILL.md",
|
||||||
|
".agents/skills/certd/agents/openai.yaml",
|
||||||
|
".claude/identity.json",
|
||||||
|
".codex/config.toml",
|
||||||
|
".codex/AGENTS.md",
|
||||||
|
".codex/agents/explorer.toml",
|
||||||
|
".codex/agents/reviewer.toml",
|
||||||
|
".codex/agents/docs-researcher.toml",
|
||||||
|
".claude/homunculus/instincts/inherited/certd-instincts.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moduleFiles": {
|
||||||
|
"runtime-core": [
|
||||||
|
".claude/skills/certd/SKILL.md",
|
||||||
|
".agents/skills/certd/SKILL.md",
|
||||||
|
".agents/skills/certd/agents/openai.yaml",
|
||||||
|
".claude/identity.json",
|
||||||
|
".codex/config.toml",
|
||||||
|
".codex/AGENTS.md",
|
||||||
|
".codex/agents/explorer.toml",
|
||||||
|
".codex/agents/reviewer.toml",
|
||||||
|
".codex/agents/docs-researcher.toml",
|
||||||
|
".claude/homunculus/instincts/inherited/certd-instincts.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".claude/skills/certd/SKILL.md",
|
||||||
|
"description": "Repository-specific Claude Code skill generated from git history."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".agents/skills/certd/SKILL.md",
|
||||||
|
"description": "Codex-facing copy of the generated repository skill."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".agents/skills/certd/agents/openai.yaml",
|
||||||
|
"description": "Codex skill metadata so the repo skill appears cleanly in the skill interface."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".claude/identity.json",
|
||||||
|
"description": "Suggested identity.json baseline derived from repository conventions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".codex/config.toml",
|
||||||
|
"description": "Repo-local Codex MCP and multi-agent baseline aligned with ECC defaults."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".codex/AGENTS.md",
|
||||||
|
"description": "Codex usage guide that points at the generated repo skill and workflow bundle."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".codex/agents/explorer.toml",
|
||||||
|
"description": "Read-only explorer role config for Codex multi-agent work."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".codex/agents/reviewer.toml",
|
||||||
|
"description": "Read-only reviewer role config focused on correctness and security."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".codex/agents/docs-researcher.toml",
|
||||||
|
"description": "Read-only docs researcher role config for API verification."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleId": "runtime-core",
|
||||||
|
"path": ".claude/homunculus/instincts/inherited/certd-instincts.yaml",
|
||||||
|
"description": "Continuous-learning instincts derived from repository patterns."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"workflows": [],
|
||||||
|
"adapters": {
|
||||||
|
"claudeCode": {
|
||||||
|
"skillPath": ".claude/skills/certd/SKILL.md",
|
||||||
|
"identityPath": ".claude/identity.json",
|
||||||
|
"commandPaths": []
|
||||||
|
},
|
||||||
|
"codex": {
|
||||||
|
"configPath": ".codex/config.toml",
|
||||||
|
"agentsGuidePath": ".codex/AGENTS.md",
|
||||||
|
"skillPath": ".agents/skills/certd/SKILL.md"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,486 @@
|
|||||||
|
# Instincts generated from https://github.com/certd/certd
|
||||||
|
# Generated: 2026-07-30T02:08:05.402Z
|
||||||
|
# Version: 2.0
|
||||||
|
# NOTE: This file supplements (does not replace) any existing curated instincts.
|
||||||
|
# High-confidence manually curated instincts should be preserved alongside these.
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-commit-conventional
|
||||||
|
trigger: "when writing a commit message"
|
||||||
|
confidence: 0.85
|
||||||
|
domain: git
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: https://github.com/certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Commit Conventional
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use conventional commit format with prefixes: feat
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- 2 commits analyzed
|
||||||
|
- Detected conventional commit pattern
|
||||||
|
- Examples: feat: 偏好设置支持从剪切板导入, feat: 偏好设置支持保存到账号并在登录后自动同步
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-commit-length
|
||||||
|
trigger: "when writing a commit message"
|
||||||
|
confidence: 0.6
|
||||||
|
domain: git
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: https://github.com/certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Commit Length
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Keep commit messages concise (~22 characters)
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Average commit message length: 22 chars
|
||||||
|
- Based on 2 commits
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-naming-files
|
||||||
|
trigger: "when creating a new file"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: https://github.com/certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Naming Files
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use camelCase naming convention
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Analyzed file naming patterns in repository
|
||||||
|
- Dominant pattern: camelCase
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-export-style
|
||||||
|
trigger: "when exporting from a module"
|
||||||
|
confidence: 0.7
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: https://github.com/certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Export Style
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Prefer named exports
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Export pattern analysis
|
||||||
|
- Dominant style: named
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-arch-feature-based
|
||||||
|
trigger: "when adding a new feature"
|
||||||
|
confidence: 0.85
|
||||||
|
domain: architecture
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: https://github.com/certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Arch Feature Based
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Create a new folder in src/features/ with all related code colocated
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Feature-based module organization detected
|
||||||
|
- Structure: src/features/[feature-name]/
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-workflow-extend-preferences-feature
|
||||||
|
trigger: "when doing extend preferences feature"
|
||||||
|
confidence: 0.6
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: https://github.com/certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Workflow Extend Preferences Feature
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Follow the extend-preferences-feature workflow:
|
||||||
|
1. Update localization files for new preference-related strings
|
||||||
|
2. Modify or add Vue components for preferences UI
|
||||||
|
3. Update or add supporting icon definitions
|
||||||
|
4. Implement or update store logic for settings
|
||||||
|
5. Add or update backend API/controller for user preferences
|
||||||
|
6. Write or update backend tests for new preference logic
|
||||||
|
7. Update backend models if necessary
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow detected from commit patterns
|
||||||
|
- Frequency: ~2x per month
|
||||||
|
- Files: packages/ui/certd-client/src/locales/langs/en-US/preferences.ts, packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts, packages/ui/certd-client/src/vben/layouts/widgets/preferences/preferences-drawer.vue
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-file-naming
|
||||||
|
trigger: "When creating a new file in the codebase"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct File Naming
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Name the file using camelCase
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in codeStyle.namingConventions.files
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-function-naming
|
||||||
|
trigger: "When defining a new function"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Function Naming
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use camelCase for function names
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in codeStyle.namingConventions.functions
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-class-naming
|
||||||
|
trigger: "When defining a new class"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Class Naming
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use PascalCase for class names
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in codeStyle.namingConventions.classes
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-constant-naming
|
||||||
|
trigger: "When declaring a constant"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Constant Naming
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use SCREAMING_SNAKE_CASE for constant names
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in codeStyle.namingConventions.constants
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-import-style
|
||||||
|
trigger: "When importing modules"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Import Style
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use absolute import paths
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in codeStyle.importStyle
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-export-style
|
||||||
|
trigger: "When exporting from a module"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Export Style
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use named exports
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in codeStyle.exportStyle
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-try-catch
|
||||||
|
trigger: "When handling errors in code"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: code-style
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Try Catch
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use try-catch blocks for error handling
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in errorHandling.style
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-test-location
|
||||||
|
trigger: "When adding or updating tests"
|
||||||
|
confidence: 0.7
|
||||||
|
domain: testing
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Test Location
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Place tests in both source and test-specific folders as appropriate (mixed location)
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in architecture.folderStructure.testLocation
|
||||||
|
- Seen in files like packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-backend-test-pattern
|
||||||
|
trigger: "When adding backend logic for user preferences"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: testing
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Backend Test Pattern
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Create or update a corresponding .test.ts file in the same directory
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Seen in packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-conventional-commits
|
||||||
|
trigger: "When writing a commit message"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: git
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Conventional Commits
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Use the conventional commit format with a type prefix (e.g., feat: ...)
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in commits.type
|
||||||
|
- Examples: feat: 偏好设置支持从剪切板导入
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-commit-length
|
||||||
|
trigger: "When writing a commit message"
|
||||||
|
confidence: 0.7
|
||||||
|
domain: git
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Commit Length
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Keep the commit message concise, around 22 characters on average
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Pattern in commits.averageLength
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-localization
|
||||||
|
trigger: "When extending or adding a user preference feature"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Localization
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Update localization files for new preference-related strings
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- Files: packages/ui/certd-client/src/locales/langs/en-US/preferences.ts, packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-ui
|
||||||
|
trigger: "When extending or adding a user preference feature"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Ui
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Modify or add Vue components for the preferences UI
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- Files: packages/ui/certd-client/src/vben/layouts/widgets/preferences/preferences-drawer.vue
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-icons
|
||||||
|
trigger: "When extending or adding a user preference feature"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Icons
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Update or add supporting icon definitions
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- File: packages/ui/certd-client/src/vben/icons/lucide.ts
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-store
|
||||||
|
trigger: "When extending or adding a user preference feature"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Store
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Implement or update store logic for settings
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- File: packages/ui/certd-client/src/store/settings/index.tsx
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-backend-api
|
||||||
|
trigger: "When extending or adding a user preference feature"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Backend Api
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Add or update backend API/controller for user preferences
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- Files: packages/ui/certd-server/src/controller/user/mine/user-preferences.ts, packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-backend-test
|
||||||
|
trigger: "When extending or adding a user preference feature"
|
||||||
|
confidence: 0.9
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Backend Test
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Write or update backend tests for new preference logic
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- File: packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts
|
||||||
|
|
||||||
|
---
|
||||||
|
id: certd-instinct-extend-preferences-models
|
||||||
|
trigger: "When extending or adding a user preference feature and new data is needed"
|
||||||
|
confidence: 0.8
|
||||||
|
domain: workflow
|
||||||
|
source: repo-analysis
|
||||||
|
source_repo: certd/certd
|
||||||
|
---
|
||||||
|
|
||||||
|
# Certd Instinct Extend Preferences Models
|
||||||
|
|
||||||
|
## Action
|
||||||
|
|
||||||
|
Update backend models if necessary
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Workflow: extend-preferences-feature
|
||||||
|
- File: packages/ui/certd-server/src/modules/mine/service/models.ts
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0",
|
||||||
|
"technicalLevel": "technical",
|
||||||
|
"preferredStyle": {
|
||||||
|
"verbosity": "detailed",
|
||||||
|
"codeComments": true,
|
||||||
|
"explanations": true
|
||||||
|
},
|
||||||
|
"domains": [
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"suggestedBy": "ecc-tools-repo-analysis",
|
||||||
|
"createdAt": "2026-07-30T02:08:05.402Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
```markdown
|
||||||
|
# certd Development Patterns
|
||||||
|
|
||||||
|
> Auto-generated skill from repository analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This skill teaches the core development patterns and workflows for the `certd` TypeScript codebase. It covers coding conventions, file organization, commit patterns, and detailed step-by-step instructions for extending user preferences—a common feature workflow. The repository is structured for modularity, with clear separation between UI and backend logic, and emphasizes maintainable, convention-driven development.
|
||||||
|
|
||||||
|
## Coding Conventions
|
||||||
|
|
||||||
|
### File Naming
|
||||||
|
|
||||||
|
- **CamelCase** is used for file names.
|
||||||
|
- Example: `userPreferences.ts`, `preferencesDrawer.vue`
|
||||||
|
|
||||||
|
### Import Style
|
||||||
|
|
||||||
|
- **Absolute imports** are preferred.
|
||||||
|
- Example:
|
||||||
|
```typescript
|
||||||
|
import { getUserPreferences } from 'packages/ui/certd-client/src/vben/layouts/widgets/preferences/api';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export Style
|
||||||
|
|
||||||
|
- **Named exports** are used throughout the codebase.
|
||||||
|
- Example:
|
||||||
|
```typescript
|
||||||
|
export function getUserPreferences() { ... }
|
||||||
|
export const PREFERENCE_KEYS = [ ... ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit Patterns
|
||||||
|
|
||||||
|
- **Conventional commits** are used, with the `feat` prefix for new features.
|
||||||
|
- Example:
|
||||||
|
```
|
||||||
|
feat: add account sync to preferences
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
### Extend Preferences Feature
|
||||||
|
|
||||||
|
**Trigger:** When someone wants to add or enhance a user preference feature (e.g., import/export, sync to account).
|
||||||
|
**Command:** `/extend-preferences`
|
||||||
|
|
||||||
|
Follow these steps to extend user preferences functionality:
|
||||||
|
|
||||||
|
1. **Update localization files**
|
||||||
|
Add or modify strings in:
|
||||||
|
- `packages/ui/certd-client/src/locales/langs/en-US/preferences.ts`
|
||||||
|
- `packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts`
|
||||||
|
```typescript
|
||||||
|
// en-US/preferences.ts
|
||||||
|
export default {
|
||||||
|
sync: "Sync Preferences",
|
||||||
|
import: "Import Preferences",
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Modify or add Vue components for preferences UI**
|
||||||
|
Update or create components such as:
|
||||||
|
- `preferences-drawer.vue`
|
||||||
|
- `account-sync.ts`
|
||||||
|
```vue
|
||||||
|
<!-- preferences-drawer.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button @click="syncPreferences">{{ $t('preferences.sync') }}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Update or add supporting icon definitions**
|
||||||
|
Edit:
|
||||||
|
- `packages/ui/certd-client/src/vben/icons/lucide.ts`
|
||||||
|
```typescript
|
||||||
|
export const SyncIcon = { /* icon definition */ };
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Implement or update store logic for settings**
|
||||||
|
Update:
|
||||||
|
- `packages/ui/certd-client/src/store/settings/index.tsx`
|
||||||
|
```typescript
|
||||||
|
export function syncPreferencesToAccount() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Add or update backend API/controller for user preferences**
|
||||||
|
Edit or add:
|
||||||
|
- `packages/ui/certd-client/src/vben/layouts/widgets/preferences/api.ts`
|
||||||
|
- `packages/ui/certd-server/src/controller/user/mine/user-preferences.ts`
|
||||||
|
```typescript
|
||||||
|
// user-preferences.ts
|
||||||
|
export async function updateUserPreferences(req, res) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Write or update backend tests for new preference logic**
|
||||||
|
Add or update:
|
||||||
|
- `packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts`
|
||||||
|
```typescript
|
||||||
|
test('should sync preferences', async () => { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Update backend models if necessary**
|
||||||
|
Edit:
|
||||||
|
- `packages/ui/certd-server/src/modules/mine/service/models.ts`
|
||||||
|
```typescript
|
||||||
|
export interface UserPreferences { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Patterns
|
||||||
|
|
||||||
|
- **Test files** follow the `*.test.*` naming convention.
|
||||||
|
- Example: `user-preferences.test.ts`
|
||||||
|
- **Testing framework** is not explicitly detected, but tests are written in TypeScript and likely use a standard Node.js testing library (e.g., Jest or Mocha).
|
||||||
|
- **Test Example:**
|
||||||
|
```typescript
|
||||||
|
test('should update preferences', async () => {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|----------------------|--------------------------------------------------------------|
|
||||||
|
| /extend-preferences | Guide to extend or enhance user preference functionality |
|
||||||
|
```
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ECC for Codex CLI
|
||||||
|
|
||||||
|
This supplements the root `AGENTS.md` with a repo-local ECC baseline.
|
||||||
|
|
||||||
|
## Repo Skill
|
||||||
|
|
||||||
|
- Repo-generated Codex skill: `.agents/skills/certd/SKILL.md`
|
||||||
|
- Claude-facing companion skill: `.claude/skills/certd/SKILL.md`
|
||||||
|
- Keep user-specific credentials and private MCPs in `~/.codex/config.toml`, not in this repo.
|
||||||
|
|
||||||
|
## MCP Baseline
|
||||||
|
|
||||||
|
Treat `.codex/config.toml` as the default ECC-safe baseline for work in this repository.
|
||||||
|
The generated baseline enables GitHub, Context7, Exa, Memory, Playwright, and Sequential Thinking.
|
||||||
|
|
||||||
|
## Multi-Agent Support
|
||||||
|
|
||||||
|
- Explorer: read-only evidence gathering
|
||||||
|
- Reviewer: correctness, security, and regression review
|
||||||
|
- Docs researcher: API and release-note verification
|
||||||
|
|
||||||
|
## Workflow Files
|
||||||
|
|
||||||
|
- No dedicated workflow command files were generated for this repo.
|
||||||
|
|
||||||
|
Use these workflow files as reusable task scaffolds when the detected repository workflows recur.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
model = "gpt-5.4"
|
||||||
|
model_reasoning_effort = "medium"
|
||||||
|
sandbox_mode = "read-only"
|
||||||
|
|
||||||
|
developer_instructions = """
|
||||||
|
Verify APIs, framework behavior, and release-note claims against primary documentation before changes land.
|
||||||
|
Cite the exact docs or file paths that support each claim.
|
||||||
|
Do not invent undocumented behavior.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
model = "gpt-5.4"
|
||||||
|
model_reasoning_effort = "medium"
|
||||||
|
sandbox_mode = "read-only"
|
||||||
|
|
||||||
|
developer_instructions = """
|
||||||
|
Stay in exploration mode.
|
||||||
|
Trace the real execution path, cite files and symbols, and avoid proposing fixes unless the parent agent asks for them.
|
||||||
|
Prefer targeted search and file reads over broad scans.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
model = "gpt-5.4"
|
||||||
|
model_reasoning_effort = "high"
|
||||||
|
sandbox_mode = "read-only"
|
||||||
|
|
||||||
|
developer_instructions = """
|
||||||
|
Review like an owner.
|
||||||
|
Prioritize correctness, security, behavioral regressions, and missing tests.
|
||||||
|
Lead with concrete findings and avoid style-only feedback unless it hides a real bug.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#:schema https://developers.openai.com/codex/config-schema.json
|
||||||
|
|
||||||
|
# ECC Tools generated Codex baseline
|
||||||
|
approval_policy = "on-request"
|
||||||
|
sandbox_mode = "workspace-write"
|
||||||
|
web_search = "live"
|
||||||
|
|
||||||
|
[mcp_servers.github]
|
||||||
|
command = "npx"
|
||||||
|
args = ["-y", "@modelcontextprotocol/server-github"]
|
||||||
|
|
||||||
|
[mcp_servers.context7]
|
||||||
|
command = "npx"
|
||||||
|
args = ["-y", "@upstash/context7-mcp@latest"]
|
||||||
|
|
||||||
|
[mcp_servers.exa]
|
||||||
|
url = "https://mcp.exa.ai/mcp"
|
||||||
|
|
||||||
|
[mcp_servers.memory]
|
||||||
|
command = "npx"
|
||||||
|
args = ["-y", "@modelcontextprotocol/server-memory"]
|
||||||
|
|
||||||
|
[mcp_servers.playwright]
|
||||||
|
command = "npx"
|
||||||
|
args = ["-y", "@playwright/mcp@latest", "--extension"]
|
||||||
|
|
||||||
|
[mcp_servers.sequential-thinking]
|
||||||
|
command = "npx"
|
||||||
|
args = ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
multi_agent = true
|
||||||
|
|
||||||
|
[agents]
|
||||||
|
max_threads = 6
|
||||||
|
max_depth = 1
|
||||||
|
|
||||||
|
[agents.explorer]
|
||||||
|
description = "Read-only codebase explorer for gathering evidence before changes are proposed."
|
||||||
|
config_file = "agents/explorer.toml"
|
||||||
|
|
||||||
|
[agents.reviewer]
|
||||||
|
description = "PR reviewer focused on correctness, security, and missing tests."
|
||||||
|
config_file = "agents/reviewer.toml"
|
||||||
|
|
||||||
|
[agents.docs_researcher]
|
||||||
|
description = "Documentation specialist that verifies APIs, framework behavior, and release notes."
|
||||||
|
config_file = "agents/docs-researcher.toml"
|
||||||
@@ -31,15 +31,6 @@ export default {
|
|||||||
copyPreferences: "Copy Preferences",
|
copyPreferences: "Copy Preferences",
|
||||||
copyPreferencesSuccessTitle: "Copy successful",
|
copyPreferencesSuccessTitle: "Copy successful",
|
||||||
copyPreferencesSuccess: "Copy successful, please override in `src/preferences.ts` under app",
|
copyPreferencesSuccess: "Copy successful, please override in `src/preferences.ts` under app",
|
||||||
importPreferences: "Import from Clipboard",
|
|
||||||
importPreferencesSuccessTitle: "Import successful",
|
|
||||||
importPreferencesSuccess: "Preferences imported from clipboard",
|
|
||||||
importPreferencesErrorTitle: "Import failed",
|
|
||||||
importPreferencesError: "Clipboard content is invalid. Please copy preferences JSON first",
|
|
||||||
saveToAccount: "Save to Account",
|
|
||||||
saveToAccountSuccess: "Preferences saved to account",
|
|
||||||
saveToAccountError: "Failed to save preferences to account",
|
|
||||||
saveToAccountNeedLogin: "Please sign in before saving to account",
|
|
||||||
clearAndLogout: "Clear Cache & Logout",
|
clearAndLogout: "Clear Cache & Logout",
|
||||||
mode: "Mode",
|
mode: "Mode",
|
||||||
general: "General",
|
general: "General",
|
||||||
|
|||||||
@@ -31,15 +31,6 @@ export default {
|
|||||||
copyPreferences: "复制偏好设置",
|
copyPreferences: "复制偏好设置",
|
||||||
copyPreferencesSuccessTitle: "复制成功",
|
copyPreferencesSuccessTitle: "复制成功",
|
||||||
copyPreferencesSuccess: "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
|
copyPreferencesSuccess: "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
|
||||||
importPreferences: "从剪切板导入",
|
|
||||||
importPreferencesSuccessTitle: "导入成功",
|
|
||||||
importPreferencesSuccess: "已从剪切板导入偏好设置",
|
|
||||||
importPreferencesErrorTitle: "导入失败",
|
|
||||||
importPreferencesError: "剪切板内容无效,请先复制偏好设置 JSON",
|
|
||||||
saveToAccount: "保存到账号",
|
|
||||||
saveToAccountSuccess: "偏好设置已保存到账号",
|
|
||||||
saveToAccountError: "保存到账号失败",
|
|
||||||
saveToAccountNeedLogin: "请先登录后再保存到账号",
|
|
||||||
clearAndLogout: "清空缓存 & 退出登录",
|
clearAndLogout: "清空缓存 & 退出登录",
|
||||||
mode: "模式",
|
mode: "模式",
|
||||||
general: "通用",
|
general: "通用",
|
||||||
|
|||||||
@@ -390,10 +390,4 @@ export const useSettingStore = defineStore({
|
|||||||
|
|
||||||
mitter.on("app.login", async () => {
|
mitter.on("app.login", async () => {
|
||||||
await useSettingStore().init();
|
await useSettingStore().init();
|
||||||
try {
|
|
||||||
const { loadPreferencesFromAccount } = await import("/@/vben/layouts/widgets/preferences/account-sync");
|
|
||||||
await loadPreferencesFromAccount();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("加载账号偏好设置失败", e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ export {
|
|||||||
CircleCheckBig,
|
CircleCheckBig,
|
||||||
CircleHelp,
|
CircleHelp,
|
||||||
Copy,
|
Copy,
|
||||||
ClipboardPaste,
|
|
||||||
CloudUpload,
|
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
Expand,
|
Expand,
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { message as antdMessage } from "ant-design-vue";
|
|
||||||
|
|
||||||
import { $t, loadLocaleMessages } from "/@/locales";
|
|
||||||
import { updatePreferences } from "/@/vben/preferences";
|
|
||||||
|
|
||||||
import { PreferencesSettingsGet, PreferencesSettingsSave } from "./api";
|
|
||||||
|
|
||||||
const PREFERENCES_KNOWN_KEYS = ["app", "theme", "logo", "sidebar", "header", "tabbar", "breadcrumb", "navigation", "widget", "footer", "copyright", "shortcutKeys", "transition"];
|
|
||||||
|
|
||||||
export function isPreferencesPayload(value: unknown): value is Record<string, any> {
|
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return PREFERENCES_KNOWN_KEYS.some(key => Object.prototype.hasOwnProperty.call(value, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 保存到账号:允许空对象(重置后的默认偏好) */
|
|
||||||
export function isPreferencesSavePayload(value: unknown): value is Record<string, any> {
|
|
||||||
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const keys = Object.keys(value as Record<string, any>);
|
|
||||||
if (keys.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return isPreferencesPayload(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function applyPreferencesFromAccount(data: Record<string, any>) {
|
|
||||||
updatePreferences(data);
|
|
||||||
if (data.app?.locale) {
|
|
||||||
await loadLocaleMessages(data.app.locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录后从账号加载偏好并应用到本地;
|
|
||||||
* - null / {}:账号无有效配置(含保存为空),默认继续使用本地偏好
|
|
||||||
*/
|
|
||||||
export async function loadPreferencesFromAccount() {
|
|
||||||
const data = await PreferencesSettingsGet();
|
|
||||||
if (data == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof data !== "object" || Array.isArray(data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 保存为空时,后续拉取到空数据后默认从本地读取,不覆盖本地
|
|
||||||
if (Object.keys(data).length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isPreferencesPayload(data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await applyPreferencesFromAccount(data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function savePreferencesToAccount(preferencesPayload: Record<string, any> | null | undefined) {
|
|
||||||
const payload = preferencesPayload && typeof preferencesPayload === "object" ? preferencesPayload : {};
|
|
||||||
if (!isPreferencesSavePayload(payload)) {
|
|
||||||
throw new Error("invalid preferences payload");
|
|
||||||
}
|
|
||||||
await PreferencesSettingsSave(payload);
|
|
||||||
antdMessage.success($t("preferences.saveToAccountSuccess"));
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// @ts-ignore
|
|
||||||
import { request } from "/@/api/service";
|
|
||||||
|
|
||||||
const apiPrefix = "/user/settings/preferences";
|
|
||||||
|
|
||||||
export async function PreferencesSettingsGet(): Promise<Record<string, any> | null> {
|
|
||||||
const res = await request({
|
|
||||||
url: apiPrefix + "/get",
|
|
||||||
method: "post",
|
|
||||||
showErrorNotify: false,
|
|
||||||
});
|
|
||||||
return (res as Record<string, any>) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function PreferencesSettingsSave(preferences: Record<string, any>) {
|
|
||||||
return await request({
|
|
||||||
url: apiPrefix + "/save",
|
|
||||||
method: "post",
|
|
||||||
data: { preferences },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
+3
-56
@@ -16,27 +16,21 @@ import type { SegmentedItem } from "/@/vben//shadcn-ui";
|
|||||||
|
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
import { ClipboardPaste, CloudUpload, Copy, RotateCw, X } from "/@/vben/icons";
|
import { Copy, RotateCw, X } from "/@/vben/icons";
|
||||||
import { $t, loadLocaleMessages } from "/@/locales";
|
import { $t, loadLocaleMessages } from "/@/locales";
|
||||||
import { clearPreferencesCache, preferences, resetPreferences, usePreferences } from "/@/vben/preferences";
|
import { clearPreferencesCache, preferences, resetPreferences, usePreferences } from "/@/vben/preferences";
|
||||||
|
|
||||||
import { useVbenDrawer } from "/@/vben//popup-ui";
|
import { useVbenDrawer } from "/@/vben//popup-ui";
|
||||||
import { VbenButton, VbenIconButton, VbenSegmented } from "/@/vben//shadcn-ui";
|
import { VbenButton, VbenIconButton, VbenSegmented } from "/@/vben//shadcn-ui";
|
||||||
import { globalShareState } from "/@/vben//shared/global-state";
|
import { globalShareState } from "/@/vben//shared/global-state";
|
||||||
import { useUserStore } from "/@/store/user";
|
|
||||||
|
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import { Animation, Block, Breadcrumb, BuiltinTheme, ColorMode, Content, Copyright, Footer, General, GlobalShortcutKeys, Header, Layout, Navigation, Radius, Sidebar, Tabbar, Theme, Widget } from "./blocks";
|
import { Animation, Block, Breadcrumb, BuiltinTheme, ColorMode, Content, Copyright, Footer, General, GlobalShortcutKeys, Header, Layout, Navigation, Radius, Sidebar, Tabbar, Theme, Widget } from "./blocks";
|
||||||
import { applyPreferencesFromAccount, isPreferencesPayload, savePreferencesToAccount } from "./account-sync";
|
|
||||||
|
|
||||||
import { message as antdMessage } from "ant-design-vue";
|
|
||||||
|
|
||||||
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||||
|
|
||||||
const message = globalShareState.getMessage();
|
const message = globalShareState.getMessage();
|
||||||
const userStore = useUserStore();
|
|
||||||
const savingToAccount = ref(false);
|
|
||||||
|
|
||||||
const appLocale = defineModel<SupportedLanguagesType>("appLocale");
|
const appLocale = defineModel<SupportedLanguagesType>("appLocale");
|
||||||
const appDynamicTitle = defineModel<boolean>("appDynamicTitle");
|
const appDynamicTitle = defineModel<boolean>("appDynamicTitle");
|
||||||
@@ -156,41 +150,6 @@ async function handleCopy() {
|
|||||||
await copy(JSON.stringify(diffPreference.value, null, 2));
|
await copy(JSON.stringify(diffPreference.value, null, 2));
|
||||||
|
|
||||||
message.copyPreferencesSuccess?.($t("preferences.copyPreferencesSuccessTitle"), $t("preferences.copyPreferencesSuccess"));
|
message.copyPreferencesSuccess?.($t("preferences.copyPreferencesSuccessTitle"), $t("preferences.copyPreferencesSuccess"));
|
||||||
antdMessage.success($t("preferences.copyPreferencesSuccessTitle"));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleImport() {
|
|
||||||
try {
|
|
||||||
const text = await navigator.clipboard.readText();
|
|
||||||
const data = JSON.parse(text);
|
|
||||||
if (!isPreferencesPayload(data)) {
|
|
||||||
throw new Error("invalid preferences payload");
|
|
||||||
}
|
|
||||||
await applyPreferencesFromAccount(data);
|
|
||||||
message.copyPreferencesSuccess?.($t("preferences.importPreferencesSuccessTitle"), $t("preferences.importPreferencesSuccess"));
|
|
||||||
antdMessage.success($t("preferences.importPreferencesSuccess"));
|
|
||||||
} catch {
|
|
||||||
antdMessage.error($t("preferences.importPreferencesError"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSaveToAccount() {
|
|
||||||
if (!userStore.isLogined) {
|
|
||||||
antdMessage.warning($t("preferences.saveToAccountNeedLogin"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (savingToAccount.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
savingToAccount.value = true;
|
|
||||||
try {
|
|
||||||
// 重置后 diff 为空时保存 {};后续登录拉取到空数据则继续使用本地偏好
|
|
||||||
await savePreferencesToAccount((diffPreference.value as Record<string, any>) || {});
|
|
||||||
} catch (e: any) {
|
|
||||||
antdMessage.error(e?.message || $t("preferences.saveToAccountError"));
|
|
||||||
} finally {
|
|
||||||
savingToAccount.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleClearCache() {
|
async function handleClearCache() {
|
||||||
@@ -351,25 +310,13 @@ async function handleReset() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex w-full flex-col gap-2 px-1">
|
<VbenButton :disabled="!diffPreference" class="mx-4 w-full" size="sm" variant="default" @click="handleCopy">
|
||||||
<div class="flex w-full gap-2">
|
|
||||||
<VbenButton :disabled="!diffPreference" class="w-full" size="sm" variant="default" @click="handleCopy">
|
|
||||||
<Copy class="mr-2 size-3" />
|
<Copy class="mr-2 size-3" />
|
||||||
{{ $t("preferences.copyPreferences") }}
|
{{ $t("preferences.copyPreferences") }}
|
||||||
</VbenButton>
|
</VbenButton>
|
||||||
<VbenButton class="w-full" size="sm" variant="outline" @click="handleImport">
|
<VbenButton :disabled="!diffPreference" class="mr-4 w-full" size="sm" variant="ghost" @click="handleClearCache">
|
||||||
<ClipboardPaste class="mr-2 size-3" />
|
|
||||||
{{ $t("preferences.importPreferences") }}
|
|
||||||
</VbenButton>
|
|
||||||
</div>
|
|
||||||
<VbenButton :disabled="!userStore.isLogined || savingToAccount" class="w-full" size="sm" variant="outline" @click="handleSaveToAccount">
|
|
||||||
<CloudUpload class="mr-2 size-3" />
|
|
||||||
{{ $t("preferences.saveToAccount") }}
|
|
||||||
</VbenButton>
|
|
||||||
<VbenButton :disabled="!diffPreference" class="w-full" size="sm" variant="ghost" @click="handleClearCache">
|
|
||||||
{{ $t("preferences.clearAndLogout") }}
|
{{ $t("preferences.clearAndLogout") }}
|
||||||
</VbenButton>
|
</VbenButton>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
/// <reference types="mocha" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
|
|
||||||
import assert from "node:assert/strict";
|
|
||||||
|
|
||||||
import { parseUserPreferencesPayload } from "./user-preferences.js";
|
|
||||||
|
|
||||||
describe("parseUserPreferencesPayload", () => {
|
|
||||||
it("parses wrapped preferences payload", () => {
|
|
||||||
const result = parseUserPreferencesPayload({
|
|
||||||
preferences: {
|
|
||||||
theme: { mode: "dark" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
assert.deepEqual(result, { theme: { mode: "dark" } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("parses direct preferences payload", () => {
|
|
||||||
const result = parseUserPreferencesPayload({
|
|
||||||
app: { locale: "en-US" },
|
|
||||||
theme: { mode: "light" },
|
|
||||||
});
|
|
||||||
assert.deepEqual(result, {
|
|
||||||
app: { locale: "en-US" },
|
|
||||||
theme: { mode: "light" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("parses empty preferences as reset payload", () => {
|
|
||||||
assert.deepEqual(parseUserPreferencesPayload({}), {});
|
|
||||||
assert.deepEqual(parseUserPreferencesPayload({ preferences: {} }), {});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns null for invalid payload", () => {
|
|
||||||
assert.equal(parseUserPreferencesPayload(null), null);
|
|
||||||
assert.equal(parseUserPreferencesPayload([]), null);
|
|
||||||
assert.equal(parseUserPreferencesPayload({ foo: 1 }), null);
|
|
||||||
assert.equal(parseUserPreferencesPayload({ preferences: { foo: 1 } }), null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
const PREFERENCES_KNOWN_KEYS = [
|
|
||||||
"app",
|
|
||||||
"theme",
|
|
||||||
"logo",
|
|
||||||
"sidebar",
|
|
||||||
"header",
|
|
||||||
"tabbar",
|
|
||||||
"breadcrumb",
|
|
||||||
"navigation",
|
|
||||||
"widget",
|
|
||||||
"footer",
|
|
||||||
"copyright",
|
|
||||||
"shortcutKeys",
|
|
||||||
"transition",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析用户偏好设置 payload。
|
|
||||||
* 兼容 `{ preferences: {...} }` 与直接传偏好对象两种格式。
|
|
||||||
* 空对象 `{}` 表示已重置为默认偏好,视为合法。
|
|
||||||
*/
|
|
||||||
export function parseUserPreferencesPayload(value: unknown): Record<string, any> | null {
|
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const raw = value as Record<string, any>;
|
|
||||||
const preferences =
|
|
||||||
raw.preferences != null && typeof raw.preferences === "object" && !Array.isArray(raw.preferences)
|
|
||||||
? (raw.preferences as Record<string, any>)
|
|
||||||
: raw;
|
|
||||||
const preferenceKeys = Object.keys(preferences);
|
|
||||||
// 空对象表示重置后的默认偏好
|
|
||||||
if (preferenceKeys.length === 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const hasKnownKey = PREFERENCES_KNOWN_KEYS.some(key => Object.prototype.hasOwnProperty.call(preferences, key));
|
|
||||||
if (!hasKnownKey) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return preferences;
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,10 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/c
|
|||||||
import { Constants, CrudController } from "@certd/lib-server";
|
import { Constants, CrudController } from "@certd/lib-server";
|
||||||
import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js";
|
import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js";
|
||||||
import { UserSettingsEntity } from "../../../modules/mine/entity/user-settings.js";
|
import { UserSettingsEntity } from "../../../modules/mine/entity/user-settings.js";
|
||||||
import { UserGrantSetting, UserPreferencesSetting } from "../../../modules/mine/service/models.js";
|
import { UserGrantSetting } from "../../../modules/mine/service/models.js";
|
||||||
import { isPlus } from "@certd/plus-core";
|
import { isPlus } from "@certd/plus-core";
|
||||||
import { merge } from "lodash-es";
|
import { merge } from "lodash-es";
|
||||||
import { ApiTags } from "@midwayjs/swagger";
|
import { ApiTags } from "@midwayjs/swagger";
|
||||||
import { parseUserPreferencesPayload } from "./user-preferences.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@@ -91,37 +90,4 @@ export class UserSettingsController extends CrudController<UserSettingsService>
|
|||||||
await this.service.saveSetting(userId, null, setting);
|
await this.service.saveSetting(userId, null, setting);
|
||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/preferences/get", { description: Constants.per.authOnly, summary: "获取用户偏好设置" })
|
|
||||||
async preferencesGet() {
|
|
||||||
const userId = this.getUserId();
|
|
||||||
const entity = await this.service.getByKey(UserPreferencesSetting.__key__, userId, null);
|
|
||||||
if (!entity?.setting) {
|
|
||||||
return this.ok(null);
|
|
||||||
}
|
|
||||||
let parsed: unknown;
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(entity.setting);
|
|
||||||
} catch {
|
|
||||||
return this.ok(null);
|
|
||||||
}
|
|
||||||
return this.ok(parseUserPreferencesPayload(parsed));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post("/preferences/save", { description: Constants.per.authOnly, summary: "保存用户偏好设置" })
|
|
||||||
async preferencesSave(@Body(ALL) bean: any) {
|
|
||||||
const userId = this.getUserId();
|
|
||||||
const preferences = parseUserPreferencesPayload(bean);
|
|
||||||
if (!preferences) {
|
|
||||||
throw new Error("偏好设置内容无效");
|
|
||||||
}
|
|
||||||
// 整份替换,避免 saveSetting 深合并留下已恢复为默认值的旧字段
|
|
||||||
const entity = new UserSettingsEntity();
|
|
||||||
entity.key = UserPreferencesSetting.__key__;
|
|
||||||
entity.title = UserPreferencesSetting.__title__;
|
|
||||||
entity.userId = userId;
|
|
||||||
entity.setting = JSON.stringify({ preferences });
|
|
||||||
await this.service.save(entity);
|
|
||||||
return this.ok({});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,6 @@ export class UserGrantSetting extends BaseSettings {
|
|||||||
allowAdminViewCerts = false;
|
allowAdminViewCerts = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserPreferencesSetting extends BaseSettings {
|
|
||||||
static __title__ = "用户偏好设置";
|
|
||||||
static __key__ = "user.preferences";
|
|
||||||
|
|
||||||
/** 偏好差异配置(相对默认值),与前端 diffPreference 结构一致 */
|
|
||||||
preferences: Record<string, any> = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserDomainImportSetting extends BaseSettings {
|
export class UserDomainImportSetting extends BaseSettings {
|
||||||
static __title__ = "用户域名导入设置";
|
static __title__ = "用户域名导入设置";
|
||||||
static __key__ = "user.domain.import";
|
static __key__ = "user.domain.import";
|
||||||
|
|||||||
Reference in New Issue
Block a user