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,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RegistryParser - Parses and validates template registry files
|
|
3
|
+
*
|
|
4
|
+
* Handles JSON schema validation, registry parsing, indexing, and search.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { ValidationError } = require('./template-error');
|
|
10
|
+
|
|
11
|
+
class RegistryParser {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.registryCache = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parses a template registry file
|
|
18
|
+
*
|
|
19
|
+
* @param {string} registryPath - Path to template-registry.json
|
|
20
|
+
* @returns {Promise<Object>} Parsed registry
|
|
21
|
+
*/
|
|
22
|
+
async parseRegistry(registryPath) {
|
|
23
|
+
// Check cache
|
|
24
|
+
if (this.registryCache.has(registryPath)) {
|
|
25
|
+
return this.registryCache.get(registryPath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Read registry file
|
|
29
|
+
if (!await fs.pathExists(registryPath)) {
|
|
30
|
+
throw new ValidationError(
|
|
31
|
+
'Registry file not found',
|
|
32
|
+
{ path: registryPath }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let registry;
|
|
37
|
+
try {
|
|
38
|
+
registry = await fs.readJson(registryPath);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new ValidationError(
|
|
41
|
+
'Failed to parse registry JSON',
|
|
42
|
+
{
|
|
43
|
+
path: registryPath,
|
|
44
|
+
error: error.message
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate schema
|
|
50
|
+
this.validateRegistrySchema(registry);
|
|
51
|
+
|
|
52
|
+
// Cache parsed registry
|
|
53
|
+
this.registryCache.set(registryPath, registry);
|
|
54
|
+
|
|
55
|
+
return registry;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validates registry schema
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} registry - Registry object
|
|
62
|
+
* @throws {ValidationError} If schema is invalid
|
|
63
|
+
*/
|
|
64
|
+
validateRegistrySchema(registry) {
|
|
65
|
+
const errors = [];
|
|
66
|
+
|
|
67
|
+
// Check version field
|
|
68
|
+
if (!registry.version) {
|
|
69
|
+
errors.push('Missing required field: version');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check templates array
|
|
73
|
+
if (!registry.templates) {
|
|
74
|
+
errors.push('Missing required field: templates');
|
|
75
|
+
} else if (!Array.isArray(registry.templates)) {
|
|
76
|
+
errors.push('Field "templates" must be an array');
|
|
77
|
+
} else {
|
|
78
|
+
// Validate each template entry
|
|
79
|
+
registry.templates.forEach((template, index) => {
|
|
80
|
+
const templateErrors = this.validateTemplateEntry(template, index);
|
|
81
|
+
errors.push(...templateErrors);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (errors.length > 0) {
|
|
86
|
+
throw new ValidationError(
|
|
87
|
+
'Registry schema validation failed',
|
|
88
|
+
{
|
|
89
|
+
errors,
|
|
90
|
+
errorCount: errors.length
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validates a single template entry
|
|
98
|
+
*
|
|
99
|
+
* @param {Object} template - Template entry
|
|
100
|
+
* @param {number} index - Template index
|
|
101
|
+
* @returns {string[]} Array of error messages
|
|
102
|
+
*/
|
|
103
|
+
validateTemplateEntry(template, index) {
|
|
104
|
+
const errors = [];
|
|
105
|
+
const prefix = `Template[${index}]`;
|
|
106
|
+
|
|
107
|
+
// Required fields
|
|
108
|
+
const requiredFields = [
|
|
109
|
+
'id', 'name', 'category', 'description',
|
|
110
|
+
'difficulty', 'tags', 'applicable_scenarios', 'files'
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const field of requiredFields) {
|
|
114
|
+
if (!template[field]) {
|
|
115
|
+
errors.push(`${prefix}: Missing required field "${field}"`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate field types
|
|
120
|
+
if (template.tags && !Array.isArray(template.tags)) {
|
|
121
|
+
errors.push(`${prefix}: Field "tags" must be an array`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (template.applicable_scenarios && !Array.isArray(template.applicable_scenarios)) {
|
|
125
|
+
errors.push(`${prefix}: Field "applicable_scenarios" must be an array`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (template.files && !Array.isArray(template.files)) {
|
|
129
|
+
errors.push(`${prefix}: Field "files" must be an array`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Validate difficulty
|
|
133
|
+
const validDifficulties = ['beginner', 'intermediate', 'advanced'];
|
|
134
|
+
if (template.difficulty && !validDifficulties.includes(template.difficulty)) {
|
|
135
|
+
errors.push(`${prefix}: Invalid difficulty "${template.difficulty}". Must be one of: ${validDifficulties.join(', ')}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Validate files array
|
|
139
|
+
if (template.files && Array.isArray(template.files)) {
|
|
140
|
+
const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
|
|
141
|
+
for (const file of requiredFiles) {
|
|
142
|
+
if (!template.files.includes(file)) {
|
|
143
|
+
errors.push(`${prefix}: Missing required file "${file}" in files array`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return errors;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Builds searchable index from registry
|
|
153
|
+
*
|
|
154
|
+
* @param {Object} registry - Parsed registry
|
|
155
|
+
* @returns {Object} Indexed registry
|
|
156
|
+
*/
|
|
157
|
+
buildIndex(registry) {
|
|
158
|
+
const index = {
|
|
159
|
+
byId: {},
|
|
160
|
+
byCategory: {},
|
|
161
|
+
byTag: {},
|
|
162
|
+
byDifficulty: {},
|
|
163
|
+
all: []
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const template of registry.templates) {
|
|
167
|
+
// Index by ID
|
|
168
|
+
index.byId[template.id] = template;
|
|
169
|
+
|
|
170
|
+
// Index by category
|
|
171
|
+
if (!index.byCategory[template.category]) {
|
|
172
|
+
index.byCategory[template.category] = [];
|
|
173
|
+
}
|
|
174
|
+
index.byCategory[template.category].push(template);
|
|
175
|
+
|
|
176
|
+
// Index by tags
|
|
177
|
+
for (const tag of template.tags || []) {
|
|
178
|
+
if (!index.byTag[tag]) {
|
|
179
|
+
index.byTag[tag] = [];
|
|
180
|
+
}
|
|
181
|
+
index.byTag[tag].push(template);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Index by difficulty
|
|
185
|
+
if (!index.byDifficulty[template.difficulty]) {
|
|
186
|
+
index.byDifficulty[template.difficulty] = [];
|
|
187
|
+
}
|
|
188
|
+
index.byDifficulty[template.difficulty].push(template);
|
|
189
|
+
|
|
190
|
+
// Add to all templates
|
|
191
|
+
index.all.push(template);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return index;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Merges multiple registries
|
|
199
|
+
*
|
|
200
|
+
* @param {Object[]} registries - Array of registry objects
|
|
201
|
+
* @returns {Object} Merged registry
|
|
202
|
+
*/
|
|
203
|
+
mergeRegistries(registries) {
|
|
204
|
+
const merged = {
|
|
205
|
+
version: '1.0.0',
|
|
206
|
+
templates: []
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const seenIds = new Set();
|
|
210
|
+
|
|
211
|
+
for (const registry of registries) {
|
|
212
|
+
for (const template of registry.templates || []) {
|
|
213
|
+
// Skip duplicates (first occurrence wins)
|
|
214
|
+
if (seenIds.has(template.id)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
seenIds.add(template.id);
|
|
219
|
+
merged.templates.push(template);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return merged;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Clears registry cache
|
|
228
|
+
*/
|
|
229
|
+
clearCache() {
|
|
230
|
+
this.registryCache.clear();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Gets template by ID
|
|
235
|
+
*
|
|
236
|
+
* @param {Object} index - Registry index
|
|
237
|
+
* @param {string} templateId - Template ID
|
|
238
|
+
* @returns {Object|null} Template or null
|
|
239
|
+
*/
|
|
240
|
+
getTemplateById(index, templateId) {
|
|
241
|
+
return index.byId[templateId] || null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Gets templates by category
|
|
246
|
+
*
|
|
247
|
+
* @param {Object} index - Registry index
|
|
248
|
+
* @param {string} category - Category name
|
|
249
|
+
* @returns {Object[]} Array of templates
|
|
250
|
+
*/
|
|
251
|
+
getTemplatesByCategory(index, category) {
|
|
252
|
+
return index.byCategory[category] || [];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Gets templates by tag
|
|
257
|
+
*
|
|
258
|
+
* @param {Object} index - Registry index
|
|
259
|
+
* @param {string} tag - Tag name
|
|
260
|
+
* @returns {Object[]} Array of templates
|
|
261
|
+
*/
|
|
262
|
+
getTemplatesByTag(index, tag) {
|
|
263
|
+
return index.byTag[tag] || [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Gets templates by difficulty
|
|
268
|
+
*
|
|
269
|
+
* @param {Object} index - Registry index
|
|
270
|
+
* @param {string} difficulty - Difficulty level
|
|
271
|
+
* @returns {Object[]} Array of templates
|
|
272
|
+
*/
|
|
273
|
+
getTemplatesByDifficulty(index, difficulty) {
|
|
274
|
+
return index.byDifficulty[difficulty] || [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Gets all categories
|
|
279
|
+
*
|
|
280
|
+
* @param {Object} index - Registry index
|
|
281
|
+
* @returns {string[]} Array of category names
|
|
282
|
+
*/
|
|
283
|
+
getCategories(index) {
|
|
284
|
+
return Object.keys(index.byCategory);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Gets all tags
|
|
289
|
+
*
|
|
290
|
+
* @param {Object} index - Registry index
|
|
291
|
+
* @returns {string[]} Array of tag names
|
|
292
|
+
*/
|
|
293
|
+
getTags(index) {
|
|
294
|
+
return Object.keys(index.byTag);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Searches templates by keyword
|
|
299
|
+
*
|
|
300
|
+
* @param {Object} index - Registry index
|
|
301
|
+
* @param {string} keyword - Search keyword
|
|
302
|
+
* @param {Object} filters - Optional filters
|
|
303
|
+
* @param {string} filters.category - Filter by category
|
|
304
|
+
* @param {string} filters.difficulty - Filter by difficulty
|
|
305
|
+
* @param {string[]} filters.tags - Filter by tags (any match)
|
|
306
|
+
* @returns {Object[]} Array of matching templates
|
|
307
|
+
*/
|
|
308
|
+
searchTemplates(index, keyword, filters = {}) {
|
|
309
|
+
const keywordLower = keyword.toLowerCase();
|
|
310
|
+
let results = [];
|
|
311
|
+
|
|
312
|
+
// Search in all templates
|
|
313
|
+
for (const template of index.all) {
|
|
314
|
+
// Check if keyword matches
|
|
315
|
+
const matchesKeyword = this._matchesKeyword(template, keywordLower);
|
|
316
|
+
|
|
317
|
+
if (!matchesKeyword) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Apply filters
|
|
322
|
+
if (filters.category && template.category !== filters.category) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (filters.difficulty && template.difficulty !== filters.difficulty) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
331
|
+
const hasMatchingTag = filters.tags.some(tag =>
|
|
332
|
+
template.tags.includes(tag)
|
|
333
|
+
);
|
|
334
|
+
if (!hasMatchingTag) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
results.push(template);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return results;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Checks if template matches keyword
|
|
347
|
+
*
|
|
348
|
+
* @param {Object} template - Template object
|
|
349
|
+
* @param {string} keywordLower - Lowercase keyword
|
|
350
|
+
* @returns {boolean} True if matches
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
_matchesKeyword(template, keywordLower) {
|
|
354
|
+
// Search in name
|
|
355
|
+
if (template.name.toLowerCase().includes(keywordLower)) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Search in description
|
|
360
|
+
if (template.description.toLowerCase().includes(keywordLower)) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Search in tags
|
|
365
|
+
for (const tag of template.tags || []) {
|
|
366
|
+
if (tag.toLowerCase().includes(keywordLower)) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Search in applicable scenarios
|
|
372
|
+
for (const scenario of template.applicable_scenarios || []) {
|
|
373
|
+
if (scenario.toLowerCase().includes(keywordLower)) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Filters templates
|
|
383
|
+
*
|
|
384
|
+
* @param {Object} index - Registry index
|
|
385
|
+
* @param {Object} filters - Filters
|
|
386
|
+
* @param {string} filters.category - Filter by category
|
|
387
|
+
* @param {string} filters.difficulty - Filter by difficulty
|
|
388
|
+
* @param {string[]} filters.tags - Filter by tags (any match)
|
|
389
|
+
* @returns {Object[]} Array of matching templates
|
|
390
|
+
*/
|
|
391
|
+
filterTemplates(index, filters = {}) {
|
|
392
|
+
let results = [...index.all];
|
|
393
|
+
|
|
394
|
+
// Filter by category
|
|
395
|
+
if (filters.category) {
|
|
396
|
+
results = results.filter(t => t.category === filters.category);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Filter by difficulty
|
|
400
|
+
if (filters.difficulty) {
|
|
401
|
+
results = results.filter(t => t.difficulty === filters.difficulty);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Filter by tags (any match)
|
|
405
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
406
|
+
results = results.filter(t =>
|
|
407
|
+
filters.tags.some(tag => t.tags.includes(tag))
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return results;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Sorts templates
|
|
416
|
+
*
|
|
417
|
+
* @param {Object[]} templates - Array of templates
|
|
418
|
+
* @param {string} sortBy - Sort field ('name', 'difficulty', 'created_at', 'updated_at')
|
|
419
|
+
* @param {string} order - Sort order ('asc' or 'desc')
|
|
420
|
+
* @returns {Object[]} Sorted templates
|
|
421
|
+
*/
|
|
422
|
+
sortTemplates(templates, sortBy = 'name', order = 'asc') {
|
|
423
|
+
const sorted = [...templates];
|
|
424
|
+
|
|
425
|
+
sorted.sort((a, b) => {
|
|
426
|
+
let aVal, bVal;
|
|
427
|
+
|
|
428
|
+
switch (sortBy) {
|
|
429
|
+
case 'name':
|
|
430
|
+
aVal = a.name.toLowerCase();
|
|
431
|
+
bVal = b.name.toLowerCase();
|
|
432
|
+
break;
|
|
433
|
+
|
|
434
|
+
case 'difficulty':
|
|
435
|
+
const difficultyOrder = { beginner: 1, intermediate: 2, advanced: 3 };
|
|
436
|
+
aVal = difficultyOrder[a.difficulty] || 0;
|
|
437
|
+
bVal = difficultyOrder[b.difficulty] || 0;
|
|
438
|
+
break;
|
|
439
|
+
|
|
440
|
+
case 'created_at':
|
|
441
|
+
aVal = new Date(a.created_at || 0);
|
|
442
|
+
bVal = new Date(b.created_at || 0);
|
|
443
|
+
break;
|
|
444
|
+
|
|
445
|
+
case 'updated_at':
|
|
446
|
+
aVal = new Date(a.updated_at || 0);
|
|
447
|
+
bVal = new Date(b.updated_at || 0);
|
|
448
|
+
break;
|
|
449
|
+
|
|
450
|
+
default:
|
|
451
|
+
aVal = a.name.toLowerCase();
|
|
452
|
+
bVal = b.name.toLowerCase();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (aVal < bVal) return order === 'asc' ? -1 : 1;
|
|
456
|
+
if (aVal > bVal) return order === 'asc' ? 1 : -1;
|
|
457
|
+
return 0;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return sorted;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Groups templates by category
|
|
465
|
+
*
|
|
466
|
+
* @param {Object[]} templates - Array of templates
|
|
467
|
+
* @returns {Object} Templates grouped by category
|
|
468
|
+
*/
|
|
469
|
+
groupByCategory(templates) {
|
|
470
|
+
const grouped = {};
|
|
471
|
+
|
|
472
|
+
for (const template of templates) {
|
|
473
|
+
if (!grouped[template.category]) {
|
|
474
|
+
grouped[template.category] = [];
|
|
475
|
+
}
|
|
476
|
+
grouped[template.category].push(template);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return grouped;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Gets template statistics
|
|
484
|
+
*
|
|
485
|
+
* @param {Object} index - Registry index
|
|
486
|
+
* @returns {Object} Statistics
|
|
487
|
+
*/
|
|
488
|
+
getStatistics(index) {
|
|
489
|
+
return {
|
|
490
|
+
totalTemplates: index.all.length,
|
|
491
|
+
categories: Object.keys(index.byCategory).length,
|
|
492
|
+
tags: Object.keys(index.byTag).length,
|
|
493
|
+
byCategory: Object.keys(index.byCategory).reduce((acc, cat) => {
|
|
494
|
+
acc[cat] = index.byCategory[cat].length;
|
|
495
|
+
return acc;
|
|
496
|
+
}, {}),
|
|
497
|
+
byDifficulty: Object.keys(index.byDifficulty).reduce((acc, diff) => {
|
|
498
|
+
acc[diff] = index.byDifficulty[diff].length;
|
|
499
|
+
return acc;
|
|
500
|
+
}, {})
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
module.exports = RegistryParser;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecReader - Reads and validates Spec files from .kiro/specs/ directory
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs').promises;
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
class SpecReader {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.specsDir = '.kiro/specs';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Finds a Spec by identifier (number or name)
|
|
16
|
+
* @param {string} identifier - Spec number (e.g., '23-00') or name
|
|
17
|
+
* @returns {Promise<Object>} Spec information
|
|
18
|
+
*/
|
|
19
|
+
async findSpec(identifier) {
|
|
20
|
+
const normalizedId = identifier.toLowerCase().trim();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const entries = await fs.readdir(this.specsDir, { withFileTypes: true });
|
|
24
|
+
const specDirs = entries.filter(entry => entry.isDirectory());
|
|
25
|
+
|
|
26
|
+
// Try exact match first
|
|
27
|
+
let matchedDir = specDirs.find(dir => {
|
|
28
|
+
const dirName = dir.name.toLowerCase();
|
|
29
|
+
return dirName === normalizedId ||
|
|
30
|
+
dirName.startsWith(`${normalizedId}-`) ||
|
|
31
|
+
dirName.includes(`-${normalizedId}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// If no exact match, try partial match
|
|
35
|
+
if (!matchedDir) {
|
|
36
|
+
matchedDir = specDirs.find(dir =>
|
|
37
|
+
dir.name.toLowerCase().includes(normalizedId)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!matchedDir) {
|
|
42
|
+
throw new Error(`Spec not found: ${identifier}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const specPath = path.join(this.specsDir, matchedDir.name);
|
|
46
|
+
return {
|
|
47
|
+
name: matchedDir.name,
|
|
48
|
+
path: specPath,
|
|
49
|
+
number: this.extractSpecNumber(matchedDir.name),
|
|
50
|
+
kebabName: this.extractKebabName(matchedDir.name)
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error.code === 'ENOENT') {
|
|
54
|
+
throw new Error(`Specs directory not found: ${this.specsDir}`);
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validates that all required files exist
|
|
62
|
+
* @param {string} specPath - Path to Spec directory
|
|
63
|
+
* @returns {Promise<Object>} Validation result
|
|
64
|
+
*/
|
|
65
|
+
async validateSpecStructure(specPath) {
|
|
66
|
+
const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
|
|
67
|
+
const missingFiles = [];
|
|
68
|
+
const existingFiles = [];
|
|
69
|
+
|
|
70
|
+
for (const file of requiredFiles) {
|
|
71
|
+
const filePath = path.join(specPath, file);
|
|
72
|
+
try {
|
|
73
|
+
await fs.access(filePath);
|
|
74
|
+
existingFiles.push(file);
|
|
75
|
+
} catch {
|
|
76
|
+
missingFiles.push(file);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
valid: missingFiles.length === 0,
|
|
82
|
+
missingFiles,
|
|
83
|
+
existingFiles
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reads all Spec files
|
|
89
|
+
* @param {string} specPath - Path to Spec directory
|
|
90
|
+
* @returns {Promise<Object>} File contents
|
|
91
|
+
*/
|
|
92
|
+
async readSpecFiles(specPath) {
|
|
93
|
+
const files = ['requirements.md', 'design.md', 'tasks.md'];
|
|
94
|
+
const contents = {};
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const filePath = path.join(specPath, file);
|
|
98
|
+
try {
|
|
99
|
+
contents[file] = await fs.readFile(filePath, 'utf-8');
|
|
100
|
+
} catch (error) {
|
|
101
|
+
contents[file] = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return contents;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extracts Spec metadata (name, number, dates)
|
|
110
|
+
* @param {string} specPath - Path to Spec directory
|
|
111
|
+
* @param {Object} fileContents - File contents
|
|
112
|
+
* @returns {Object} Extracted metadata
|
|
113
|
+
*/
|
|
114
|
+
extractSpecMetadata(specPath, fileContents) {
|
|
115
|
+
const specName = path.basename(specPath);
|
|
116
|
+
const specNumber = this.extractSpecNumber(specName);
|
|
117
|
+
const kebabName = this.extractKebabName(specName);
|
|
118
|
+
const titleName = this.kebabToTitle(kebabName);
|
|
119
|
+
|
|
120
|
+
// Extract dates from file stats
|
|
121
|
+
const dates = this.extractDates(specPath);
|
|
122
|
+
|
|
123
|
+
// Extract author from git config or content
|
|
124
|
+
const author = this.extractAuthor(fileContents);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
specNumber,
|
|
128
|
+
specName: kebabName,
|
|
129
|
+
specNameTitle: titleName,
|
|
130
|
+
specPath,
|
|
131
|
+
fullDirName: specName,
|
|
132
|
+
author,
|
|
133
|
+
dates
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extracts Spec number from directory name
|
|
139
|
+
* @param {string} dirName - Directory name
|
|
140
|
+
* @returns {string} Spec number
|
|
141
|
+
*/
|
|
142
|
+
extractSpecNumber(dirName) {
|
|
143
|
+
const match = dirName.match(/^(\d+-\d+)/);
|
|
144
|
+
return match ? match[1] : '';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Extracts kebab-case name from directory name
|
|
149
|
+
* @param {string} dirName - Directory name
|
|
150
|
+
* @returns {string} Kebab-case name
|
|
151
|
+
*/
|
|
152
|
+
extractKebabName(dirName) {
|
|
153
|
+
// Remove spec number prefix
|
|
154
|
+
const withoutNumber = dirName.replace(/^\d+-\d+-/, '');
|
|
155
|
+
return withoutNumber;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Converts kebab-case to Title Case
|
|
160
|
+
* @param {string} kebabStr - Kebab-case string
|
|
161
|
+
* @returns {string} Title case string
|
|
162
|
+
*/
|
|
163
|
+
kebabToTitle(kebabStr) {
|
|
164
|
+
return kebabStr
|
|
165
|
+
.split('-')
|
|
166
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
167
|
+
.join(' ');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Extracts dates from file system
|
|
172
|
+
* @param {string} specPath - Path to Spec directory
|
|
173
|
+
* @returns {Object} Dates
|
|
174
|
+
*/
|
|
175
|
+
extractDates(specPath) {
|
|
176
|
+
try {
|
|
177
|
+
const stats = require('fs').statSync(specPath);
|
|
178
|
+
return {
|
|
179
|
+
created: stats.birthtime.toISOString().split('T')[0],
|
|
180
|
+
modified: stats.mtime.toISOString().split('T')[0]
|
|
181
|
+
};
|
|
182
|
+
} catch {
|
|
183
|
+
const today = new Date().toISOString().split('T')[0];
|
|
184
|
+
return {
|
|
185
|
+
created: today,
|
|
186
|
+
modified: today
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Extracts author from git config or content
|
|
193
|
+
* @param {Object} fileContents - File contents
|
|
194
|
+
* @returns {string} Author name
|
|
195
|
+
*/
|
|
196
|
+
extractAuthor(fileContents) {
|
|
197
|
+
// Try git config first
|
|
198
|
+
try {
|
|
199
|
+
const gitUser = execSync('git config user.name', { encoding: 'utf-8' }).trim();
|
|
200
|
+
if (gitUser) return gitUser;
|
|
201
|
+
} catch {
|
|
202
|
+
// Git not available or not configured
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Try to find author in content
|
|
206
|
+
const allContent = Object.values(fileContents).join('\n');
|
|
207
|
+
const authorMatch = allContent.match(/(?:Author|Created by|Written by):\s*([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/);
|
|
208
|
+
if (authorMatch) {
|
|
209
|
+
return authorMatch[1];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return 'Unknown';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = SpecReader;
|