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,70 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ComplianceErrorReporter - Formats and displays compliance violations
|
|
5
|
+
*
|
|
6
|
+
* Creates user-friendly error messages with clear sections,
|
|
7
|
+
* violation details, and actionable fix suggestions.
|
|
8
|
+
*/
|
|
9
|
+
class ComplianceErrorReporter {
|
|
10
|
+
/**
|
|
11
|
+
* Format compliance violations into user-friendly message
|
|
12
|
+
*
|
|
13
|
+
* @param {ComplianceViolation[]} violations - List of violations
|
|
14
|
+
* @returns {string} Formatted error message
|
|
15
|
+
*/
|
|
16
|
+
formatError(violations) {
|
|
17
|
+
const disallowedFiles = violations.filter(v => v.type === 'disallowed_file');
|
|
18
|
+
const subdirectories = violations.filter(v => v.type === 'subdirectory');
|
|
19
|
+
|
|
20
|
+
let message = '\n';
|
|
21
|
+
message += chalk.red.bold('❌ Steering Directory Compliance Check Failed') + '\n\n';
|
|
22
|
+
message += 'The .kiro/steering/ directory contains files or subdirectories that are not allowed.\n';
|
|
23
|
+
message += 'This directory is automatically loaded in every AI session, so keeping it clean is critical.\n\n';
|
|
24
|
+
|
|
25
|
+
message += chalk.yellow.bold('Violations Found:') + '\n';
|
|
26
|
+
|
|
27
|
+
if (disallowedFiles.length > 0) {
|
|
28
|
+
message += ' • ' + chalk.yellow('Disallowed files:') + '\n';
|
|
29
|
+
disallowedFiles.forEach(v => {
|
|
30
|
+
message += ` - ${v.name}\n`;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (subdirectories.length > 0) {
|
|
35
|
+
message += ' • ' + chalk.yellow('Subdirectories (not allowed):') + '\n';
|
|
36
|
+
subdirectories.forEach(v => {
|
|
37
|
+
message += ` - ${v.name}/\n`;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
message += '\n' + chalk.green.bold('Allowed Files:') + '\n';
|
|
42
|
+
message += ' ✓ CORE_PRINCIPLES.md\n';
|
|
43
|
+
message += ' ✓ ENVIRONMENT.md\n';
|
|
44
|
+
message += ' ✓ CURRENT_CONTEXT.md\n';
|
|
45
|
+
message += ' ✓ RULES_GUIDE.md\n';
|
|
46
|
+
|
|
47
|
+
message += '\n' + chalk.cyan.bold('Fix Suggestions:') + '\n';
|
|
48
|
+
message += ' • Move analysis reports to: .kiro/specs/{spec-name}/reports/\n';
|
|
49
|
+
message += ' • Move historical data to: .kiro/specs/{spec-name}/\n';
|
|
50
|
+
message += ' • Move detailed docs to: docs/\n';
|
|
51
|
+
message += ' • Delete temporary files\n';
|
|
52
|
+
|
|
53
|
+
message += '\n' + chalk.gray('To bypass this check (not recommended):') + '\n';
|
|
54
|
+
message += chalk.gray(' kse <command> --skip-steering-check') + '\n';
|
|
55
|
+
|
|
56
|
+
return message;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Display error and exit
|
|
61
|
+
*
|
|
62
|
+
* @param {ComplianceViolation[]} violations - List of violations
|
|
63
|
+
*/
|
|
64
|
+
reportAndExit(violations) {
|
|
65
|
+
console.error(this.formatError(violations));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = ComplianceErrorReporter;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextSyncManager - Maintains CURRENT_CONTEXT.md as multi-Spec progress summary
|
|
3
|
+
*
|
|
4
|
+
* Parses and formats CURRENT_CONTEXT.md between Markdown and structured objects.
|
|
5
|
+
* Uses SteeringFileLock for concurrent write protection in multi-Agent mode.
|
|
6
|
+
* In single-Agent mode, updateSpecProgress is a no-op.
|
|
7
|
+
*
|
|
8
|
+
* Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 6.2
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
const fsUtils = require('../utils/fs-utils');
|
|
14
|
+
const { MultiAgentConfig } = require('../collab/multi-agent-config');
|
|
15
|
+
|
|
16
|
+
const STEERING_DIR = '.kiro/steering';
|
|
17
|
+
const CONTEXT_FILENAME = 'CURRENT_CONTEXT.md';
|
|
18
|
+
const SPECS_DIR = '.kiro/specs';
|
|
19
|
+
|
|
20
|
+
class ContextSyncManager {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} workspaceRoot - Absolute path to the project root
|
|
23
|
+
* @param {import('../lock/steering-file-lock').SteeringFileLock} steeringFileLock
|
|
24
|
+
*/
|
|
25
|
+
constructor(workspaceRoot, steeringFileLock) {
|
|
26
|
+
this._workspaceRoot = workspaceRoot;
|
|
27
|
+
this._steeringFileLock = steeringFileLock;
|
|
28
|
+
this._multiAgentConfig = new MultiAgentConfig(workspaceRoot);
|
|
29
|
+
this._contextPath = path.join(workspaceRoot, STEERING_DIR, CONTEXT_FILENAME);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Update the progress entry for a specific Spec.
|
|
34
|
+
* Uses SteeringFileLock for concurrent write protection.
|
|
35
|
+
* Single-Agent mode: no-op returning {success: true}.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} specName
|
|
38
|
+
* @param {{status: string, progress: number, summary: string}} entry
|
|
39
|
+
* @returns {Promise<{success: boolean}>}
|
|
40
|
+
*/
|
|
41
|
+
async updateSpecProgress(specName, entry) {
|
|
42
|
+
const enabled = await this._multiAgentConfig.isEnabled();
|
|
43
|
+
if (!enabled) {
|
|
44
|
+
return { success: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await this._steeringFileLock.withLock(CONTEXT_FILENAME, async () => {
|
|
48
|
+
const context = await this.readContext();
|
|
49
|
+
context.specs[specName] = {
|
|
50
|
+
status: entry.status,
|
|
51
|
+
progress: entry.progress,
|
|
52
|
+
summary: entry.summary,
|
|
53
|
+
};
|
|
54
|
+
await this.writeContext(context);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return { success: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read and parse CURRENT_CONTEXT.md into a structured object.
|
|
62
|
+
* Returns default empty object if file doesn't exist.
|
|
63
|
+
*
|
|
64
|
+
* @returns {Promise<{version: string|null, globalStatus: string|null, specs: object}>}
|
|
65
|
+
*/
|
|
66
|
+
async readContext() {
|
|
67
|
+
const exists = await fs.pathExists(this._contextPath);
|
|
68
|
+
if (!exists) {
|
|
69
|
+
return { version: null, globalStatus: null, specs: {} };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const content = await fs.readFile(this._contextPath, 'utf8');
|
|
74
|
+
return this.parseContext(content);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn(`[ContextSyncManager] Failed to read ${this._contextPath}: ${err.message}`);
|
|
77
|
+
return { version: null, globalStatus: null, specs: {} };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Write a structured context object back to CURRENT_CONTEXT.md.
|
|
83
|
+
* Uses atomic write for file integrity.
|
|
84
|
+
*
|
|
85
|
+
* @param {{version: string|null, globalStatus: string|null, specs: object}} context
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async writeContext(context) {
|
|
89
|
+
const dir = path.dirname(this._contextPath);
|
|
90
|
+
await fsUtils.ensureDirectory(dir);
|
|
91
|
+
const content = this.formatContext(context);
|
|
92
|
+
await fsUtils.atomicWrite(this._contextPath, content);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compute progress percentage for a Spec based on tasks.md completion.
|
|
97
|
+
* Counts only leaf tasks (lines matching checkbox patterns).
|
|
98
|
+
*
|
|
99
|
+
* @param {string} specName
|
|
100
|
+
* @returns {Promise<number>} 0-100
|
|
101
|
+
*/
|
|
102
|
+
async computeProgress(specName) {
|
|
103
|
+
const tasksPath = path.join(this._workspaceRoot, SPECS_DIR, specName, 'tasks.md');
|
|
104
|
+
const exists = await fs.pathExists(tasksPath);
|
|
105
|
+
if (!exists) {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const content = await fs.readFile(tasksPath, 'utf8');
|
|
111
|
+
return this._computeProgressFromContent(content);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.warn(`[ContextSyncManager] Failed to read ${tasksPath}: ${err.message}`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse CURRENT_CONTEXT.md Markdown content into a structured object.
|
|
120
|
+
* Handles both old single-agent format and new multi-agent table format.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} content
|
|
123
|
+
* @returns {{version: string|null, globalStatus: string|null, specs: object}}
|
|
124
|
+
*/
|
|
125
|
+
parseContext(content) {
|
|
126
|
+
const result = { version: null, globalStatus: null, specs: {} };
|
|
127
|
+
|
|
128
|
+
if (!content || typeof content !== 'string') {
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const lines = content.split('\n');
|
|
133
|
+
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
const trimmed = line.trim();
|
|
136
|
+
|
|
137
|
+
// Parse version line: **版本**: ...
|
|
138
|
+
const versionMatch = trimmed.match(/^\*\*版本\*\*:\s*(.+)$/);
|
|
139
|
+
if (versionMatch) {
|
|
140
|
+
result.version = versionMatch[1].trim();
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Parse global status line: **状态**: ...
|
|
145
|
+
const statusMatch = trimmed.match(/^\*\*状态\*\*:\s*(.+)$/);
|
|
146
|
+
if (statusMatch) {
|
|
147
|
+
result.globalStatus = statusMatch[1].trim();
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Parse table rows for Spec progress
|
|
152
|
+
// Format: | spec-name | status | progress% | summary |
|
|
153
|
+
if (trimmed.startsWith('|') && !trimmed.startsWith('|---') && !trimmed.startsWith('| Spec')) {
|
|
154
|
+
const cells = trimmed.split('|').map(c => c.trim()).filter(c => c.length > 0);
|
|
155
|
+
if (cells.length >= 4) {
|
|
156
|
+
const specName = cells[0];
|
|
157
|
+
const status = cells[1];
|
|
158
|
+
const progressStr = cells[2].replace('%', '');
|
|
159
|
+
const progress = parseInt(progressStr, 10);
|
|
160
|
+
const summary = cells[3];
|
|
161
|
+
|
|
162
|
+
if (specName && status && !isNaN(progress)) {
|
|
163
|
+
result.specs[specName] = { status, progress, summary };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Format a structured context object into CURRENT_CONTEXT.md Markdown.
|
|
174
|
+
*
|
|
175
|
+
* @param {{version: string|null, globalStatus: string|null, specs: object}} context
|
|
176
|
+
* @returns {string}
|
|
177
|
+
*/
|
|
178
|
+
formatContext(context) {
|
|
179
|
+
const lines = [];
|
|
180
|
+
|
|
181
|
+
lines.push('# 当前场景');
|
|
182
|
+
lines.push('');
|
|
183
|
+
|
|
184
|
+
if (context.version) {
|
|
185
|
+
lines.push(`**版本**: ${context.version}`);
|
|
186
|
+
}
|
|
187
|
+
if (context.globalStatus) {
|
|
188
|
+
lines.push(`**状态**: ${context.globalStatus}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const specEntries = Object.entries(context.specs || {});
|
|
192
|
+
if (specEntries.length > 0) {
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push('## Spec 进度');
|
|
195
|
+
lines.push('');
|
|
196
|
+
lines.push('| Spec | 状态 | 进度 | 摘要 |');
|
|
197
|
+
lines.push('|------|------|------|------|');
|
|
198
|
+
|
|
199
|
+
for (const [name, entry] of specEntries) {
|
|
200
|
+
const status = entry.status || '';
|
|
201
|
+
const progress = entry.progress != null ? `${entry.progress}%` : '0%';
|
|
202
|
+
const summary = entry.summary || '';
|
|
203
|
+
lines.push(`| ${name} | ${status} | ${progress} | ${summary} |`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
lines.push('');
|
|
208
|
+
lines.push('---');
|
|
209
|
+
lines.push('');
|
|
210
|
+
const now = new Date();
|
|
211
|
+
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
212
|
+
lines.push(`v1.0 | ${dateStr} | 自动更新`);
|
|
213
|
+
lines.push('');
|
|
214
|
+
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Compute progress from tasks.md content.
|
|
222
|
+
* Counts only leaf tasks (lines with checkbox patterns that have no sub-tasks).
|
|
223
|
+
*
|
|
224
|
+
* @param {string} content
|
|
225
|
+
* @returns {number} 0-100
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
_computeProgressFromContent(content) {
|
|
229
|
+
const lines = content.split('\n');
|
|
230
|
+
// Checkbox pattern: - [ ], - [x], - [-], - [~]
|
|
231
|
+
const taskPattern = /^(\s*)- \[([ x\-~])\]\*?\s/;
|
|
232
|
+
|
|
233
|
+
// First pass: identify all task lines with their indentation levels
|
|
234
|
+
const tasks = [];
|
|
235
|
+
for (let i = 0; i < lines.length; i++) {
|
|
236
|
+
const match = lines[i].match(taskPattern);
|
|
237
|
+
if (match) {
|
|
238
|
+
tasks.push({
|
|
239
|
+
index: i,
|
|
240
|
+
indent: match[1].length,
|
|
241
|
+
status: match[2],
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Second pass: identify leaf tasks (tasks with no sub-tasks)
|
|
247
|
+
let total = 0;
|
|
248
|
+
let completed = 0;
|
|
249
|
+
|
|
250
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
251
|
+
const current = tasks[i];
|
|
252
|
+
const next = tasks[i + 1];
|
|
253
|
+
|
|
254
|
+
// A task is a leaf if the next task is not more indented
|
|
255
|
+
const isLeaf = !next || next.indent <= current.indent;
|
|
256
|
+
|
|
257
|
+
if (isLeaf) {
|
|
258
|
+
total++;
|
|
259
|
+
if (current.status === 'x') {
|
|
260
|
+
completed++;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (total === 0) {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return Math.round(completed / total * 100);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = { ContextSyncManager };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const SteeringComplianceChecker = require('./steering-compliance-checker');
|
|
3
|
+
const ComplianceCache = require('./compliance-cache');
|
|
4
|
+
const ComplianceErrorReporter = require('./compliance-error-reporter');
|
|
5
|
+
const ComplianceAutoFixer = require('./compliance-auto-fixer');
|
|
6
|
+
const { SpecSteering } = require('./spec-steering');
|
|
7
|
+
const { SteeringLoader } = require('./steering-loader');
|
|
8
|
+
const { ContextSyncManager } = require('./context-sync-manager');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Run steering directory compliance check
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} options - Check options
|
|
14
|
+
* @param {boolean} options.skip - Skip the check entirely
|
|
15
|
+
* @param {boolean} options.force - Force check even if cache is valid
|
|
16
|
+
* @param {string} options.projectPath - Project root path (defaults to cwd)
|
|
17
|
+
* @param {string} options.version - Current kse version
|
|
18
|
+
* @returns {boolean} True if compliant or check skipped, false otherwise
|
|
19
|
+
*/
|
|
20
|
+
async function runSteeringComplianceCheck(options = {}) {
|
|
21
|
+
const {
|
|
22
|
+
skip = false,
|
|
23
|
+
force = false,
|
|
24
|
+
projectPath = process.cwd(),
|
|
25
|
+
version = require('../../package.json').version
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
// Skip check if requested
|
|
29
|
+
if (skip) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const checker = new SteeringComplianceChecker();
|
|
35
|
+
const cache = new ComplianceCache();
|
|
36
|
+
const reporter = new ComplianceErrorReporter();
|
|
37
|
+
const fixer = new ComplianceAutoFixer();
|
|
38
|
+
|
|
39
|
+
// Check cache unless forced
|
|
40
|
+
if (!force && cache.isValid(version)) {
|
|
41
|
+
// Cache is valid, skip check
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Perform compliance check with performance measurement
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
const steeringPath = path.join(projectPath, '.kiro', 'steering');
|
|
48
|
+
const result = checker.check(steeringPath);
|
|
49
|
+
const duration = Date.now() - startTime;
|
|
50
|
+
|
|
51
|
+
// Log warning if check exceeds performance target
|
|
52
|
+
if (duration > 50) {
|
|
53
|
+
console.warn(`Warning: Steering compliance check took ${duration}ms (target: <50ms)`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!result.compliant) {
|
|
57
|
+
// Auto-fix violations
|
|
58
|
+
console.log(''); // Empty line for better formatting
|
|
59
|
+
const fixResult = await fixer.fix(steeringPath, result.violations);
|
|
60
|
+
|
|
61
|
+
if (fixResult.success) {
|
|
62
|
+
console.log('✓ Steering directory is now compliant\n');
|
|
63
|
+
// Update cache after successful fix
|
|
64
|
+
cache.update(version);
|
|
65
|
+
return true;
|
|
66
|
+
} else {
|
|
67
|
+
// If fix failed, report and exit
|
|
68
|
+
reporter.reportAndExit(result.violations);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Update cache on success
|
|
74
|
+
cache.update(version);
|
|
75
|
+
return true;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// Log error but don't block execution
|
|
78
|
+
console.warn(`Warning: Steering compliance check failed: ${error.message}`);
|
|
79
|
+
return true; // Allow execution to proceed
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
runSteeringComplianceCheck,
|
|
85
|
+
SteeringComplianceChecker,
|
|
86
|
+
ComplianceCache,
|
|
87
|
+
ComplianceErrorReporter,
|
|
88
|
+
ComplianceAutoFixer,
|
|
89
|
+
SpecSteering,
|
|
90
|
+
SteeringLoader,
|
|
91
|
+
ContextSyncManager
|
|
92
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecSteering - Spec 级 steering.md 管理
|
|
3
|
+
*
|
|
4
|
+
* 负责 `.kiro/specs/{spec-name}/steering.md` 的 CRUD 操作和模板管理。
|
|
5
|
+
* 在单 Agent 模式下 createTemplate 为 no-op。
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 1.1, 1.3, 1.4, 7.1, 7.2, 7.3
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs-extra');
|
|
12
|
+
const fsUtils = require('../utils/fs-utils');
|
|
13
|
+
const { MultiAgentConfig } = require('../collab/multi-agent-config');
|
|
14
|
+
|
|
15
|
+
const STEERING_FILENAME = 'steering.md';
|
|
16
|
+
const SPECS_DIR = '.kiro/specs';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Section headers used in steering.md template.
|
|
20
|
+
* Order matters for format output.
|
|
21
|
+
*/
|
|
22
|
+
const SECTIONS = [
|
|
23
|
+
{ key: 'constraints', header: '## 约束 (Constraints)' },
|
|
24
|
+
{ key: 'notes', header: '## 注意事项 (Notes)' },
|
|
25
|
+
{ key: 'decisions', header: '## 决策记录 (Decisions)' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
class SpecSteering {
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} workspaceRoot - Absolute path to the project root
|
|
31
|
+
*/
|
|
32
|
+
constructor(workspaceRoot) {
|
|
33
|
+
this._workspaceRoot = workspaceRoot;
|
|
34
|
+
this._multiAgentConfig = new MultiAgentConfig(workspaceRoot);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build the absolute path to a Spec's steering.md.
|
|
39
|
+
* @param {string} specName
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
_steeringPath(specName) {
|
|
43
|
+
return path.join(this._workspaceRoot, SPECS_DIR, specName, STEERING_FILENAME);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate a steering.md template for the given Spec.
|
|
48
|
+
* Only executes in multi-Agent mode; single-Agent mode returns no-op.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} specName
|
|
51
|
+
* @returns {Promise<{created: boolean, path: string}>}
|
|
52
|
+
*/
|
|
53
|
+
async createTemplate(specName) {
|
|
54
|
+
const enabled = await this._multiAgentConfig.isEnabled();
|
|
55
|
+
if (!enabled) {
|
|
56
|
+
return { created: false, path: '' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const filePath = this._steeringPath(specName);
|
|
60
|
+
|
|
61
|
+
// Don't overwrite an existing steering.md
|
|
62
|
+
const exists = await fs.pathExists(filePath);
|
|
63
|
+
if (exists) {
|
|
64
|
+
return { created: false, path: filePath };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const specDir = path.dirname(filePath);
|
|
68
|
+
await fsUtils.ensureDirectory(specDir);
|
|
69
|
+
|
|
70
|
+
const template = this.format({
|
|
71
|
+
constraints: [],
|
|
72
|
+
notes: [],
|
|
73
|
+
decisions: [],
|
|
74
|
+
}, specName);
|
|
75
|
+
|
|
76
|
+
await fsUtils.atomicWrite(filePath, template);
|
|
77
|
+
return { created: true, path: filePath };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Read and parse a Spec's steering.md into a structured object.
|
|
82
|
+
* Returns null if the file does not exist.
|
|
83
|
+
* Returns empty arrays if the file is corrupted / unparseable.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} specName
|
|
86
|
+
* @returns {Promise<{constraints: string[], notes: string[], decisions: string[]}|null>}
|
|
87
|
+
*/
|
|
88
|
+
async read(specName) {
|
|
89
|
+
const filePath = this._steeringPath(specName);
|
|
90
|
+
const exists = await fs.pathExists(filePath);
|
|
91
|
+
if (!exists) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
97
|
+
return this.parse(content);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.warn(`[SpecSteering] Failed to read ${filePath}: ${err.message}`);
|
|
100
|
+
return { constraints: [], notes: [], decisions: [] };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Write a structured object back to steering.md using atomic write.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} specName
|
|
108
|
+
* @param {{constraints: string[], notes: string[], decisions: string[]}} data
|
|
109
|
+
* @returns {Promise<void>}
|
|
110
|
+
*/
|
|
111
|
+
async write(specName, data) {
|
|
112
|
+
const filePath = this._steeringPath(specName);
|
|
113
|
+
const specDir = path.dirname(filePath);
|
|
114
|
+
await fsUtils.ensureDirectory(specDir);
|
|
115
|
+
|
|
116
|
+
const content = this.format(data, specName);
|
|
117
|
+
await fsUtils.atomicWrite(filePath, content);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Parse steering.md Markdown content into a structured object.
|
|
122
|
+
* Gracefully handles corrupted / unexpected content by returning empty arrays.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} content - Markdown string
|
|
125
|
+
* @returns {{constraints: string[], notes: string[], decisions: string[]}}
|
|
126
|
+
*/
|
|
127
|
+
parse(content) {
|
|
128
|
+
const result = { constraints: [], notes: [], decisions: [] };
|
|
129
|
+
|
|
130
|
+
if (!content || typeof content !== 'string') {
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const lines = content.split('\n');
|
|
136
|
+
let currentSection = null;
|
|
137
|
+
|
|
138
|
+
for (const line of lines) {
|
|
139
|
+
const trimmed = line.trim();
|
|
140
|
+
|
|
141
|
+
// Detect section headers
|
|
142
|
+
const section = this._detectSection(trimmed);
|
|
143
|
+
if (section) {
|
|
144
|
+
currentSection = section;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Collect list items under the current section
|
|
149
|
+
if (currentSection && trimmed.startsWith('- ')) {
|
|
150
|
+
const item = trimmed.slice(2).trim();
|
|
151
|
+
if (item && !item.startsWith('[') ) {
|
|
152
|
+
// Skip placeholder items like "[约束条目]"
|
|
153
|
+
result[currentSection].push(item);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.warn(`[SpecSteering] Failed to parse steering.md content: ${err.message}`);
|
|
159
|
+
return { constraints: [], notes: [], decisions: [] };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Format a structured object into steering.md Markdown.
|
|
167
|
+
*
|
|
168
|
+
* @param {{constraints: string[], notes: string[], decisions: string[]}} data
|
|
169
|
+
* @param {string} [specName=''] - Used for the title line
|
|
170
|
+
* @returns {string}
|
|
171
|
+
*/
|
|
172
|
+
format(data, specName = '') {
|
|
173
|
+
const lines = [];
|
|
174
|
+
|
|
175
|
+
// Title
|
|
176
|
+
lines.push(`# Spec Steering: ${specName}`);
|
|
177
|
+
lines.push('');
|
|
178
|
+
|
|
179
|
+
for (const { key, header } of SECTIONS) {
|
|
180
|
+
lines.push(header);
|
|
181
|
+
lines.push('');
|
|
182
|
+
|
|
183
|
+
const items = (data && Array.isArray(data[key])) ? data[key] : [];
|
|
184
|
+
if (items.length === 0) {
|
|
185
|
+
// Empty section placeholder
|
|
186
|
+
lines.push(`- [${this._placeholderFor(key)}]`);
|
|
187
|
+
} else {
|
|
188
|
+
for (const item of items) {
|
|
189
|
+
lines.push(`- ${item}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
lines.push('');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return lines.join('\n');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Detect which section a header line belongs to.
|
|
203
|
+
* @param {string} trimmedLine
|
|
204
|
+
* @returns {string|null} Section key or null
|
|
205
|
+
*/
|
|
206
|
+
_detectSection(trimmedLine) {
|
|
207
|
+
for (const { key, header } of SECTIONS) {
|
|
208
|
+
if (trimmedLine === header || trimmedLine.startsWith(header.split(' (')[0])) {
|
|
209
|
+
return key;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Return the placeholder text for an empty section.
|
|
217
|
+
* @param {string} sectionKey
|
|
218
|
+
* @returns {string}
|
|
219
|
+
*/
|
|
220
|
+
_placeholderFor(sectionKey) {
|
|
221
|
+
const map = {
|
|
222
|
+
constraints: '约束条目',
|
|
223
|
+
notes: '注意事项条目',
|
|
224
|
+
decisions: '决策条目',
|
|
225
|
+
};
|
|
226
|
+
return map[sectionKey] || sectionKey;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { SpecSteering };
|