@chongyan/autospec 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +472 -0
- package/README.md +476 -0
- package/bin/autospec.js +3 -0
- package/knowledge/README.md +144 -0
- package/knowledge/checklists/code.md +182 -0
- package/knowledge/checklists/design.md +196 -0
- package/knowledge/checklists/release.md +70 -0
- package/knowledge/checklists/requirement.md +169 -0
- package/knowledge/checklists/test.md +46 -0
- package/knowledge/config/README.en.md +44 -0
- package/knowledge/config/README.md +44 -0
- package/knowledge/config/role-composition.yaml +98 -0
- package/knowledge/config/role-extensions.yaml +140 -0
- package/knowledge/config/skill-compositions.yaml +142 -0
- package/knowledge/config/team-stage.yaml +95 -0
- package/knowledge/config/team-tasks.yaml +139 -0
- package/knowledge/config/team-triggers.yaml +198 -0
- package/knowledge/config/validation-patterns.yaml +137 -0
- package/knowledge/domain/README.md +115 -0
- package/knowledge/domain/flows/README.md +194 -0
- package/knowledge/domain/glossary.md +143 -0
- package/knowledge/domain/rules.md +138 -0
- package/knowledge/environment/README.en.md +36 -0
- package/knowledge/environment/README.md +87 -0
- package/knowledge/environment/component-knowledge.md +316 -0
- package/knowledge/environment/detection-patterns.yaml +502 -0
- package/knowledge/environment/middleware-knowledge.md +237 -0
- package/knowledge/environment/template-registry.md +321 -0
- package/knowledge/guides/domain-driven-design.md +345 -0
- package/knowledge/guides/knowledge-management.md +369 -0
- package/knowledge/guides/requirement-engineering.md +329 -0
- package/knowledge/guides/stages/ai-effect-evaluator.md +93 -0
- package/knowledge/guides/stages/code-implementer.md +205 -0
- package/knowledge/guides/stages/code-reviewer.md +111 -0
- package/knowledge/guides/stages/consistency-checker.md +177 -0
- package/knowledge/guides/stages/design-planner.md +401 -0
- package/knowledge/guides/stages/design-reviewer.md +83 -0
- package/knowledge/guides/stages/integration-test-runner.md +105 -0
- package/knowledge/guides/stages/release-checker.md +205 -0
- package/knowledge/guides/stages/requirement-analyzer.md +195 -0
- package/knowledge/guides/stages/requirement-reviewer.md +83 -0
- package/knowledge/guides/stages/security-reviewer.md +89 -0
- package/knowledge/guides/stages/test-context-analyzer.md +250 -0
- package/knowledge/guides/stages/test-generator.md +241 -0
- package/knowledge/guides/stages/test-planner.md +183 -0
- package/knowledge/guides/stages/test-reviewer.md +76 -0
- package/knowledge/guides/stages/unit-test-runner.md +83 -0
- package/knowledge/guides/support/ai-agent-analyzer.md +362 -0
- package/knowledge/guides/support/ai-anomaly-analyzer.md +213 -0
- package/knowledge/guides/support/ai-artifact-evaluator.md +192 -0
- package/knowledge/guides/support/ai-capability-analyzer.md +193 -0
- package/knowledge/guides/support/ai-component-analyzer.md +169 -0
- package/knowledge/guides/support/ai-data-validator.md +276 -0
- package/knowledge/guides/support/ai-evaluation-planner.md +374 -0
- package/knowledge/guides/support/ai-path-evaluator.md +274 -0
- package/knowledge/guides/support/ai-pipeline-evaluator.md +219 -0
- package/knowledge/guides/support/ai-rag-analyzer.md +339 -0
- package/knowledge/guides/support/ai-task-assessor.md +418 -0
- package/knowledge/guides/support/ai-test-diagnostics.md +133 -0
- package/knowledge/guides/support/complexity-assessor.md +268 -0
- package/knowledge/guides/support/component-discovery.md +183 -0
- package/knowledge/guides/support/environment-scanner.md +207 -0
- package/knowledge/guides/support/environment-validator.md +207 -0
- package/knowledge/guides/support/knowledge-generator.md +234 -0
- package/knowledge/guides/support/methodology-extractor.md +55 -0
- package/knowledge/guides/support/pipeline-protocol.md +438 -0
- package/knowledge/guides/support/practice-logger.md +359 -0
- package/knowledge/guides/support/scope-inference.md +174 -0
- package/knowledge/guides/support/skill-distiller.md +91 -0
- package/knowledge/guides/support/skill-updater.md +45 -0
- package/knowledge/guides/support/skill-validator.md +72 -0
- package/knowledge/guides/support/team-orchestrator.md +323 -0
- package/knowledge/guides/support/tech-stack-analyzer.md +139 -0
- package/knowledge/guides/support/test-runner.md +254 -0
- package/knowledge/guides/system-design.md +352 -0
- package/knowledge/organization/ai-native-team.md +318 -0
- package/knowledge/organization/team-metrics.md +228 -0
- package/knowledge/principles/constitution.md +134 -0
- package/knowledge/principles/core-principles.md +368 -0
- package/knowledge/principles/design-philosophy.md +877 -0
- package/knowledge/principles/evolution.md +553 -0
- package/knowledge/process/01-requirement.md +113 -0
- package/knowledge/process/02-design.md +123 -0
- package/knowledge/process/03-implementation.md +90 -0
- package/knowledge/process/04-review.md +80 -0
- package/knowledge/process/05-testing.md +90 -0
- package/knowledge/process/06-delivery.md +88 -0
- package/knowledge/process/README.en.md +38 -0
- package/knowledge/process/README.md +48 -0
- package/knowledge/process/ai-sdlc.md +475 -0
- package/knowledge/process/overview.md +319 -0
- package/knowledge/standards/code-review.md +876 -0
- package/knowledge/standards/coding-style.md +940 -0
- package/knowledge/standards/data-consistency.md +1085 -0
- package/knowledge/standards/document-versioning.md +210 -0
- package/knowledge/standards/risk-detection.md +186 -0
- package/knowledge/templates/ai-evaluation.md +150 -0
- package/knowledge/templates/api-design.md +117 -0
- package/knowledge/templates/database-design.md +132 -0
- package/knowledge/templates/domain-driven-design.md +321 -0
- package/knowledge/templates/product-proposal.md +201 -0
- package/knowledge/templates/system-design.md +227 -0
- package/knowledge/templates/task-breakdown.md +107 -0
- package/knowledge/templates/test-case.md +170 -0
- package/package.json +53 -0
- package/plugins/.claude-plugin/plugin.json +134 -0
- package/plugins/agents/roles/ai-engineer.md +129 -0
- package/plugins/agents/roles/backend-engineer.md +165 -0
- package/plugins/agents/roles/ceo.md +94 -0
- package/plugins/agents/roles/data-engineer.md +135 -0
- package/plugins/agents/roles/devops-engineer.md +181 -0
- package/plugins/agents/roles/frontend-engineer.md +129 -0
- package/plugins/agents/roles/product-owner.md +98 -0
- package/plugins/agents/roles/quality-engineer.md +129 -0
- package/plugins/agents/roles/security-engineer.md +180 -0
- package/plugins/agents/roles/tech-lead.md +97 -0
- package/plugins/agents/support/blind-comparator.md +88 -0
- package/plugins/agents/support/consistency-checker.md +103 -0
- package/plugins/agents/support/failure-diagnostician.md +141 -0
- package/plugins/agents/support/independent-reviewer.md +80 -0
- package/plugins/agents/support/safety-auditor.md +121 -0
- package/plugins/agents/support/skill-benchmarker.md +86 -0
- package/plugins/agents/support/skill-forger.md +105 -0
- package/plugins/agents/support/stage-gate-evaluator.md +121 -0
- package/plugins/agents/support/test-coverage-reviewer.md +73 -0
- package/plugins/benchmarks/templates/README.md +44 -0
- package/plugins/benchmarks/templates/commands/explore-template.yaml +48 -0
- package/plugins/benchmarks/templates/pipeline/agile-template.yaml +84 -0
- package/plugins/benchmarks/templates/pipeline/waterfall-template.yaml +106 -0
- package/plugins/benchmarks/templates/skills/requirement-analyzer-template.yaml +48 -0
- package/plugins/commands/README.en.md +96 -0
- package/plugins/commands/README.md +96 -0
- package/plugins/commands/apply.md +191 -0
- package/plugins/commands/archive.md +76 -0
- package/plugins/commands/env-export.md +79 -0
- package/plugins/commands/env-sync.md +640 -0
- package/plugins/commands/env-template.md +223 -0
- package/plugins/commands/env-update.md +264 -0
- package/plugins/commands/env-validate.md +176 -0
- package/plugins/commands/env.md +79 -0
- package/plugins/commands/explore.md +76 -0
- package/plugins/commands/field-evolve.md +536 -0
- package/plugins/commands/memory.md +249 -0
- package/plugins/commands/project-evolve.md +821 -0
- package/plugins/commands/propose.md +93 -0
- package/plugins/commands/review.md +140 -0
- package/plugins/commands/run.md +224 -0
- package/plugins/commands/status.md +62 -0
- package/plugins/commands/validate.md +108 -0
- package/plugins/hooks/README.en.md +56 -0
- package/plugins/hooks/README.md +56 -0
- package/plugins/hooks/ai-project-guard.js +329 -0
- package/plugins/hooks/artifact-evaluation-hook.js +237 -0
- package/plugins/hooks/constitution-guard.js +211 -0
- package/plugins/hooks/environment-autocommit.js +264 -0
- package/plugins/hooks/environment-manager.js +778 -0
- package/plugins/hooks/execution-tracker.js +354 -0
- package/plugins/hooks/frozen-zone-guard.js +140 -0
- package/plugins/hooks/layer1-validator.js +423 -0
- package/plugins/hooks/lib/artifact-evaluator.js +414 -0
- package/plugins/hooks/lib/benchmarks/change-detector.js +390 -0
- package/plugins/hooks/lib/benchmarks/evaluator.js +605 -0
- package/plugins/hooks/lib/benchmarks/integration-example.js +169 -0
- package/plugins/hooks/lib/data-and-ai-detector.js +275 -0
- package/plugins/hooks/lib/detection-pattern-loader.js +865 -0
- package/plugins/hooks/lib/directory-discovery.js +395 -0
- package/plugins/hooks/lib/environment-config-loader.js +341 -0
- package/plugins/hooks/lib/environment-detector.js +553 -0
- package/plugins/hooks/lib/environment-evolver.js +564 -0
- package/plugins/hooks/lib/environment-registry.js +813 -0
- package/plugins/hooks/lib/execution-path.js +427 -0
- package/plugins/hooks/lib/hook-error-recorder.js +245 -0
- package/plugins/hooks/lib/hook-logger.js +538 -0
- package/plugins/hooks/lib/hook-runner.js +97 -0
- package/plugins/hooks/lib/hook-runner.sh +44 -0
- package/plugins/hooks/lib/hook-state-manager.js +480 -0
- package/plugins/hooks/lib/memory-extractor.js +377 -0
- package/plugins/hooks/lib/memory-manager.js +673 -0
- package/plugins/hooks/lib/metrics-analyzer.js +489 -0
- package/plugins/hooks/lib/project-evolution/auto-fixer.js +511 -0
- package/plugins/hooks/lib/project-evolution/memory-manager.js +346 -0
- package/plugins/hooks/lib/project-evolution/pattern-detector.js +476 -0
- package/plugins/hooks/lib/project-evolution/semantic-indexer.js +480 -0
- package/plugins/hooks/lib/project-structure-detector.js +326 -0
- package/plugins/hooks/lib/rollback-tracker.js +346 -0
- package/plugins/hooks/lib/source-code-scanner.js +596 -0
- package/plugins/hooks/lib/technology-stack-detector.js +374 -0
- package/plugins/hooks/lib/test-failure-analyzer.js +375 -0
- package/plugins/hooks/lib/test-failure-fixer.js +268 -0
- package/plugins/hooks/lib/trace-context.js +277 -0
- package/plugins/hooks/lib/validation-patterns.js +415 -0
- package/plugins/hooks/memory-sync.js +171 -0
- package/plugins/hooks/pipeline-observer.js +413 -0
- package/plugins/hooks/scope-sentinel.js +204 -0
- package/plugins/hooks/trace-initialization.js +169 -0
- package/plugins/memory/templates/code-quality.yaml +149 -0
- package/plugins/memory/templates/multi-system.yaml +155 -0
- package/plugins/memory/templates/team-habits.yaml +119 -0
- package/plugins/memory/templates/testing.yaml +121 -0
- package/plugins/skills/README.en.md +47 -0
- package/plugins/skills/README.md +104 -0
- package/plugins/skills/benchmark-executor/README.md +93 -0
- package/plugins/skills/benchmark-executor/SKILL.md +647 -0
- package/plugins/skills/benchmark-generator/SKILL.md +349 -0
- package/plugins/skills/delivery-stage/SKILL.md +203 -0
- package/plugins/skills/design-stage/SKILL.md +216 -0
- package/plugins/skills/evolution-process/SKILL.md +291 -0
- package/plugins/skills/exploration-phase/SKILL.md +133 -0
- package/plugins/skills/implementation-stage/SKILL.md +179 -0
- package/plugins/skills/layer1-validation/SKILL.md +79 -0
- package/plugins/skills/pending-dashboard/SKILL.md +109 -0
- package/plugins/skills/project-evolution/SKILL.md +847 -0
- package/plugins/skills/requirement-stage/SKILL.md +183 -0
- package/plugins/skills/skill-forge/SKILL.md +223 -0
- package/plugins/skills/skill-forge/references/description-guide.md +92 -0
- package/plugins/skills/skill-forge/references/quality-rubric.md +104 -0
- package/plugins/skills/skill-forge/references/skill-template.md +106 -0
- package/plugins/skills/startup-guard/SKILL.md +38 -0
- package/plugins/skills/testing-stage/SKILL.md +195 -0
- package/scripts/cli/global-init.js +288 -0
- package/scripts/cli/global.js +324 -0
- package/scripts/cli/index.js +55 -0
- package/scripts/cli/init.js +382 -0
- package/scripts/cli/list.js +69 -0
- package/scripts/cli/org.js +340 -0
- package/scripts/cli/update.js +44 -0
- package/scripts/config/commands.config.js +145 -0
- package/scripts/config/hooks.config.js +197 -0
- package/scripts/evolution/evolution-router.js +273 -0
- package/scripts/evolution/evolution-signal-collector.js +307 -0
- package/scripts/evolution/knowledge-loader.js +346 -0
- package/scripts/evolution/marketplace.js +317 -0
- package/scripts/evolution/version-manager.js +371 -0
- package/scripts/install/agents.js +106 -0
- package/scripts/install/commands.js +133 -0
- package/scripts/install/constants.js +424 -0
- package/scripts/install/hook-logger.js +536 -0
- package/scripts/install/hooks.js +110 -0
- package/scripts/install/index.js +39 -0
- package/scripts/install/skills.js +95 -0
- package/scripts/postinstall.js +25 -0
- package/scripts/state.js +376 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoSpec Hooks Utilities
|
|
3
|
+
* Shared utilities for all hook scripts
|
|
4
|
+
*
|
|
5
|
+
* This file is self-contained for hooks to work independently after being copied to target projects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Global error handlers (防止未捕获的错误导致 hook 静默失败)
|
|
10
|
+
// ============================================================
|
|
11
|
+
|
|
12
|
+
// 只注册一次
|
|
13
|
+
let globalHandlersRegistered = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 注册全局错误处理器
|
|
17
|
+
* 防止 uncaughtException 和 unhandledRejection 导致 hook 静默失败
|
|
18
|
+
*/
|
|
19
|
+
function registerGlobalErrorHandlers() {
|
|
20
|
+
if (globalHandlersRegistered) return;
|
|
21
|
+
globalHandlersRegistered = true;
|
|
22
|
+
|
|
23
|
+
process.on('uncaughtException', (err) => {
|
|
24
|
+
console.error('[AutoSpec Hook] Uncaught exception:', err.message);
|
|
25
|
+
if (process.env.VERBOSE || process.env.DEBUG) {
|
|
26
|
+
console.error(err.stack);
|
|
27
|
+
}
|
|
28
|
+
process.exit(0); // 允许操作继续,但至少记录了错误
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
32
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
33
|
+
console.error('[AutoSpec Hook] Unhandled rejection:', message);
|
|
34
|
+
if (process.env.VERBOSE || process.env.DEBUG && reason instanceof Error) {
|
|
35
|
+
console.error(reason.stack);
|
|
36
|
+
}
|
|
37
|
+
process.exit(0);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 自动注册全局错误处理器
|
|
42
|
+
registerGlobalErrorHandlers();
|
|
43
|
+
|
|
44
|
+
// ============================================================
|
|
45
|
+
// Logger utilities
|
|
46
|
+
// ============================================================
|
|
47
|
+
|
|
48
|
+
export const LOG_LEVELS = {
|
|
49
|
+
ERROR: 0,
|
|
50
|
+
WARN: 1,
|
|
51
|
+
INFO: 2,
|
|
52
|
+
DEBUG: 3
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
let currentLevel = LOG_LEVELS.INFO;
|
|
56
|
+
let isVerbose = false;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set log level
|
|
60
|
+
*/
|
|
61
|
+
export function setLogLevel(level) {
|
|
62
|
+
if (typeof level === 'string') {
|
|
63
|
+
currentLevel = LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.INFO;
|
|
64
|
+
} else {
|
|
65
|
+
currentLevel = level;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Enable verbose mode
|
|
71
|
+
*/
|
|
72
|
+
export function setVerbose(verbose) {
|
|
73
|
+
isVerbose = verbose;
|
|
74
|
+
if (verbose) {
|
|
75
|
+
currentLevel = LOG_LEVELS.DEBUG;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if verbose mode is enabled
|
|
81
|
+
*/
|
|
82
|
+
export function isVerboseMode() {
|
|
83
|
+
return isVerbose || currentLevel >= LOG_LEVELS.DEBUG;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Format log message with timestamp and level
|
|
88
|
+
*/
|
|
89
|
+
function formatMessage(level, message, meta = {}) {
|
|
90
|
+
const timestamp = new Date().toISOString();
|
|
91
|
+
const levelStr = Object.keys(LOG_LEVELS).find(k => LOG_LEVELS[k] === level) || 'UNKNOWN';
|
|
92
|
+
|
|
93
|
+
let output = `[${timestamp}] [${levelStr}] ${message}`;
|
|
94
|
+
|
|
95
|
+
if (Object.keys(meta).length > 0) {
|
|
96
|
+
output += '\n' + Object.entries(meta)
|
|
97
|
+
.map(([k, v]) => ` ${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`)
|
|
98
|
+
.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Log error message
|
|
106
|
+
*/
|
|
107
|
+
export function error(message, meta = {}) {
|
|
108
|
+
if (currentLevel >= LOG_LEVELS.ERROR) {
|
|
109
|
+
console.error(formatMessage(LOG_LEVELS.ERROR, message, meta));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Log warning message
|
|
115
|
+
*/
|
|
116
|
+
export function warn(message, meta = {}) {
|
|
117
|
+
if (currentLevel >= LOG_LEVELS.WARN) {
|
|
118
|
+
console.warn(formatMessage(LOG_LEVELS.WARN, message, meta));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Log info message
|
|
124
|
+
*/
|
|
125
|
+
export function info(message, meta = {}) {
|
|
126
|
+
if (currentLevel >= LOG_LEVELS.INFO) {
|
|
127
|
+
console.log(formatMessage(LOG_LEVELS.INFO, message, meta));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Log debug message (only in verbose mode)
|
|
133
|
+
*/
|
|
134
|
+
export function debug(message, meta = {}) {
|
|
135
|
+
if (currentLevel >= LOG_LEVELS.DEBUG) {
|
|
136
|
+
console.log(formatMessage(LOG_LEVELS.DEBUG, message, meta));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Create a hook-specific logger
|
|
142
|
+
*/
|
|
143
|
+
export function createHookLogger(hookName) {
|
|
144
|
+
return {
|
|
145
|
+
error: (msg, meta) => error(`[${hookName}] ${msg}`, meta),
|
|
146
|
+
warn: (msg, meta) => warn(`[${hookName}] ${msg}`, meta),
|
|
147
|
+
info: (msg, meta) => info(`[${hookName}] ${msg}`, meta),
|
|
148
|
+
debug: (msg, meta) => debug(`[${hookName}] ${msg}`, meta)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================
|
|
153
|
+
// Hook error classification and security levels
|
|
154
|
+
// ============================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Hook 错误类型枚举
|
|
158
|
+
*/
|
|
159
|
+
export const HookErrorType = {
|
|
160
|
+
// 可恢复错误 - hook 逻辑自身无法处理,但不影响操作
|
|
161
|
+
RECOVERABLE: {
|
|
162
|
+
code: 'RECOVERABLE',
|
|
163
|
+
severity: 'low',
|
|
164
|
+
failStrategy: 'open', // 允许操作继续
|
|
165
|
+
shouldAlert: false,
|
|
166
|
+
shouldLog: true
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// 输入解析错误 - 输入数据格式问题
|
|
170
|
+
PARSE_ERROR: {
|
|
171
|
+
code: 'PARSE_ERROR',
|
|
172
|
+
severity: 'medium',
|
|
173
|
+
failStrategy: 'open', // 允许操作继续(无法解析则跳过检查)
|
|
174
|
+
shouldAlert: true,
|
|
175
|
+
shouldLog: true
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// 权限错误 - 文件系统权限问题
|
|
179
|
+
PERMISSION_ERROR: {
|
|
180
|
+
code: 'PERMISSION_ERROR',
|
|
181
|
+
severity: 'high',
|
|
182
|
+
failStrategy: 'open', // 允许操作继续,但必须告警
|
|
183
|
+
shouldAlert: true,
|
|
184
|
+
shouldLog: true
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// 运行时错误 - hook 内部逻辑错误
|
|
188
|
+
RUNTIME_ERROR: {
|
|
189
|
+
code: 'RUNTIME_ERROR',
|
|
190
|
+
severity: 'high',
|
|
191
|
+
failStrategy: 'context', // 根据 hook 类型决定
|
|
192
|
+
shouldAlert: true,
|
|
193
|
+
shouldLog: true
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// 关键错误 - 必须阻止操作
|
|
197
|
+
CRITICAL_ERROR: {
|
|
198
|
+
code: 'CRITICAL_ERROR',
|
|
199
|
+
severity: 'critical',
|
|
200
|
+
failStrategy: 'closed', // 阻止操作
|
|
201
|
+
shouldAlert: true,
|
|
202
|
+
shouldLog: true
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 根据 hook 名称获取其安全级别
|
|
208
|
+
*/
|
|
209
|
+
export const HookSecurityLevel = {
|
|
210
|
+
'frozen-zone-guard': 'critical', // 关键安全 hook
|
|
211
|
+
'layer1-validator': 'critical', // 流程门禁 hook
|
|
212
|
+
'constitution-guard': 'high',
|
|
213
|
+
'scope-sentinel': 'medium',
|
|
214
|
+
'execution-tracker': 'low',
|
|
215
|
+
'pipeline-observer': 'low',
|
|
216
|
+
'trace-initialization': 'low',
|
|
217
|
+
'artifact-evaluation-hook': 'medium',
|
|
218
|
+
'environment-manager': 'medium',
|
|
219
|
+
'ai-project-guard': 'medium'
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 分类错误
|
|
224
|
+
* @param {Error} err - 错误对象
|
|
225
|
+
* @param {string} hookName - Hook 名称
|
|
226
|
+
* @returns {Object} 错误类型
|
|
227
|
+
*/
|
|
228
|
+
export function classifyError(err, hookName) {
|
|
229
|
+
// 权限错误
|
|
230
|
+
if (err.code === 'EACCES' ||
|
|
231
|
+
err.message?.includes('permission denied') ||
|
|
232
|
+
err.message?.includes('ENOENT')) {
|
|
233
|
+
return HookErrorType.PERMISSION_ERROR;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 解析错误
|
|
237
|
+
if (err.message?.includes('JSON') ||
|
|
238
|
+
err.message?.includes('parse') ||
|
|
239
|
+
err.message?.includes('Unexpected token')) {
|
|
240
|
+
return HookErrorType.PARSE_ERROR;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 根据 hook 安全级别和错误上下文判断
|
|
244
|
+
const securityLevel = HookSecurityLevel[hookName] || 'low';
|
|
245
|
+
|
|
246
|
+
// 关键 hook 的任何错误都视为严重
|
|
247
|
+
if (securityLevel === 'critical') {
|
|
248
|
+
return {
|
|
249
|
+
...HookErrorType.RUNTIME_ERROR,
|
|
250
|
+
failStrategy: 'closed' // 覆盖为 fail-closed
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return HookErrorType.RUNTIME_ERROR;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Handle hook errors gracefully
|
|
259
|
+
* 增强版错误处理:分类错误、记录日志、决定是否阻止操作
|
|
260
|
+
* @param {string} hookName - Hook 名称
|
|
261
|
+
* @param {Error} err - 错误对象
|
|
262
|
+
* @param {Object} context - 上下文信息(包含 eventName, projectRoot, input 等)
|
|
263
|
+
* @returns {Object} 输出对象,包含是否应该阻止操作
|
|
264
|
+
*/
|
|
265
|
+
export function handleHookError(hookName, err, context = {}) {
|
|
266
|
+
const errorType = classifyError(err, hookName);
|
|
267
|
+
const securityLevel = HookSecurityLevel[hookName] || 'low';
|
|
268
|
+
|
|
269
|
+
// 构建详细的错误信息
|
|
270
|
+
const errorInfo = {
|
|
271
|
+
hookName,
|
|
272
|
+
errorType: errorType.code,
|
|
273
|
+
errorMessage: err.message,
|
|
274
|
+
errorCode: err.code,
|
|
275
|
+
severity: errorType.severity,
|
|
276
|
+
timestamp: new Date().toISOString(),
|
|
277
|
+
context
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// 记录到控制台
|
|
281
|
+
const errorMeta = {
|
|
282
|
+
hook: hookName,
|
|
283
|
+
error: err.message,
|
|
284
|
+
code: err.code,
|
|
285
|
+
type: errorType.code,
|
|
286
|
+
severity: errorType.severity,
|
|
287
|
+
stack: isVerbose ? err.stack : undefined
|
|
288
|
+
};
|
|
289
|
+
error(`Hook execution failed`, errorMeta);
|
|
290
|
+
|
|
291
|
+
// 记录到 hook 错误日志(如果有 projectRoot)
|
|
292
|
+
if (context.projectRoot) {
|
|
293
|
+
try {
|
|
294
|
+
// 动态导入避免循环依赖
|
|
295
|
+
import('./hook-error-recorder.js').then(({ recordHookError }) => {
|
|
296
|
+
recordHookError(context.projectRoot, errorInfo);
|
|
297
|
+
}).catch(() => {
|
|
298
|
+
// 忽略导入错误
|
|
299
|
+
});
|
|
300
|
+
} catch {
|
|
301
|
+
// 忽略错误
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 决定是否阻止操作
|
|
306
|
+
let permissionDecision = 'allow';
|
|
307
|
+
let permissionDecisionReason = null;
|
|
308
|
+
|
|
309
|
+
if (errorType.failStrategy === 'closed' ||
|
|
310
|
+
(errorType.failStrategy === 'context' && securityLevel === 'critical')) {
|
|
311
|
+
permissionDecision = 'deny';
|
|
312
|
+
permissionDecisionReason =
|
|
313
|
+
`[AutoSpec] 🚨 HOOK ERROR - Operation Blocked\n` +
|
|
314
|
+
`Hook: ${hookName}\n` +
|
|
315
|
+
`Error Type: ${errorType.code}\n` +
|
|
316
|
+
`Reason: ${err.message}\n\n` +
|
|
317
|
+
`This is a critical hook that protects the project. ` +
|
|
318
|
+
`When it fails, the operation must be blocked for safety.\n` +
|
|
319
|
+
`Please check the error and try again.`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 构建用户提示
|
|
323
|
+
let additionalContext = buildUserMessage(hookName, errorType, err);
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
hookSpecificOutput: {
|
|
327
|
+
hookEventName: context.eventName || 'Unknown',
|
|
328
|
+
permissionDecision,
|
|
329
|
+
permissionDecisionReason,
|
|
330
|
+
additionalContext
|
|
331
|
+
},
|
|
332
|
+
// 返回错误分类信息供调用者使用
|
|
333
|
+
errorType,
|
|
334
|
+
shouldBlock: permissionDecision === 'deny'
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 构建用户提示信息
|
|
340
|
+
*/
|
|
341
|
+
function buildUserMessage(hookName, errorType, err) {
|
|
342
|
+
// 权限错误的特殊处理
|
|
343
|
+
if (errorType.code === 'PERMISSION_ERROR') {
|
|
344
|
+
return `[AutoSpec] ⚠️ 权限错误: ${hookName} 无法写入文件
|
|
345
|
+
|
|
346
|
+
可能原因:.autospec 或 .claude 目录由 root 创建
|
|
347
|
+
|
|
348
|
+
修复方法:运行以下命令修复权限
|
|
349
|
+
sudo chown -R $(whoami) .autospec .claude
|
|
350
|
+
|
|
351
|
+
或者重新初始化(不使用 sudo):
|
|
352
|
+
rm -rf .autospec .claude CLAUDE.md
|
|
353
|
+
autospec init`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 其他错误的通用提示
|
|
357
|
+
const severity = errorType.severity;
|
|
358
|
+
const icon = severity === 'critical' ? '🚨' :
|
|
359
|
+
severity === 'high' ? '⚠️' : 'ℹ️';
|
|
360
|
+
|
|
361
|
+
return `[AutoSpec] ${icon} Hook '${hookName}' encountered an error (${errorType.code})
|
|
362
|
+
|
|
363
|
+
Error: ${err.message}
|
|
364
|
+
|
|
365
|
+
${errorType.failStrategy === 'closed' ?
|
|
366
|
+
'This operation has been blocked for safety.' :
|
|
367
|
+
'The operation was allowed to continue. Use --verbose for details.'}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Safe JSON parse with logging
|
|
372
|
+
*/
|
|
373
|
+
export function safeJsonParse(str, defaultValue = null, context = '') {
|
|
374
|
+
try {
|
|
375
|
+
return JSON.parse(str);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
debug(`JSON parse failed${context ? ` (${context})` : ''}`, {
|
|
378
|
+
error: err.message,
|
|
379
|
+
input: str?.slice(0, 200) // Log truncated input
|
|
380
|
+
});
|
|
381
|
+
return defaultValue;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ============================================================
|
|
386
|
+
// Common utilities
|
|
387
|
+
// ============================================================
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Read stdin safely with timeout
|
|
391
|
+
*/
|
|
392
|
+
export function readStdin(timeout = 1000) {
|
|
393
|
+
return new Promise((resolve) => {
|
|
394
|
+
let data = '';
|
|
395
|
+
process.stdin.setEncoding('utf8');
|
|
396
|
+
|
|
397
|
+
const timeoutId = setTimeout(() => {
|
|
398
|
+
cleanup();
|
|
399
|
+
resolve(data);
|
|
400
|
+
}, timeout);
|
|
401
|
+
|
|
402
|
+
function cleanup() {
|
|
403
|
+
clearTimeout(timeoutId);
|
|
404
|
+
process.stdin.removeListener('data', onData);
|
|
405
|
+
process.stdin.removeListener('end', onEnd);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function onData(chunk) {
|
|
409
|
+
data += chunk;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function onEnd() {
|
|
413
|
+
cleanup();
|
|
414
|
+
resolve(data);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
process.stdin.on('data', onData);
|
|
418
|
+
process.stdin.on('end', onEnd);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Find project root by searching upward for .autospec directory
|
|
424
|
+
*/
|
|
425
|
+
export function findProjectRoot(startDir) {
|
|
426
|
+
const fs = require('fs');
|
|
427
|
+
const path = require('path');
|
|
428
|
+
|
|
429
|
+
let dir = path.resolve(startDir);
|
|
430
|
+
const root = path.parse(dir).root;
|
|
431
|
+
|
|
432
|
+
while (dir !== root) {
|
|
433
|
+
if (fs.existsSync(path.join(dir, '.autospec'))) {
|
|
434
|
+
return dir;
|
|
435
|
+
}
|
|
436
|
+
dir = path.dirname(dir);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Async version of findProjectRoot
|
|
444
|
+
*/
|
|
445
|
+
export async function findProjectRootAsync(startDir) {
|
|
446
|
+
const { default: fs } = await import('fs');
|
|
447
|
+
const { default: path } = await import('path');
|
|
448
|
+
|
|
449
|
+
let dir = path.resolve(startDir);
|
|
450
|
+
const root = path.parse(dir).root;
|
|
451
|
+
|
|
452
|
+
while (dir !== root) {
|
|
453
|
+
try {
|
|
454
|
+
await fs.promises.access(path.join(dir, '.autospec'));
|
|
455
|
+
return dir;
|
|
456
|
+
} catch {
|
|
457
|
+
dir = path.dirname(dir);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Simple in-memory cache for hooks
|
|
466
|
+
*/
|
|
467
|
+
export class HookCache {
|
|
468
|
+
constructor(maxSize = 100) {
|
|
469
|
+
this.cache = new Map();
|
|
470
|
+
this.maxSize = maxSize;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
get(key) {
|
|
474
|
+
const item = this.cache.get(key);
|
|
475
|
+
if (item && Date.now() - item.timestamp < item.ttl) {
|
|
476
|
+
return item.value;
|
|
477
|
+
}
|
|
478
|
+
this.cache.delete(key);
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
set(key, value, ttlMs = 60000) {
|
|
483
|
+
if (this.cache.size >= this.maxSize) {
|
|
484
|
+
const firstKey = this.cache.keys().next().value;
|
|
485
|
+
this.cache.delete(firstKey);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
this.cache.set(key, {
|
|
489
|
+
value,
|
|
490
|
+
timestamp: Date.now(),
|
|
491
|
+
ttl: ttlMs
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
clear() {
|
|
496
|
+
this.cache.clear();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* 全局单例缓存实例
|
|
502
|
+
* 供所有 hook 统一使用,避免各自实例化
|
|
503
|
+
*/
|
|
504
|
+
export const globalHookCache = new HookCache(200);
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Check if path matches any pattern
|
|
508
|
+
*/
|
|
509
|
+
export function matchesAnyPattern(path, patterns) {
|
|
510
|
+
return patterns.some(pattern => pattern.test(path));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Safe file existence check
|
|
515
|
+
*/
|
|
516
|
+
export async function fileExists(path) {
|
|
517
|
+
const { default: fs } = await import('fs');
|
|
518
|
+
try {
|
|
519
|
+
await fs.promises.access(path);
|
|
520
|
+
return true;
|
|
521
|
+
} catch {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Safe directory sync check
|
|
528
|
+
*/
|
|
529
|
+
export function directoryExistsSync(path) {
|
|
530
|
+
const fs = require('fs');
|
|
531
|
+
try {
|
|
532
|
+
return fs.existsSync(path) && fs.statSync(path).isDirectory();
|
|
533
|
+
} catch {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks generator for Claude Code integration
|
|
3
|
+
*
|
|
4
|
+
* Generates hooks configuration for deterministic guardrails
|
|
5
|
+
* (frozen zone, Layer 1, practice log, etc.)
|
|
6
|
+
*
|
|
7
|
+
* Note: Hook scripts are in plugins/hooks/ and are copied
|
|
8
|
+
* as part of the init process.
|
|
9
|
+
*
|
|
10
|
+
* Using inline bash command to support working from subdirectories.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { HOOKS_CONFIG } from '../config/hooks.config.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate hooks configuration using declarative config
|
|
19
|
+
* @param {string} claudeDir - Target .claude directory path
|
|
20
|
+
* @param {string} autospecDir - Target .autospec directory path
|
|
21
|
+
*/
|
|
22
|
+
export function generateHooksConfig(claudeDir, autospecDir) {
|
|
23
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
24
|
+
|
|
25
|
+
let settings = {};
|
|
26
|
+
if (fs.existsSync(settingsPath)) {
|
|
27
|
+
try {
|
|
28
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
29
|
+
} catch {
|
|
30
|
+
settings = {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!settings.hooks) settings.hooks = {};
|
|
35
|
+
|
|
36
|
+
for (const [eventName, hooks] of Object.entries(HOOKS_CONFIG)) {
|
|
37
|
+
if (!settings.hooks[eventName]) {
|
|
38
|
+
settings.hooks[eventName] = [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const hook of hooks) {
|
|
42
|
+
const hookName = extractHookName(hook.script);
|
|
43
|
+
// 使用内联 bash 命令,从当前目录向上查找 .autospec 目录
|
|
44
|
+
// 这样可以支持在子目录中工作
|
|
45
|
+
const command = buildHookCommand(hookName);
|
|
46
|
+
|
|
47
|
+
upsertHookByName(
|
|
48
|
+
settings.hooks[eventName],
|
|
49
|
+
hook.matcher || '',
|
|
50
|
+
command,
|
|
51
|
+
hook.name
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 构建 hook 命令 - 支持从子目录执行
|
|
61
|
+
* @param {string} hookName - Hook 名称
|
|
62
|
+
* @returns {string} 完整的命令字符串
|
|
63
|
+
*/
|
|
64
|
+
function buildHookCommand(hookName) {
|
|
65
|
+
// 使用内联 bash 命令:
|
|
66
|
+
// 1. 从当前目录向上查找 .autospec 目录
|
|
67
|
+
// 2. 找到后执行对应的 hook 脚本
|
|
68
|
+
return `bash -c 'DIR=$(pwd); while [ "$DIR" != "/" ] && [ ! -d "$DIR/.autospec" ]; do DIR=$(dirname "$DIR"); done; [ -d "$DIR/.autospec" ] && node "$DIR/.autospec/plugins/hooks/lib/hook-runner.js" ${hookName} || true'`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 从脚本路径提取 hook 名称
|
|
73
|
+
* 例如: 'plugins/hooks/pipeline-observer.js' -> 'pipeline-observer'
|
|
74
|
+
*/
|
|
75
|
+
function extractHookName(scriptPath) {
|
|
76
|
+
const filename = path.basename(scriptPath, '.js');
|
|
77
|
+
return filename;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add or update a named hook entry
|
|
82
|
+
* @param {Array} hookArray - Array of hook configurations
|
|
83
|
+
* @param {string} matcher - Tool matcher pattern
|
|
84
|
+
* @param {string} command - Hook command to execute
|
|
85
|
+
* @param {string} name - Hook name for identification
|
|
86
|
+
*/
|
|
87
|
+
function upsertHookByName(hookArray, matcher, command, name) {
|
|
88
|
+
const existing = hookArray.findIndex(h =>
|
|
89
|
+
h.hooks?.some(hook =>
|
|
90
|
+
(typeof hook === 'string' ? hook : hook.command || '').includes(name)
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const entry = {
|
|
95
|
+
matcher,
|
|
96
|
+
hooks: [{ type: 'command', command }]
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (existing >= 0) {
|
|
100
|
+
hookArray[existing] = entry;
|
|
101
|
+
} else {
|
|
102
|
+
hookArray.push(entry);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Note: copyHookScripts function has been removed.
|
|
108
|
+
* Hook scripts are now in plugins/hooks/ and are copied
|
|
109
|
+
* as part of the init process.
|
|
110
|
+
*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generators module
|
|
3
|
+
*
|
|
4
|
+
* Exports all generator functions for Claude Code integration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { generateSkills } from './skills.js';
|
|
9
|
+
import { generateAgents } from './agents.js';
|
|
10
|
+
import { generateHooksConfig } from './hooks.js';
|
|
11
|
+
|
|
12
|
+
export { generateSkills } from './skills.js';
|
|
13
|
+
export { generateAgents } from './agents.js';
|
|
14
|
+
export { generateHooksConfig } from './hooks.js';
|
|
15
|
+
|
|
16
|
+
// Re-export commands module
|
|
17
|
+
export {
|
|
18
|
+
generateCommands,
|
|
19
|
+
clearTemplateCache,
|
|
20
|
+
getTemplateCacheStats,
|
|
21
|
+
getInternalCommandNames,
|
|
22
|
+
generateInternalCommands
|
|
23
|
+
} from './commands.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate all Claude Code integration files (skills + agents + hooks)
|
|
27
|
+
* This is the main entry point for generating Claude Code integration.
|
|
28
|
+
*
|
|
29
|
+
* Note: Hook scripts are in plugins/hooks/ and are copied
|
|
30
|
+
* as part of the init process.
|
|
31
|
+
*/
|
|
32
|
+
export function generateIntegration(targetDir) {
|
|
33
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
34
|
+
const autospecDir = path.join(targetDir, '.autospec');
|
|
35
|
+
|
|
36
|
+
generateSkills(claudeDir);
|
|
37
|
+
generateAgents(claudeDir);
|
|
38
|
+
generateHooksConfig(claudeDir, autospecDir);
|
|
39
|
+
}
|