scene-capability-engine 3.0.0
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/CHANGELOG.md +2513 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/README.zh.md +630 -0
- package/bin/kiro-spec-engine.js +796 -0
- package/bin/kse.js +3 -0
- package/bin/sce.js +3 -0
- package/bin/sco.js +3 -0
- package/docs/331-poc-adaptation-roadmap.md +156 -0
- package/docs/331-poc-dual-track-integration-guide.md +120 -0
- package/docs/331-poc-weekly-delivery-checklist.md +52 -0
- package/docs/OFFLINE_INSTALL.md +96 -0
- package/docs/README.md +279 -0
- package/docs/adopt-migration-guide.md +599 -0
- package/docs/adoption-guide.md +616 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/architecture.md +733 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
- package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
- package/docs/autonomous-control-guide.md +851 -0
- package/docs/command-reference.md +1368 -0
- package/docs/community.md +115 -0
- package/docs/cross-tool-guide.md +555 -0
- package/docs/developer-guide.md +619 -0
- package/docs/document-governance.md +865 -0
- package/docs/environment-management-guide.md +526 -0
- package/docs/examples/add-export-command/design.md +194 -0
- package/docs/examples/add-export-command/requirements.md +110 -0
- package/docs/examples/add-export-command/tasks.md +88 -0
- package/docs/examples/add-rest-api/design.md +855 -0
- package/docs/examples/add-rest-api/requirements.md +323 -0
- package/docs/examples/add-rest-api/tasks.md +355 -0
- package/docs/examples/add-user-dashboard/design.md +192 -0
- package/docs/examples/add-user-dashboard/requirements.md +143 -0
- package/docs/examples/add-user-dashboard/tasks.md +91 -0
- package/docs/faq.md +697 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
- package/docs/images/wechat-qr.png +0 -0
- package/docs/integration-modes.md +529 -0
- package/docs/integration-philosophy.md +313 -0
- package/docs/knowledge-management-guide.md +263 -0
- package/docs/manual-workflows-guide.md +418 -0
- package/docs/moqui-capability-matrix.md +73 -0
- package/docs/moqui-template-core-library-playbook.md +109 -0
- package/docs/multi-agent-coordination-guide.md +553 -0
- package/docs/multi-repo-management-guide.md +1344 -0
- package/docs/quick-start-with-ai-tools.md +375 -0
- package/docs/quick-start.md +146 -0
- package/docs/release-checklist.md +121 -0
- package/docs/releases/README.md +13 -0
- package/docs/releases/v1.46.2-validation.md +45 -0
- package/docs/releases/v1.46.2.md +50 -0
- package/docs/scene-runtime-guide.md +347 -0
- package/docs/spec-collaboration-guide.md +369 -0
- package/docs/spec-locking-guide.md +225 -0
- package/docs/spec-numbering-guide.md +348 -0
- package/docs/spec-workflow.md +519 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/team-collaboration-guide.md +465 -0
- package/docs/testing-strategy.md +272 -0
- package/docs/tools/claude-guide.md +654 -0
- package/docs/tools/cursor-guide.md +706 -0
- package/docs/tools/generic-guide.md +446 -0
- package/docs/tools/kiro-guide.md +308 -0
- package/docs/tools/vscode-guide.md +445 -0
- package/docs/tools/windsurf-guide.md +391 -0
- package/docs/troubleshooting.md +1135 -0
- package/docs/upgrade-guide.md +639 -0
- package/docs/value-observability-guide.md +127 -0
- package/docs/zh/README.md +341 -0
- package/docs/zh/quick-start.md +764 -0
- package/docs/zh/release-checklist.md +121 -0
- package/docs/zh/releases/README.md +13 -0
- package/docs/zh/releases/v1.46.2-validation.md +45 -0
- package/docs/zh/releases/v1.46.2.md +50 -0
- package/docs/zh/spec-numbering-guide.md +348 -0
- package/docs/zh/tools/claude-guide.md +349 -0
- package/docs/zh/tools/cursor-guide.md +281 -0
- package/docs/zh/tools/generic-guide.md +499 -0
- package/docs/zh/tools/kiro-guide.md +342 -0
- package/docs/zh/tools/vscode-guide.md +449 -0
- package/docs/zh/tools/windsurf-guide.md +378 -0
- package/docs/zh/value-observability-guide.md +127 -0
- package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
- package/lib/adoption/adoption-logger.js +487 -0
- package/lib/adoption/adoption-strategy.js +538 -0
- package/lib/adoption/backup-manager.js +420 -0
- package/lib/adoption/conflict-resolver.js +410 -0
- package/lib/adoption/detection-engine.js +275 -0
- package/lib/adoption/diff-viewer.js +226 -0
- package/lib/adoption/error-formatter.js +509 -0
- package/lib/adoption/file-classifier.js +385 -0
- package/lib/adoption/progress-reporter.js +534 -0
- package/lib/adoption/smart-orchestrator.js +470 -0
- package/lib/adoption/strategy-selector.js +218 -0
- package/lib/adoption/summary-generator.js +493 -0
- package/lib/adoption/template-sync.js +605 -0
- package/lib/auto/autonomous-engine.js +485 -0
- package/lib/auto/checkpoint-manager.js +300 -0
- package/lib/auto/close-loop-runner.js +2476 -0
- package/lib/auto/config-schema.js +176 -0
- package/lib/auto/decision-engine.js +344 -0
- package/lib/auto/error-recovery-manager.js +580 -0
- package/lib/auto/goal-decomposer.js +278 -0
- package/lib/auto/progress-tracker.js +502 -0
- package/lib/auto/safety-manager.js +186 -0
- package/lib/auto/semantic-decomposer.js +137 -0
- package/lib/auto/state-manager.js +126 -0
- package/lib/auto/task-queue-manager.js +340 -0
- package/lib/backup/backup-system.js +372 -0
- package/lib/backup/selective-backup.js +207 -0
- package/lib/collab/agent-registry.js +240 -0
- package/lib/collab/collab-manager.js +285 -0
- package/lib/collab/contract-manager.js +320 -0
- package/lib/collab/coordinator.js +370 -0
- package/lib/collab/dependency-manager.js +280 -0
- package/lib/collab/index.js +20 -0
- package/lib/collab/integration-manager.js +202 -0
- package/lib/collab/merge-coordinator.js +252 -0
- package/lib/collab/metadata-manager.js +233 -0
- package/lib/collab/multi-agent-config.js +120 -0
- package/lib/collab/spec-lifecycle-manager.js +304 -0
- package/lib/collab/sync-barrier.js +88 -0
- package/lib/collab/visualizer.js +208 -0
- package/lib/commands/adopt.js +749 -0
- package/lib/commands/auto.js +19559 -0
- package/lib/commands/collab.js +275 -0
- package/lib/commands/context.js +99 -0
- package/lib/commands/docs.js +808 -0
- package/lib/commands/doctor.js +273 -0
- package/lib/commands/env.js +420 -0
- package/lib/commands/knowledge.js +309 -0
- package/lib/commands/lock.js +235 -0
- package/lib/commands/ops.js +409 -0
- package/lib/commands/orchestrate.js +446 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/repo.js +118 -0
- package/lib/commands/rollback.js +219 -0
- package/lib/commands/scene.js +15549 -0
- package/lib/commands/spec-bootstrap.js +147 -0
- package/lib/commands/spec-gate.js +157 -0
- package/lib/commands/spec-pipeline.js +205 -0
- package/lib/commands/status.js +321 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/templates.js +654 -0
- package/lib/commands/upgrade.js +231 -0
- package/lib/commands/value.js +569 -0
- package/lib/commands/watch.js +684 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace-multi.js +325 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/data/moqui-capability-lexicon.json +45 -0
- package/lib/environment/backup-system.js +189 -0
- package/lib/environment/environment-manager.js +379 -0
- package/lib/environment/environment-registry.js +168 -0
- package/lib/gitignore/gitignore-backup.js +229 -0
- package/lib/gitignore/gitignore-detector.js +239 -0
- package/lib/gitignore/gitignore-integration.js +267 -0
- package/lib/gitignore/gitignore-transformer.js +193 -0
- package/lib/gitignore/layered-rules-template.js +42 -0
- package/lib/governance/archive-tool.js +284 -0
- package/lib/governance/cleanup-tool.js +237 -0
- package/lib/governance/config-manager.js +186 -0
- package/lib/governance/diagnostic-engine.js +271 -0
- package/lib/governance/doc-reference-checker.js +200 -0
- package/lib/governance/execution-logger.js +243 -0
- package/lib/governance/file-scanner.js +285 -0
- package/lib/governance/hooks-manager.js +333 -0
- package/lib/governance/reporter.js +337 -0
- package/lib/governance/validation-engine.js +181 -0
- package/lib/i18n.js +79 -0
- package/lib/knowledge/entry-manager.js +208 -0
- package/lib/knowledge/index-manager.js +261 -0
- package/lib/knowledge/knowledge-manager.js +273 -0
- package/lib/knowledge/template-manager.js +191 -0
- package/lib/lock/index.js +21 -0
- package/lib/lock/lock-file.js +192 -0
- package/lib/lock/lock-manager.js +321 -0
- package/lib/lock/machine-identifier.js +135 -0
- package/lib/lock/steering-file-lock.js +207 -0
- package/lib/lock/task-lock-manager.js +345 -0
- package/lib/operations/audit-logger.js +293 -0
- package/lib/operations/feedback-manager.js +1147 -0
- package/lib/operations/index.js +23 -0
- package/lib/operations/models/index.js +170 -0
- package/lib/operations/operations-manager.js +151 -0
- package/lib/operations/operations-validator.js +280 -0
- package/lib/operations/permission-manager.js +354 -0
- package/lib/operations/template-loader.js +143 -0
- package/lib/orchestrator/agent-spawner.js +629 -0
- package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
- package/lib/orchestrator/index.js +19 -0
- package/lib/orchestrator/orchestration-engine.js +1270 -0
- package/lib/orchestrator/orchestrator-config.js +173 -0
- package/lib/orchestrator/status-monitor.js +591 -0
- package/lib/python-checker.js +209 -0
- package/lib/repo/config-manager.js +580 -0
- package/lib/repo/errors/config-error.js +13 -0
- package/lib/repo/errors/git-error.js +15 -0
- package/lib/repo/errors/repo-error.js +14 -0
- package/lib/repo/git-operations.js +181 -0
- package/lib/repo/handlers/.gitkeep +1 -0
- package/lib/repo/handlers/exec-handler.js +155 -0
- package/lib/repo/handlers/health-handler.js +169 -0
- package/lib/repo/handlers/init-handler.js +197 -0
- package/lib/repo/handlers/status-handler.js +176 -0
- package/lib/repo/output-formatter.js +184 -0
- package/lib/repo/path-resolver.js +178 -0
- package/lib/repo/repo-manager.js +514 -0
- package/lib/scene-runtime/audit-emitter.js +59 -0
- package/lib/scene-runtime/binding-plugin-loader.js +351 -0
- package/lib/scene-runtime/binding-registry.js +349 -0
- package/lib/scene-runtime/eval-bridge.js +44 -0
- package/lib/scene-runtime/index.js +19 -0
- package/lib/scene-runtime/moqui-adapter.js +620 -0
- package/lib/scene-runtime/moqui-client.js +606 -0
- package/lib/scene-runtime/moqui-extractor.js +2029 -0
- package/lib/scene-runtime/plan-compiler.js +208 -0
- package/lib/scene-runtime/policy-gate.js +58 -0
- package/lib/scene-runtime/runtime-executor.js +358 -0
- package/lib/scene-runtime/scene-loader.js +96 -0
- package/lib/scene-runtime/scene-ontology.js +959 -0
- package/lib/scene-runtime/scene-template-linter.js +852 -0
- package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
- package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
- package/lib/spec/bootstrap/context-collector.js +48 -0
- package/lib/spec/bootstrap/draft-generator.js +158 -0
- package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
- package/lib/spec/bootstrap/trace-emitter.js +59 -0
- package/lib/spec/multi-spec-orchestrate.js +93 -0
- package/lib/spec/pipeline/constants.js +6 -0
- package/lib/spec/pipeline/stage-adapters.js +118 -0
- package/lib/spec/pipeline/stage-runner.js +146 -0
- package/lib/spec/pipeline/state-store.js +119 -0
- package/lib/spec-gate/engine/gate-engine.js +165 -0
- package/lib/spec-gate/policy/default-policy.js +22 -0
- package/lib/spec-gate/policy/policy-loader.js +103 -0
- package/lib/spec-gate/result-emitter.js +81 -0
- package/lib/spec-gate/rules/default-rules.js +156 -0
- package/lib/spec-gate/rules/rule-registry.js +51 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/compliance-auto-fixer.js +204 -0
- package/lib/steering/compliance-cache.js +99 -0
- package/lib/steering/compliance-error-reporter.js +70 -0
- package/lib/steering/context-sync-manager.js +273 -0
- package/lib/steering/index.js +92 -0
- package/lib/steering/spec-steering.js +230 -0
- package/lib/steering/steering-compliance-checker.js +73 -0
- package/lib/steering/steering-loader.js +144 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/index.js +12 -0
- package/lib/task/task-claimer.js +489 -0
- package/lib/task/task-status-store.js +418 -0
- package/lib/templates/cache-manager.js +440 -0
- package/lib/templates/content-generalizer.js +247 -0
- package/lib/templates/frontmatter-generator.js +128 -0
- package/lib/templates/git-handler.js +471 -0
- package/lib/templates/metadata-collector.js +328 -0
- package/lib/templates/path-utils.js +144 -0
- package/lib/templates/registry-parser.js +505 -0
- package/lib/templates/spec-reader.js +216 -0
- package/lib/templates/template-applicator.js +249 -0
- package/lib/templates/template-creator.js +256 -0
- package/lib/templates/template-error.js +143 -0
- package/lib/templates/template-exporter.js +502 -0
- package/lib/templates/template-manager.js +782 -0
- package/lib/templates/template-validator.js +361 -0
- package/lib/upgrade/migration-engine.js +382 -0
- package/lib/upgrade/migrations/.gitkeep +52 -0
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
- package/lib/utils/file-diff.js +177 -0
- package/lib/utils/fs-utils.js +274 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/utils/validation.js +324 -0
- package/lib/value/gate-summary-emitter.js +99 -0
- package/lib/value/metric-contract-loader.js +210 -0
- package/lib/value/risk-evaluator.js +117 -0
- package/lib/value/weekly-snapshot-builder.js +61 -0
- package/lib/version/version-checker.js +156 -0
- package/lib/version/version-manager.js +327 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/multi/global-config.js +150 -0
- package/lib/workspace/multi/index.js +22 -0
- package/lib/workspace/multi/path-utils.js +173 -0
- package/lib/workspace/multi/workspace-context-resolver.js +244 -0
- package/lib/workspace/multi/workspace-registry.js +196 -0
- package/lib/workspace/multi/workspace-state-manager.js +537 -0
- package/lib/workspace/multi/workspace.js +90 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/locales/en.json +114 -0
- package/locales/zh.json +114 -0
- package/package.json +102 -0
- package/template/.kiro/README.md +247 -0
- package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
- package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
- package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
- package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
- package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
- package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
- package/template/.kiro/steering/ENVIRONMENT.md +35 -0
- package/template/.kiro/steering/RULES_GUIDE.md +46 -0
- package/template/.kiro/templates/operations/default/change-impact.md +112 -0
- package/template/.kiro/templates/operations/default/deployment.md +91 -0
- package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
- package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
- package/template/.kiro/templates/operations/default/monitoring.md +135 -0
- package/template/.kiro/templates/operations/default/operations.md +135 -0
- package/template/.kiro/templates/operations/default/rollback.md +143 -0
- package/template/.kiro/templates/operations/default/tools.yaml +364 -0
- package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
- package/template/README.md +111 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ConfigError = require('./errors/config-error');
|
|
4
|
+
const PathResolver = require('./path-resolver');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ConfigManager - Manages the project-repos.json configuration file
|
|
8
|
+
*
|
|
9
|
+
* Handles loading, saving, and validating repository configuration with
|
|
10
|
+
* JSON schema validation and comprehensive error checking.
|
|
11
|
+
*/
|
|
12
|
+
class ConfigManager {
|
|
13
|
+
/**
|
|
14
|
+
* Create a new ConfigManager
|
|
15
|
+
* @param {string} projectRoot - The project root directory
|
|
16
|
+
*/
|
|
17
|
+
constructor(projectRoot) {
|
|
18
|
+
if (!projectRoot) {
|
|
19
|
+
throw new Error('Project root is required');
|
|
20
|
+
}
|
|
21
|
+
this.projectRoot = projectRoot;
|
|
22
|
+
this.pathResolver = new PathResolver();
|
|
23
|
+
this.configFileName = 'project-repos.json';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the configuration file path
|
|
28
|
+
* @returns {string} Absolute path to the configuration file
|
|
29
|
+
*/
|
|
30
|
+
getConfigPath() {
|
|
31
|
+
return path.join(this.projectRoot, '.kiro', this.configFileName);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if configuration file exists
|
|
36
|
+
* @returns {Promise<boolean>} True if configuration file exists
|
|
37
|
+
*/
|
|
38
|
+
async configExists() {
|
|
39
|
+
try {
|
|
40
|
+
await fs.access(this.getConfigPath());
|
|
41
|
+
return true;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load and validate configuration from disk
|
|
49
|
+
* @param {Object} options - Load options
|
|
50
|
+
* @param {boolean} options.skipFilesystemValidation - Skip filesystem validation (for testing)
|
|
51
|
+
* @returns {Promise<Object>} The loaded and validated configuration
|
|
52
|
+
* @throws {ConfigError} If file is missing, invalid JSON, or validation fails
|
|
53
|
+
*/
|
|
54
|
+
async loadConfig(options = {}) {
|
|
55
|
+
const { skipFilesystemValidation = false } = options;
|
|
56
|
+
const configPath = this.getConfigPath();
|
|
57
|
+
|
|
58
|
+
// Check if file exists
|
|
59
|
+
if (!(await this.configExists())) {
|
|
60
|
+
throw new ConfigError(
|
|
61
|
+
'Configuration file not found. Run "kse repo init" to create it.',
|
|
62
|
+
{ path: configPath }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Read and parse JSON
|
|
67
|
+
let configData;
|
|
68
|
+
try {
|
|
69
|
+
const fileContent = await fs.readFile(configPath, 'utf8');
|
|
70
|
+
configData = JSON.parse(fileContent);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof SyntaxError) {
|
|
73
|
+
throw new ConfigError(
|
|
74
|
+
'Configuration file contains invalid JSON',
|
|
75
|
+
{ path: configPath, parseError: error.message }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
throw new ConfigError(
|
|
79
|
+
`Failed to read configuration file: ${error.message}`,
|
|
80
|
+
{ path: configPath }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate configuration structure
|
|
85
|
+
const validation = this.validateConfig(configData, { validateFilesystem: false });
|
|
86
|
+
if (!validation.valid) {
|
|
87
|
+
throw new ConfigError(
|
|
88
|
+
'Configuration validation failed',
|
|
89
|
+
{ errors: validation.errors }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Perform filesystem validation for each repository (unless skipped)
|
|
94
|
+
if (!skipFilesystemValidation) {
|
|
95
|
+
const filesystemErrors = [];
|
|
96
|
+
for (const repo of configData.repositories) {
|
|
97
|
+
if (repo.path && repo.name) {
|
|
98
|
+
const pathErrors = await this._validateRepositoryPath(repo.path, repo.name);
|
|
99
|
+
filesystemErrors.push(...pathErrors);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (filesystemErrors.length > 0) {
|
|
104
|
+
throw new ConfigError(
|
|
105
|
+
'Repository path validation failed',
|
|
106
|
+
{ errors: filesystemErrors }
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return configData;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Save configuration to disk
|
|
116
|
+
* @param {Object} config - The configuration object to save
|
|
117
|
+
* @returns {Promise<void>}
|
|
118
|
+
* @throws {ConfigError} If validation fails or save fails
|
|
119
|
+
*/
|
|
120
|
+
async saveConfig(config) {
|
|
121
|
+
// Validate before saving
|
|
122
|
+
const validation = this.validateConfig(config);
|
|
123
|
+
if (!validation.valid) {
|
|
124
|
+
throw new ConfigError(
|
|
125
|
+
'Cannot save invalid configuration',
|
|
126
|
+
{ errors: validation.errors }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const configPath = this.getConfigPath();
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// Ensure .kiro directory exists
|
|
134
|
+
const kiroDir = path.dirname(configPath);
|
|
135
|
+
await fs.mkdir(kiroDir, { recursive: true });
|
|
136
|
+
|
|
137
|
+
// Write configuration with pretty formatting
|
|
138
|
+
const jsonContent = JSON.stringify(config, null, 2);
|
|
139
|
+
await fs.writeFile(configPath, jsonContent, 'utf8');
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new ConfigError(
|
|
142
|
+
`Failed to save configuration: ${error.message}`,
|
|
143
|
+
{ path: configPath }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate configuration structure and content
|
|
150
|
+
* @param {Object} config - The configuration object to validate
|
|
151
|
+
* @param {Object} options - Validation options
|
|
152
|
+
* @param {boolean} options.validateFilesystem - Whether to validate paths on filesystem (default: false)
|
|
153
|
+
* @returns {{valid: boolean, errors: string[]}} Validation result
|
|
154
|
+
*/
|
|
155
|
+
validateConfig(config, options = {}) {
|
|
156
|
+
const errors = [];
|
|
157
|
+
|
|
158
|
+
// Check if config is an object
|
|
159
|
+
if (!config || typeof config !== 'object') {
|
|
160
|
+
return { valid: false, errors: ['Configuration must be an object'] };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate version field (optional, defaults to '1.0')
|
|
164
|
+
const version = config.version || '1.0';
|
|
165
|
+
if (typeof version !== 'string') {
|
|
166
|
+
errors.push('Field "version" must be a string');
|
|
167
|
+
} else if (!this._isSupportedVersion(version)) {
|
|
168
|
+
errors.push(
|
|
169
|
+
`Unsupported configuration version: ${version}. ` +
|
|
170
|
+
'Please upgrade to the latest version of kse.'
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Validate repositories array
|
|
175
|
+
if (!config.repositories) {
|
|
176
|
+
errors.push('Missing required field: repositories');
|
|
177
|
+
return { valid: false, errors }; // Can't continue without repositories
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!Array.isArray(config.repositories)) {
|
|
181
|
+
errors.push('Field "repositories" must be an array');
|
|
182
|
+
return { valid: false, errors };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Validate each repository
|
|
186
|
+
const repoNames = new Set();
|
|
187
|
+
const repoPaths = [];
|
|
188
|
+
|
|
189
|
+
config.repositories.forEach((repo, index) => {
|
|
190
|
+
const repoErrors = this._validateRepository(repo, index, config.repositories, options.validateFilesystem);
|
|
191
|
+
errors.push(...repoErrors);
|
|
192
|
+
|
|
193
|
+
// Collect names and paths for duplicate checking
|
|
194
|
+
if (repo.name) {
|
|
195
|
+
if (repoNames.has(repo.name)) {
|
|
196
|
+
errors.push(`Duplicate repository name: "${repo.name}"`);
|
|
197
|
+
}
|
|
198
|
+
repoNames.add(repo.name);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (repo.path) {
|
|
202
|
+
repoPaths.push(repo.path);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Validate parent references
|
|
207
|
+
const parentErrors = this._validateParentReferences(config.repositories);
|
|
208
|
+
errors.push(...parentErrors);
|
|
209
|
+
|
|
210
|
+
// Validate no duplicate or overlapping paths
|
|
211
|
+
if (repoPaths.length > 0) {
|
|
212
|
+
// Check if nested mode is enabled in settings
|
|
213
|
+
const nestedMode = config.settings && config.settings.nestedMode === true;
|
|
214
|
+
const pathValidation = this._validatePaths(repoPaths, nestedMode);
|
|
215
|
+
errors.push(...pathValidation.errors);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Validate optional groups field
|
|
219
|
+
if (config.groups && typeof config.groups !== 'object') {
|
|
220
|
+
errors.push('Field "groups" must be an object');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Validate optional settings field
|
|
224
|
+
if (config.settings && typeof config.settings !== 'object') {
|
|
225
|
+
errors.push('Field "settings" must be an object');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
valid: errors.length === 0,
|
|
230
|
+
errors
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Validate a single repository configuration
|
|
236
|
+
* @private
|
|
237
|
+
* @param {Object} repo - Repository configuration object
|
|
238
|
+
* @param {number} index - Repository index in array
|
|
239
|
+
* @param {Array<Object>} allRepos - All repositories (for parent validation)
|
|
240
|
+
* @returns {string[]} Array of validation errors
|
|
241
|
+
*/
|
|
242
|
+
_validateRepository(repo, index, allRepos = []) {
|
|
243
|
+
const errors = [];
|
|
244
|
+
const prefix = `Repository at index ${index}`;
|
|
245
|
+
|
|
246
|
+
// Check if repo is an object
|
|
247
|
+
if (!repo || typeof repo !== 'object') {
|
|
248
|
+
return [`${prefix}: must be an object`];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Validate required fields
|
|
252
|
+
if (!repo.name) {
|
|
253
|
+
errors.push(`${prefix}: missing required field "name"`);
|
|
254
|
+
} else if (typeof repo.name !== 'string') {
|
|
255
|
+
errors.push(`${prefix}: field "name" must be a string`);
|
|
256
|
+
} else if (!this._isValidRepoName(repo.name)) {
|
|
257
|
+
errors.push(
|
|
258
|
+
`${prefix}: invalid repository name "${repo.name}". ` +
|
|
259
|
+
'Names must contain only alphanumeric characters, hyphens, underscores, and dots.'
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!repo.path) {
|
|
264
|
+
errors.push(`${prefix}: missing required field "path"`);
|
|
265
|
+
} else if (typeof repo.path !== 'string') {
|
|
266
|
+
errors.push(`${prefix}: field "path" must be a string`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Validate optional fields
|
|
270
|
+
if (repo.remote !== undefined && repo.remote !== null && typeof repo.remote !== 'string') {
|
|
271
|
+
errors.push(`${prefix}: field "remote" must be a string or null`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (repo.defaultBranch !== undefined && typeof repo.defaultBranch !== 'string') {
|
|
275
|
+
errors.push(`${prefix}: field "defaultBranch" must be a string`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (repo.description !== undefined && typeof repo.description !== 'string') {
|
|
279
|
+
errors.push(`${prefix}: field "description" must be a string`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (repo.tags !== undefined) {
|
|
283
|
+
if (!Array.isArray(repo.tags)) {
|
|
284
|
+
errors.push(`${prefix}: field "tags" must be an array`);
|
|
285
|
+
} else if (!repo.tags.every(tag => typeof tag === 'string')) {
|
|
286
|
+
errors.push(`${prefix}: all tags must be strings`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (repo.group !== undefined && typeof repo.group !== 'string') {
|
|
291
|
+
errors.push(`${prefix}: field "group" must be a string`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Validate parent field (NEW)
|
|
295
|
+
if (repo.parent !== undefined && repo.parent !== null) {
|
|
296
|
+
if (typeof repo.parent !== 'string') {
|
|
297
|
+
errors.push(`${prefix}: field "parent" must be a string or null`);
|
|
298
|
+
}
|
|
299
|
+
// Note: Parent reference validation is done separately in _validateParentReferences
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return errors;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Normalize path for comparison
|
|
307
|
+
* @private
|
|
308
|
+
* @param {string} pathStr - Path to normalize
|
|
309
|
+
* @returns {string} Normalized path
|
|
310
|
+
*/
|
|
311
|
+
_normalizePath(pathStr) {
|
|
312
|
+
if (!pathStr) return '';
|
|
313
|
+
|
|
314
|
+
// Convert backslashes to forward slashes
|
|
315
|
+
let normalized = pathStr.replace(/\\/g, '/');
|
|
316
|
+
|
|
317
|
+
// Remove trailing slashes
|
|
318
|
+
normalized = normalized.replace(/\/+$/, '');
|
|
319
|
+
|
|
320
|
+
// Remove leading './'
|
|
321
|
+
normalized = normalized.replace(/^\.\//, '');
|
|
322
|
+
|
|
323
|
+
// Handle '.' as current directory
|
|
324
|
+
if (normalized === '.') {
|
|
325
|
+
normalized = '';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return normalized;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Validate parent references in repositories
|
|
333
|
+
* @private
|
|
334
|
+
* @param {Array<Object>} repos - All repositories
|
|
335
|
+
* @returns {string[]} Array of validation errors
|
|
336
|
+
*/
|
|
337
|
+
_validateParentReferences(repos) {
|
|
338
|
+
const errors = [];
|
|
339
|
+
|
|
340
|
+
// Build a map of repository paths for O(1) lookup
|
|
341
|
+
const pathMap = new Map();
|
|
342
|
+
repos.forEach(repo => {
|
|
343
|
+
if (repo.path) {
|
|
344
|
+
const normalizedPath = this._normalizePath(repo.path);
|
|
345
|
+
pathMap.set(normalizedPath, repo);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Check each repository's parent reference
|
|
350
|
+
repos.forEach((repo, index) => {
|
|
351
|
+
if (repo.parent) {
|
|
352
|
+
// Check if parent path exists (using normalized paths)
|
|
353
|
+
const normalizedParent = this._normalizePath(repo.parent);
|
|
354
|
+
if (!pathMap.has(normalizedParent)) {
|
|
355
|
+
errors.push(
|
|
356
|
+
`Repository "${repo.name}" (index ${index}): ` +
|
|
357
|
+
`parent path "${repo.parent}" does not reference an existing repository. ` +
|
|
358
|
+
`Available paths: ${Array.from(pathMap.keys()).join(', ')}`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Detect circular parent references using depth-first search
|
|
365
|
+
const visited = new Set();
|
|
366
|
+
const recursionStack = new Set();
|
|
367
|
+
|
|
368
|
+
const detectCycle = (normalizedPath, path = []) => {
|
|
369
|
+
if (recursionStack.has(normalizedPath)) {
|
|
370
|
+
// Found a cycle
|
|
371
|
+
const cycleStart = path.indexOf(normalizedPath);
|
|
372
|
+
const cycle = path.slice(cycleStart).concat(normalizedPath);
|
|
373
|
+
errors.push(
|
|
374
|
+
`Circular parent reference detected: ${cycle.join(' → ')}`
|
|
375
|
+
);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (visited.has(normalizedPath)) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
visited.add(normalizedPath);
|
|
384
|
+
recursionStack.add(normalizedPath);
|
|
385
|
+
path.push(normalizedPath);
|
|
386
|
+
|
|
387
|
+
const repo = pathMap.get(normalizedPath);
|
|
388
|
+
if (repo && repo.parent) {
|
|
389
|
+
const normalizedParent = this._normalizePath(repo.parent);
|
|
390
|
+
detectCycle(normalizedParent, path);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
path.pop();
|
|
394
|
+
recursionStack.delete(normalizedPath);
|
|
395
|
+
return false;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Check for cycles starting from each repository (use normalized paths)
|
|
399
|
+
repos.forEach(repo => {
|
|
400
|
+
if (repo.path) {
|
|
401
|
+
const normalizedPath = this._normalizePath(repo.path);
|
|
402
|
+
if (!visited.has(normalizedPath)) {
|
|
403
|
+
detectCycle(normalizedPath, []);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return errors;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Validate repository paths for duplicates and overlaps
|
|
413
|
+
* @private
|
|
414
|
+
* @param {string[]} paths - Array of repository paths
|
|
415
|
+
* @param {boolean} allowNested - Whether to allow nested paths (for nested mode)
|
|
416
|
+
* @returns {{errors: string[]}} Validation result
|
|
417
|
+
*/
|
|
418
|
+
_validatePaths(paths, allowNested = false) {
|
|
419
|
+
const errors = [];
|
|
420
|
+
|
|
421
|
+
// Resolve all paths to absolute for comparison
|
|
422
|
+
const resolvedPaths = paths.map(p => {
|
|
423
|
+
try {
|
|
424
|
+
return this.pathResolver.resolvePath(p, this.projectRoot);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
// If path resolution fails, return the original path
|
|
427
|
+
// The error will be caught during actual operations
|
|
428
|
+
return p;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Check for duplicates and overlaps
|
|
433
|
+
const pathValidation = this.pathResolver.validateNoOverlap(resolvedPaths);
|
|
434
|
+
|
|
435
|
+
if (!pathValidation.valid) {
|
|
436
|
+
// Categorize errors into duplicate and nested types
|
|
437
|
+
const duplicateErrors = [];
|
|
438
|
+
const nestedErrors = [];
|
|
439
|
+
|
|
440
|
+
pathValidation.errors.forEach(error => {
|
|
441
|
+
if (error.includes('Duplicate path found')) {
|
|
442
|
+
duplicateErrors.push(error);
|
|
443
|
+
} else if (error.includes('nested within')) {
|
|
444
|
+
nestedErrors.push(error);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Always report duplicate paths (always invalid)
|
|
449
|
+
errors.push(...duplicateErrors);
|
|
450
|
+
|
|
451
|
+
// Report nested paths only if nestedMode is not enabled
|
|
452
|
+
if (nestedErrors.length > 0 && !allowNested) {
|
|
453
|
+
errors.push(...nestedErrors);
|
|
454
|
+
// Add helpful hint about enabling nestedMode
|
|
455
|
+
errors.push(
|
|
456
|
+
'Hint: Enable nestedMode in settings to allow nested repositories: ' +
|
|
457
|
+
'{ "settings": { "nestedMode": true } }'
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return { errors };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Check if repository name is valid
|
|
467
|
+
* @private
|
|
468
|
+
* @param {string} name - Repository name to validate
|
|
469
|
+
* @returns {boolean} True if name is valid
|
|
470
|
+
*/
|
|
471
|
+
_isValidRepoName(name) {
|
|
472
|
+
// Allow alphanumeric, hyphens, underscores, dots, and names starting with dots (hidden directories)
|
|
473
|
+
// Must not contain spaces or special characters like @, #, $, etc.
|
|
474
|
+
// Examples: "backend", ".github", ".kiro", "my-repo", "repo_name", "repo.name"
|
|
475
|
+
const validPattern = /^\.?[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
476
|
+
return validPattern.test(name);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Check if a path is a valid Git repository
|
|
481
|
+
* @private
|
|
482
|
+
* @param {string} dirPath - Directory path to check
|
|
483
|
+
* @returns {Promise<boolean>} True if path contains a .git directory (not file)
|
|
484
|
+
*/
|
|
485
|
+
async _isGitRepository(dirPath) {
|
|
486
|
+
try {
|
|
487
|
+
const gitPath = path.join(dirPath, '.git');
|
|
488
|
+
const stats = await fs.stat(gitPath);
|
|
489
|
+
// Return true only if .git is a directory (not a file, which indicates a Git worktree)
|
|
490
|
+
return stats.isDirectory();
|
|
491
|
+
} catch (error) {
|
|
492
|
+
// Path doesn't exist, no permissions, or other error
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Validate that a repository path exists and is a valid Git repository
|
|
499
|
+
* @private
|
|
500
|
+
* @param {string} repoPath - Repository path to validate
|
|
501
|
+
* @param {string} repoName - Repository name (for error messages)
|
|
502
|
+
* @returns {Promise<string[]>} Array of validation errors
|
|
503
|
+
*/
|
|
504
|
+
async _validateRepositoryPath(repoPath, repoName) {
|
|
505
|
+
const errors = [];
|
|
506
|
+
|
|
507
|
+
// Resolve path relative to project root
|
|
508
|
+
const absolutePath = path.isAbsolute(repoPath)
|
|
509
|
+
? repoPath
|
|
510
|
+
: path.join(this.projectRoot, repoPath);
|
|
511
|
+
|
|
512
|
+
// Check if path exists
|
|
513
|
+
try {
|
|
514
|
+
await fs.access(absolutePath);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
errors.push(
|
|
517
|
+
`Repository "${repoName}": path "${repoPath}" does not exist. ` +
|
|
518
|
+
'Please check the path is correct.'
|
|
519
|
+
);
|
|
520
|
+
return errors; // Can't continue validation if path doesn't exist
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Check if it's a directory
|
|
524
|
+
try {
|
|
525
|
+
const stats = await fs.stat(absolutePath);
|
|
526
|
+
if (!stats.isDirectory()) {
|
|
527
|
+
errors.push(
|
|
528
|
+
`Repository "${repoName}": path "${repoPath}" is not a directory.`
|
|
529
|
+
);
|
|
530
|
+
return errors;
|
|
531
|
+
}
|
|
532
|
+
} catch (error) {
|
|
533
|
+
errors.push(
|
|
534
|
+
`Repository "${repoName}": cannot access path "${repoPath}". ${error.message}`
|
|
535
|
+
);
|
|
536
|
+
return errors;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Check if .git exists
|
|
540
|
+
const gitPath = path.join(absolutePath, '.git');
|
|
541
|
+
try {
|
|
542
|
+
const gitStats = await fs.stat(gitPath);
|
|
543
|
+
|
|
544
|
+
if (gitStats.isFile()) {
|
|
545
|
+
// .git is a file, which indicates a Git worktree
|
|
546
|
+
errors.push(
|
|
547
|
+
`Repository "${repoName}": path "${repoPath}" appears to be a Git worktree (not supported). ` +
|
|
548
|
+
'Please use the main repository path instead.'
|
|
549
|
+
);
|
|
550
|
+
} else if (!gitStats.isDirectory()) {
|
|
551
|
+
errors.push(
|
|
552
|
+
`Repository "${repoName}": path "${repoPath}" has an invalid .git entry.`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
// If .git is a directory, it's valid - no error
|
|
556
|
+
} catch (error) {
|
|
557
|
+
// .git doesn't exist
|
|
558
|
+
errors.push(
|
|
559
|
+
`Repository "${repoName}": path "${repoPath}" is not a Git repository (no .git directory found). ` +
|
|
560
|
+
'Please ensure this is a Git repository.'
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return errors;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Check if configuration version is supported
|
|
569
|
+
* @private
|
|
570
|
+
* @param {string} version - Version string to check
|
|
571
|
+
* @returns {boolean} True if version is supported
|
|
572
|
+
*/
|
|
573
|
+
_isSupportedVersion(version) {
|
|
574
|
+
// Currently only version 1.0 is supported
|
|
575
|
+
const supportedVersions = ['1.0'];
|
|
576
|
+
return supportedVersions.includes(version);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when configuration operations fail
|
|
3
|
+
*/
|
|
4
|
+
class ConfigError extends Error {
|
|
5
|
+
constructor(message, details = null) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'ConfigError';
|
|
8
|
+
this.details = details;
|
|
9
|
+
Error.captureStackTrace(this, this.constructor);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = ConfigError;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when Git operations fail
|
|
3
|
+
*/
|
|
4
|
+
class GitError extends Error {
|
|
5
|
+
constructor(message, command = null, exitCode = null, details = null) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'GitError';
|
|
8
|
+
this.command = command;
|
|
9
|
+
this.exitCode = exitCode;
|
|
10
|
+
this.details = details;
|
|
11
|
+
Error.captureStackTrace(this, this.constructor);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = GitError;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when repository operations fail
|
|
3
|
+
*/
|
|
4
|
+
class RepoError extends Error {
|
|
5
|
+
constructor(message, repoName = null, details = null) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'RepoError';
|
|
8
|
+
this.repoName = repoName;
|
|
9
|
+
this.details = details;
|
|
10
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = RepoError;
|