Compare commits

..
22 changed files with 1109 additions and 298 deletions
+133
View File
@@ -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 |
```
+6
View File
@@ -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
+227
View File
@@ -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
+14
View File
@@ -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"
}
+133
View File
@@ -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 |
```
+26
View File
@@ -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.
+9
View File
@@ -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.
"""
+9
View File
@@ -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.
"""
+9
View File
@@ -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.
"""
+48
View File
@@ -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 },
});
}
@@ -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";