xtep-workspace 1.10.10
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 +525 -0
- package/README.md +188 -0
- package/config/component-versions.json +16 -0
- package/config/scenarioCapabilities.json +29 -0
- package/config/version-notes.yaml +244 -0
- package/dist/adapters/OutputAdapter.d.ts +79 -0
- package/dist/adapters/OutputAdapter.d.ts.map +1 -0
- package/dist/adapters/OutputAdapter.js +124 -0
- package/dist/adapters/OutputAdapter.js.map +1 -0
- package/dist/cli/check-node-version.d.ts +3 -0
- package/dist/cli/check-node-version.d.ts.map +1 -0
- package/dist/cli/check-node-version.js +153 -0
- package/dist/cli/check-node-version.js.map +1 -0
- package/dist/cli/plugins.d.ts +41 -0
- package/dist/cli/plugins.d.ts.map +1 -0
- package/dist/cli/plugins.js +742 -0
- package/dist/cli/plugins.js.map +1 -0
- package/dist/cli/rebuild.d.ts +63 -0
- package/dist/cli/rebuild.d.ts.map +1 -0
- package/dist/cli/rebuild.js +989 -0
- package/dist/cli/rebuild.js.map +1 -0
- package/dist/cli/repair.d.ts +7 -0
- package/dist/cli/repair.d.ts.map +1 -0
- package/dist/cli/repair.js +925 -0
- package/dist/cli/repair.js.map +1 -0
- package/dist/cli/setup.d.ts +7 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +452 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/update.d.ts +10 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +426 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/webui.d.ts +6 -0
- package/dist/cli/webui.d.ts.map +1 -0
- package/dist/cli/webui.js +210 -0
- package/dist/cli/webui.js.map +1 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +15 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/middleware/errorHandler.d.ts +16 -0
- package/dist/http/middleware/errorHandler.d.ts.map +1 -0
- package/dist/http/middleware/errorHandler.js +79 -0
- package/dist/http/middleware/errorHandler.js.map +1 -0
- package/dist/http/routes/admin.d.ts +3 -0
- package/dist/http/routes/admin.d.ts.map +1 -0
- package/dist/http/routes/admin.js +730 -0
- package/dist/http/routes/admin.js.map +1 -0
- package/dist/http/routes/backup.d.ts +3 -0
- package/dist/http/routes/backup.d.ts.map +1 -0
- package/dist/http/routes/backup.js +172 -0
- package/dist/http/routes/backup.js.map +1 -0
- package/dist/http/routes/config.d.ts +3 -0
- package/dist/http/routes/config.d.ts.map +1 -0
- package/dist/http/routes/config.js +157 -0
- package/dist/http/routes/config.js.map +1 -0
- package/dist/http/routes/context.d.ts +3 -0
- package/dist/http/routes/context.d.ts.map +1 -0
- package/dist/http/routes/context.js +82 -0
- package/dist/http/routes/context.js.map +1 -0
- package/dist/http/routes/log.d.ts +3 -0
- package/dist/http/routes/log.d.ts.map +1 -0
- package/dist/http/routes/log.js +105 -0
- package/dist/http/routes/log.js.map +1 -0
- package/dist/http/routes/memo.d.ts +6 -0
- package/dist/http/routes/memo.d.ts.map +1 -0
- package/dist/http/routes/memo.js +29 -0
- package/dist/http/routes/memo.js.map +1 -0
- package/dist/http/routes/node.d.ts +3 -0
- package/dist/http/routes/node.d.ts.map +1 -0
- package/dist/http/routes/node.js +251 -0
- package/dist/http/routes/node.js.map +1 -0
- package/dist/http/routes/state.d.ts +3 -0
- package/dist/http/routes/state.d.ts.map +1 -0
- package/dist/http/routes/state.js +48 -0
- package/dist/http/routes/state.js.map +1 -0
- package/dist/http/routes/workspace.d.ts +3 -0
- package/dist/http/routes/workspace.d.ts.map +1 -0
- package/dist/http/routes/workspace.js +249 -0
- package/dist/http/routes/workspace.js.map +1 -0
- package/dist/http/server.d.ts +10 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +284 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/services.d.ts +93 -0
- package/dist/http/services.d.ts.map +1 -0
- package/dist/http/services.js +297 -0
- package/dist/http/services.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1073 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/guidanceContent.d.ts +18 -0
- package/dist/prompts/guidanceContent.d.ts.map +1 -0
- package/dist/prompts/guidanceContent.js +814 -0
- package/dist/prompts/guidanceContent.js.map +1 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +4 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/instructions.d.ts +56 -0
- package/dist/prompts/instructions.d.ts.map +1 -0
- package/dist/prompts/instructions.js +1343 -0
- package/dist/prompts/instructions.js.map +1 -0
- package/dist/services/BackupService.d.ts +104 -0
- package/dist/services/BackupService.d.ts.map +1 -0
- package/dist/services/BackupService.js +549 -0
- package/dist/services/BackupService.js.map +1 -0
- package/dist/services/CapabilityService.d.ts +38 -0
- package/dist/services/CapabilityService.d.ts.map +1 -0
- package/dist/services/CapabilityService.js +256 -0
- package/dist/services/CapabilityService.js.map +1 -0
- package/dist/services/ConfigService.d.ts +35 -0
- package/dist/services/ConfigService.d.ts.map +1 -0
- package/dist/services/ConfigService.js +105 -0
- package/dist/services/ConfigService.js.map +1 -0
- package/dist/services/ContextService.d.ts +65 -0
- package/dist/services/ContextService.d.ts.map +1 -0
- package/dist/services/ContextService.js +503 -0
- package/dist/services/ContextService.js.map +1 -0
- package/dist/services/DetectionService.d.ts +76 -0
- package/dist/services/DetectionService.d.ts.map +1 -0
- package/dist/services/DetectionService.js +262 -0
- package/dist/services/DetectionService.js.map +1 -0
- package/dist/services/DispatchService.d.ts +267 -0
- package/dist/services/DispatchService.d.ts.map +1 -0
- package/dist/services/DispatchService.js +1357 -0
- package/dist/services/DispatchService.js.map +1 -0
- package/dist/services/EventService.d.ts +81 -0
- package/dist/services/EventService.d.ts.map +1 -0
- package/dist/services/EventService.js +187 -0
- package/dist/services/EventService.js.map +1 -0
- package/dist/services/GuidanceService.d.ts +64 -0
- package/dist/services/GuidanceService.d.ts.map +1 -0
- package/dist/services/GuidanceService.js +259 -0
- package/dist/services/GuidanceService.js.map +1 -0
- package/dist/services/HealthService.d.ts +43 -0
- package/dist/services/HealthService.d.ts.map +1 -0
- package/dist/services/HealthService.js +276 -0
- package/dist/services/HealthService.js.map +1 -0
- package/dist/services/InstallationService.d.ts +62 -0
- package/dist/services/InstallationService.d.ts.map +1 -0
- package/dist/services/InstallationService.js +204 -0
- package/dist/services/InstallationService.js.map +1 -0
- package/dist/services/LogService.d.ts +35 -0
- package/dist/services/LogService.d.ts.map +1 -0
- package/dist/services/LogService.js +189 -0
- package/dist/services/LogService.js.map +1 -0
- package/dist/services/MemoService.d.ts +39 -0
- package/dist/services/MemoService.d.ts.map +1 -0
- package/dist/services/MemoService.js +288 -0
- package/dist/services/MemoService.js.map +1 -0
- package/dist/services/NodeService.d.ts +90 -0
- package/dist/services/NodeService.d.ts.map +1 -0
- package/dist/services/NodeService.js +958 -0
- package/dist/services/NodeService.js.map +1 -0
- package/dist/services/OpenSpecParser.d.ts +43 -0
- package/dist/services/OpenSpecParser.d.ts.map +1 -0
- package/dist/services/OpenSpecParser.js +191 -0
- package/dist/services/OpenSpecParser.js.map +1 -0
- package/dist/services/ReferenceService.d.ts +35 -0
- package/dist/services/ReferenceService.d.ts.map +1 -0
- package/dist/services/ReferenceService.js +195 -0
- package/dist/services/ReferenceService.js.map +1 -0
- package/dist/services/RepairService.d.ts +36 -0
- package/dist/services/RepairService.d.ts.map +1 -0
- package/dist/services/RepairService.js +429 -0
- package/dist/services/RepairService.js.map +1 -0
- package/dist/services/SearchService.d.ts +34 -0
- package/dist/services/SearchService.d.ts.map +1 -0
- package/dist/services/SearchService.js +293 -0
- package/dist/services/SearchService.js.map +1 -0
- package/dist/services/SessionService.d.ts +136 -0
- package/dist/services/SessionService.d.ts.map +1 -0
- package/dist/services/SessionService.js +297 -0
- package/dist/services/SessionService.js.map +1 -0
- package/dist/services/StateService.d.ts +97 -0
- package/dist/services/StateService.d.ts.map +1 -0
- package/dist/services/StateService.js +846 -0
- package/dist/services/StateService.js.map +1 -0
- package/dist/services/TutorialService.d.ts +114 -0
- package/dist/services/TutorialService.d.ts.map +1 -0
- package/dist/services/TutorialService.js +1262 -0
- package/dist/services/TutorialService.js.map +1 -0
- package/dist/services/WorkspaceService.d.ts +273 -0
- package/dist/services/WorkspaceService.d.ts.map +1 -0
- package/dist/services/WorkspaceService.js +1764 -0
- package/dist/services/WorkspaceService.js.map +1 -0
- package/dist/services/index.d.ts +15 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +14 -0
- package/dist/services/index.js.map +1 -0
- package/dist/storage/FileSystemAdapter.d.ts +223 -0
- package/dist/storage/FileSystemAdapter.d.ts.map +1 -0
- package/dist/storage/FileSystemAdapter.js +384 -0
- package/dist/storage/FileSystemAdapter.js.map +1 -0
- package/dist/storage/JsonStorage.d.ts +158 -0
- package/dist/storage/JsonStorage.d.ts.map +1 -0
- package/dist/storage/JsonStorage.js +613 -0
- package/dist/storage/JsonStorage.js.map +1 -0
- package/dist/storage/MarkdownStorage.d.ts +178 -0
- package/dist/storage/MarkdownStorage.d.ts.map +1 -0
- package/dist/storage/MarkdownStorage.js +918 -0
- package/dist/storage/MarkdownStorage.js.map +1 -0
- package/dist/storage/SessionBindingStorage.d.ts +69 -0
- package/dist/storage/SessionBindingStorage.d.ts.map +1 -0
- package/dist/storage/SessionBindingStorage.js +131 -0
- package/dist/storage/SessionBindingStorage.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/tools/capability.d.ts +18 -0
- package/dist/tools/capability.d.ts.map +1 -0
- package/dist/tools/capability.js +73 -0
- package/dist/tools/capability.js.map +1 -0
- package/dist/tools/config.d.ts +14 -0
- package/dist/tools/config.d.ts.map +1 -0
- package/dist/tools/config.js +61 -0
- package/dist/tools/config.js.map +1 -0
- package/dist/tools/context.d.ts +22 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +139 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/dispatch.d.ts +41 -0
- package/dist/tools/dispatch.d.ts.map +1 -0
- package/dist/tools/dispatch.js +380 -0
- package/dist/tools/dispatch.js.map +1 -0
- package/dist/tools/help.d.ts +44 -0
- package/dist/tools/help.d.ts.map +1 -0
- package/dist/tools/help.js +227 -0
- package/dist/tools/help.js.map +1 -0
- package/dist/tools/import.d.ts +17 -0
- package/dist/tools/import.d.ts.map +1 -0
- package/dist/tools/import.js +96 -0
- package/dist/tools/import.js.map +1 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/log.d.ts +21 -0
- package/dist/tools/log.d.ts.map +1 -0
- package/dist/tools/log.js +93 -0
- package/dist/tools/log.js.map +1 -0
- package/dist/tools/memo.d.ts +26 -0
- package/dist/tools/memo.d.ts.map +1 -0
- package/dist/tools/memo.js +188 -0
- package/dist/tools/memo.js.map +1 -0
- package/dist/tools/node.d.ts +34 -0
- package/dist/tools/node.d.ts.map +1 -0
- package/dist/tools/node.js +328 -0
- package/dist/tools/node.js.map +1 -0
- package/dist/tools/search.d.ts +14 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +95 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/session.d.ts +22 -0
- package/dist/tools/session.d.ts.map +1 -0
- package/dist/tools/session.js +127 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/state.d.ts +10 -0
- package/dist/tools/state.d.ts.map +1 -0
- package/dist/tools/state.js +79 -0
- package/dist/tools/state.js.map +1 -0
- package/dist/tools/workspace.d.ts +38 -0
- package/dist/tools/workspace.d.ts.map +1 -0
- package/dist/tools/workspace.js +240 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/types/capability.d.ts +36 -0
- package/dist/types/capability.d.ts.map +1 -0
- package/dist/types/capability.js +3 -0
- package/dist/types/capability.js.map +1 -0
- package/dist/types/confirmation.d.ts +35 -0
- package/dist/types/confirmation.d.ts.map +1 -0
- package/dist/types/confirmation.js +3 -0
- package/dist/types/confirmation.js.map +1 -0
- package/dist/types/context.d.ts +174 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/context.js +3 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/errors.d.ts +81 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +154 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/guidance.d.ts +162 -0
- package/dist/types/guidance.d.ts.map +1 -0
- package/dist/types/guidance.js +4 -0
- package/dist/types/guidance.js.map +1 -0
- package/dist/types/health.d.ts +61 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +3 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/memo.d.ts +132 -0
- package/dist/types/memo.d.ts.map +1 -0
- package/dist/types/memo.js +3 -0
- package/dist/types/memo.js.map +1 -0
- package/dist/types/node.d.ts +316 -0
- package/dist/types/node.d.ts.map +1 -0
- package/dist/types/node.js +3 -0
- package/dist/types/node.js.map +1 -0
- package/dist/types/repair.d.ts +62 -0
- package/dist/types/repair.d.ts.map +1 -0
- package/dist/types/repair.js +4 -0
- package/dist/types/repair.js.map +1 -0
- package/dist/types/search.d.ts +58 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +3 -0
- package/dist/types/search.js.map +1 -0
- package/dist/types/settings.d.ts +109 -0
- package/dist/types/settings.d.ts.map +1 -0
- package/dist/types/settings.js +30 -0
- package/dist/types/settings.js.map +1 -0
- package/dist/types/workspace.d.ts +357 -0
- package/dist/types/workspace.d.ts.map +1 -0
- package/dist/types/workspace.js +3 -0
- package/dist/types/workspace.js.map +1 -0
- package/dist/utils/contentValidation.d.ts +47 -0
- package/dist/utils/contentValidation.d.ts.map +1 -0
- package/dist/utils/contentValidation.js +93 -0
- package/dist/utils/contentValidation.js.map +1 -0
- package/dist/utils/devLog.d.ts +43 -0
- package/dist/utils/devLog.d.ts.map +1 -0
- package/dist/utils/devLog.js +94 -0
- package/dist/utils/devLog.js.map +1 -0
- package/dist/utils/errorLogger.d.ts +27 -0
- package/dist/utils/errorLogger.d.ts.map +1 -0
- package/dist/utils/errorLogger.js +105 -0
- package/dist/utils/errorLogger.js.map +1 -0
- package/dist/utils/git.d.ts +123 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +400 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/hash.d.ts +32 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +37 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/id.d.ts +54 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +96 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +42 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +228 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/manualChangeFormatter.d.ts +8 -0
- package/dist/utils/manualChangeFormatter.d.ts.map +1 -0
- package/dist/utils/manualChangeFormatter.js +21 -0
- package/dist/utils/manualChangeFormatter.js.map +1 -0
- package/dist/utils/paramValidator.d.ts +35 -0
- package/dist/utils/paramValidator.d.ts.map +1 -0
- package/dist/utils/paramValidator.js +214 -0
- package/dist/utils/paramValidator.js.map +1 -0
- package/dist/utils/port.d.ts +7 -0
- package/dist/utils/port.d.ts.map +1 -0
- package/dist/utils/port.js +28 -0
- package/dist/utils/port.js.map +1 -0
- package/dist/utils/processManager.d.ts +53 -0
- package/dist/utils/processManager.d.ts.map +1 -0
- package/dist/utils/processManager.js +267 -0
- package/dist/utils/processManager.js.map +1 -0
- package/dist/utils/sessionLogger.d.ts +28 -0
- package/dist/utils/sessionLogger.d.ts.map +1 -0
- package/dist/utils/sessionLogger.js +142 -0
- package/dist/utils/sessionLogger.js.map +1 -0
- package/dist/utils/time.d.ts +15 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +32 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/utils/validation.d.ts +23 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +88 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs//346/227/245/345/277/227/347/263/273/347/273/237.md +389 -0
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +1446 -0
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214/344/270/216/346/212/200/346/234/257/346/214/207/345/215/227.md +873 -0
- package/package.json +89 -0
- package/plugin/README.md +141 -0
- package/plugin/agents/xtep-executor.md +114 -0
- package/plugin/agents/xtep-reviewer.md +133 -0
- package/plugin/docs/diagnostic-guide.md +128 -0
- package/plugin/hooks/hooks.json.deprecated +70 -0
- package/plugin/scripts/cursor-hook-entry.cjs +217 -0
- package/plugin/scripts/hook-entry.cjs +663 -0
- package/plugin/scripts/openspec-import.cjs +714 -0
- package/plugin/scripts/shared/binding.cjs +98 -0
- package/plugin/scripts/shared/config.cjs +65 -0
- package/plugin/scripts/shared/context.cjs +120 -0
- package/plugin/scripts/shared/index.cjs +34 -0
- package/plugin/scripts/shared/logger.cjs +196 -0
- package/plugin/scripts/shared/reminder.cjs +261 -0
- package/plugin/scripts/shared/utils.cjs +62 -0
- package/plugin/scripts/shared/workspace.cjs +322 -0
- package/plugin/skills/aligning-intent/SKILL.md +275 -0
- package/plugin/skills/analyzing-measurements/SKILL.md +223 -0
- package/plugin/skills/bootstrapping-workspace/SKILL.md +260 -0
- package/plugin/skills/designing-solutions/SKILL.md +363 -0
- package/plugin/skills/diagnosing-issues/SKILL.md +219 -0
- package/plugin/skills/discovering-context/SKILL.md +283 -0
- package/plugin/skills/dispatching-parent/SKILL.md +399 -0
- package/plugin/skills/executing-task/SKILL.md +340 -0
- package/plugin/skills/memo-create/SKILL.md +222 -0
- package/plugin/skills/planning-verification/SKILL.md +245 -0
- package/plugin/skills/preparing-dispatch/SKILL.md +299 -0
- package/plugin/skills/researching-tech/SKILL.md +223 -0
- package/plugin/skills/reviewing-quality/SKILL.md +354 -0
- package/plugin/skills/reviewing-spec/SKILL.md +333 -0
- package/plugin/skills/starting-info-flow/SKILL.md +196 -0
- package/web/README.md +5 -0
- package//351/205/215/347/275/256/346/226/271/345/274/217.md +330 -0
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
// src/storage/MarkdownStorage.ts
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// ⚠️ 重要:参数命名约定
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// 本文件中的方法参数遵循以下命名规范:
|
|
6
|
+
//
|
|
7
|
+
// - workspaceId: 工作区的唯一标识符(如 "ws-abc123")
|
|
8
|
+
// 用于工作区级别的操作(Workspace.md, Log.md 等)
|
|
9
|
+
//
|
|
10
|
+
// - wsDirName: 工作区的实际目录名(可能与 workspaceId 不同)
|
|
11
|
+
// 用于节点级别的操作,因为需要先定位工作区目录
|
|
12
|
+
//
|
|
13
|
+
// - nodeDirName: 节点的实际目录名(可能与 nodeId 不同)
|
|
14
|
+
// 从 graph.json 中获取:graph.nodes[nodeId].dirName || nodeId
|
|
15
|
+
//
|
|
16
|
+
// 调用方在使用节点相关方法前,必须先通过 JsonStorage.readGraph() 获取正确的 dirName
|
|
17
|
+
// 参见 src/http/services.ts 中的 resolveDirNames() 辅助函数
|
|
18
|
+
// ============================================================================
|
|
19
|
+
import { formatShort } from "../utils/time.js";
|
|
20
|
+
import { validateMultilineContent, validateSingleLineContent, validateRules, escapeTableCell, unescapeTableCell, } from "../utils/contentValidation.js";
|
|
21
|
+
/**
|
|
22
|
+
* Markdown 存储封装
|
|
23
|
+
* 提供 Markdown 文件的读写和 frontmatter 解析
|
|
24
|
+
*/
|
|
25
|
+
export class MarkdownStorage {
|
|
26
|
+
fs;
|
|
27
|
+
constructor(fs) {
|
|
28
|
+
this.fs = fs;
|
|
29
|
+
}
|
|
30
|
+
// ========== 解析与序列化 ==========
|
|
31
|
+
/**
|
|
32
|
+
* 解析 Markdown 内容(提取 frontmatter 和正文)
|
|
33
|
+
* 增强健壮性:处理各种边界情况和格式变体
|
|
34
|
+
*/
|
|
35
|
+
parse(content) {
|
|
36
|
+
// 处理空内容
|
|
37
|
+
if (!content || typeof content !== "string") {
|
|
38
|
+
return {
|
|
39
|
+
frontmatter: {},
|
|
40
|
+
content: ""
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// 规范化换行符(处理 Windows \r\n)
|
|
44
|
+
const normalizedContent = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
45
|
+
// 更宽松的 frontmatter 正则:允许开头有空白,末尾可选换行
|
|
46
|
+
const frontmatterRegex = /^\s*---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
|
|
47
|
+
const match = normalizedContent.match(frontmatterRegex);
|
|
48
|
+
if (!match) {
|
|
49
|
+
return {
|
|
50
|
+
frontmatter: {},
|
|
51
|
+
content: normalizedContent.trim()
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const frontmatterStr = match[1];
|
|
55
|
+
const body = match[2];
|
|
56
|
+
// 增强的 YAML 解析(处理引号值、冒号在值中等情况)
|
|
57
|
+
const frontmatter = {};
|
|
58
|
+
const lines = frontmatterStr.split("\n");
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
// 跳过空行和注释
|
|
61
|
+
const trimmedLine = line.trim();
|
|
62
|
+
if (!trimmedLine || trimmedLine.startsWith("#")) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const colonIndex = line.indexOf(":");
|
|
66
|
+
if (colonIndex !== -1) {
|
|
67
|
+
const key = line.substring(0, colonIndex).trim();
|
|
68
|
+
let value = line.substring(colonIndex + 1).trim();
|
|
69
|
+
// 处理引号包裹的值(支持单引号和双引号)
|
|
70
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
71
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
72
|
+
value = value.slice(1, -1);
|
|
73
|
+
}
|
|
74
|
+
if (key) {
|
|
75
|
+
frontmatter[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
frontmatter,
|
|
81
|
+
content: body
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 安全地提取 section 内容
|
|
86
|
+
* @param content Markdown 内容
|
|
87
|
+
* @param sectionName section 名称(不含 ##)
|
|
88
|
+
* @param isLast 是否是最后一个 section(不需要后续 section 作为结束标记)
|
|
89
|
+
* @returns 提取的内容,如果未找到返回空字符串
|
|
90
|
+
*/
|
|
91
|
+
extractSection(content, sectionName, isLast = false) {
|
|
92
|
+
if (!content)
|
|
93
|
+
return "";
|
|
94
|
+
// 规范化换行符
|
|
95
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
96
|
+
// 构建更宽松的正则:允许 section 标题后有可选的提示行(> 开头)
|
|
97
|
+
const pattern = isLast
|
|
98
|
+
? new RegExp(`## ${sectionName}\\n\\n(?:>.*\\n\\n)?([\\s\\S]*)$`)
|
|
99
|
+
: new RegExp(`## ${sectionName}\\n\\n(?:>.*\\n\\n)?([\\s\\S]*?)(?=\\n## |$)`);
|
|
100
|
+
const match = normalized.match(pattern);
|
|
101
|
+
return match ? match[1].trim() : "";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 安全解析列表项
|
|
105
|
+
* @param content section 内容
|
|
106
|
+
* @returns 列表项数组
|
|
107
|
+
*/
|
|
108
|
+
parseListItems(content) {
|
|
109
|
+
if (!content)
|
|
110
|
+
return [];
|
|
111
|
+
return content
|
|
112
|
+
.split("\n")
|
|
113
|
+
.filter(line => line.trim().startsWith("- "))
|
|
114
|
+
.map(line => line.trim().substring(2).trim())
|
|
115
|
+
.filter(item => item.length > 0);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 序列化 Markdown 数据
|
|
119
|
+
*/
|
|
120
|
+
serialize(data) {
|
|
121
|
+
const frontmatterLines = [];
|
|
122
|
+
for (const [key, value] of Object.entries(data.frontmatter)) {
|
|
123
|
+
frontmatterLines.push(`${key}: ${value}`);
|
|
124
|
+
}
|
|
125
|
+
return `---\n${frontmatterLines.join("\n")}\n---\n${data.content}`;
|
|
126
|
+
}
|
|
127
|
+
// ========== Workspace.md ==========
|
|
128
|
+
/**
|
|
129
|
+
* 读取 Workspace.md
|
|
130
|
+
* @param isArchived 是否为归档工作区
|
|
131
|
+
*/
|
|
132
|
+
async readWorkspaceMd(projectRoot, workspaceId, isArchived = false) {
|
|
133
|
+
const mdPath = isArchived
|
|
134
|
+
? this.fs.getWorkspaceMdPathWithArchive(projectRoot, workspaceId, true)
|
|
135
|
+
: this.fs.getWorkspaceMdPath(projectRoot, workspaceId);
|
|
136
|
+
const content = await this.fs.readFile(mdPath);
|
|
137
|
+
const parsed = this.parse(content);
|
|
138
|
+
// 解析规则
|
|
139
|
+
const rules = [];
|
|
140
|
+
const rulesMatch = parsed.content.match(/## 规则\n\n(?:>.*\n\n)?([\s\S]*?)(?=\n## |$)/);
|
|
141
|
+
if (rulesMatch) {
|
|
142
|
+
const rulesContent = rulesMatch[1].trim();
|
|
143
|
+
if (rulesContent) {
|
|
144
|
+
const ruleLines = rulesContent.split("\n").filter(line => line.startsWith("- "));
|
|
145
|
+
for (const line of ruleLines) {
|
|
146
|
+
rules.push(line.substring(2).trim());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 解析文档
|
|
151
|
+
const docs = [];
|
|
152
|
+
const docsMatch = parsed.content.match(/## 文档\n\n(?:>.*\n\n)?([\s\S]*?)(?=\n## |$)/);
|
|
153
|
+
if (docsMatch) {
|
|
154
|
+
const docsContent = docsMatch[1].trim();
|
|
155
|
+
if (docsContent) {
|
|
156
|
+
const docLines = docsContent.split("\n").filter(line => line.startsWith("- "));
|
|
157
|
+
for (const line of docLines) {
|
|
158
|
+
const docMatch = line.match(/- \[(.+?)\]\((.+?)\)(?: - (.+))?/);
|
|
159
|
+
if (docMatch) {
|
|
160
|
+
docs.push({
|
|
161
|
+
path: docMatch[2],
|
|
162
|
+
description: docMatch[3] || docMatch[1]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// goal 已移至根节点 requirement,不再从 Workspace.md 读取
|
|
169
|
+
return {
|
|
170
|
+
name: parsed.frontmatter.name || "",
|
|
171
|
+
createdAt: parsed.frontmatter.createdAt || "",
|
|
172
|
+
updatedAt: parsed.frontmatter.updatedAt || "",
|
|
173
|
+
rules,
|
|
174
|
+
docs,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 读取旧版 Workspace.md 中的 goal(仅用于懒迁移)
|
|
179
|
+
* 新版工作区 goal 存储在根节点 requirement 中
|
|
180
|
+
* @returns goal 内容,如果不存在或为空返回 null
|
|
181
|
+
*/
|
|
182
|
+
async readLegacyGoal(projectRoot, workspaceId, isArchived = false) {
|
|
183
|
+
try {
|
|
184
|
+
const mdPath = isArchived
|
|
185
|
+
? this.fs.getWorkspaceMdPathWithArchive(projectRoot, workspaceId, true)
|
|
186
|
+
: this.fs.getWorkspaceMdPath(projectRoot, workspaceId);
|
|
187
|
+
const content = await this.fs.readFile(mdPath);
|
|
188
|
+
const parsed = this.parse(content);
|
|
189
|
+
// 解析旧版 ## 目标 section
|
|
190
|
+
const goalMatch = parsed.content.match(/## 目标\n\n([\s\S]*?)(?=\n## |$)/);
|
|
191
|
+
if (goalMatch) {
|
|
192
|
+
const goal = goalMatch[1].trim();
|
|
193
|
+
if (goal) {
|
|
194
|
+
return goal;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 写入 Workspace.md
|
|
205
|
+
*/
|
|
206
|
+
async writeWorkspaceMd(projectRoot, workspaceId, data) {
|
|
207
|
+
// 验证内容格式
|
|
208
|
+
validateSingleLineContent(data.name, "工作区名称");
|
|
209
|
+
// goal 已移至根节点 requirement,不再写入 Workspace.md
|
|
210
|
+
if (data.rules.length > 0) {
|
|
211
|
+
validateRules(data.rules);
|
|
212
|
+
}
|
|
213
|
+
const mdPath = this.fs.getWorkspaceMdPath(projectRoot, workspaceId);
|
|
214
|
+
const rulesContent = data.rules.length > 0
|
|
215
|
+
? data.rules.map(rule => `- ${rule}`).join("\n")
|
|
216
|
+
: "";
|
|
217
|
+
const docsContent = data.docs.length > 0
|
|
218
|
+
? data.docs.map(doc => `- [${doc.description}](${doc.path})`).join("\n")
|
|
219
|
+
: "";
|
|
220
|
+
const content = `---
|
|
221
|
+
name: ${data.name}
|
|
222
|
+
createdAt: ${data.createdAt}
|
|
223
|
+
updatedAt: ${data.updatedAt}
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 规则
|
|
227
|
+
|
|
228
|
+
> 只读,上下文必须遵循的约束
|
|
229
|
+
|
|
230
|
+
${rulesContent}
|
|
231
|
+
|
|
232
|
+
## 文档
|
|
233
|
+
|
|
234
|
+
> 读写,全局参考文档
|
|
235
|
+
|
|
236
|
+
${docsContent}
|
|
237
|
+
`;
|
|
238
|
+
await this.fs.writeFile(mdPath, content);
|
|
239
|
+
}
|
|
240
|
+
// ========== Node Info.md ==========
|
|
241
|
+
/**
|
|
242
|
+
* 读取节点 Info.md
|
|
243
|
+
* @param wsDirName 工作区目录名(非 workspaceId,需通过 graph 解析)
|
|
244
|
+
* @param nodeDirName 节点目录名(非 nodeId,需通过 graph.nodes[nodeId].dirName 获取)
|
|
245
|
+
* @param isArchived 是否为归档工作区
|
|
246
|
+
*/
|
|
247
|
+
async readNodeInfo(projectRoot, wsDirName, nodeDirName, isArchived = false) {
|
|
248
|
+
const infoPath = isArchived
|
|
249
|
+
? this.fs.getNodeInfoPathWithArchive(projectRoot, wsDirName, nodeDirName, true)
|
|
250
|
+
: this.fs.getNodeInfoPath(projectRoot, wsDirName, nodeDirName);
|
|
251
|
+
const content = await this.fs.readFile(infoPath);
|
|
252
|
+
const parsed = this.parse(content);
|
|
253
|
+
// 解析需求
|
|
254
|
+
let requirement = "";
|
|
255
|
+
const reqMatch = parsed.content.match(/## 需求\n\n([\s\S]*?)(?=\n## |$)/);
|
|
256
|
+
if (reqMatch) {
|
|
257
|
+
requirement = reqMatch[1].trim();
|
|
258
|
+
}
|
|
259
|
+
// 解析验收标准
|
|
260
|
+
const acceptanceCriteria = [];
|
|
261
|
+
const acMatch = parsed.content.match(/## 验收标准\n\n\| 条件 \(WHEN\) \| 期望结果 \(THEN\) \|\n\|[-\s|]+\|\n([\s\S]*?)(?=\n## |$)/);
|
|
262
|
+
if (acMatch) {
|
|
263
|
+
const acContent = acMatch[1].trim();
|
|
264
|
+
if (acContent) {
|
|
265
|
+
const acLines = acContent.split("\n").filter(line => line.startsWith("|"));
|
|
266
|
+
for (const line of acLines) {
|
|
267
|
+
const cells = line.split("|").map(c => c.trim()).filter(Boolean);
|
|
268
|
+
if (cells.length >= 2) {
|
|
269
|
+
acceptanceCriteria.push({
|
|
270
|
+
when: unescapeTableCell(cells[0]),
|
|
271
|
+
then: unescapeTableCell(cells[1])
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// 解析文档引用
|
|
278
|
+
const docs = [];
|
|
279
|
+
const docsMatch = parsed.content.match(/## 文档引用\n\n(?:>.*\n\n)?([\s\S]*?)(?=\n## |$)/);
|
|
280
|
+
if (docsMatch) {
|
|
281
|
+
const docsContent = docsMatch[1].trim();
|
|
282
|
+
if (docsContent) {
|
|
283
|
+
const docLines = docsContent.split("\n").filter(line => line.startsWith("- "));
|
|
284
|
+
for (const line of docLines) {
|
|
285
|
+
const docMatch = line.match(/- \[(.+?)\]\((.+?)\)(?: - (.+))?/);
|
|
286
|
+
if (docMatch) {
|
|
287
|
+
docs.push({
|
|
288
|
+
path: docMatch[2],
|
|
289
|
+
description: docMatch[3] || docMatch[1]
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// 解析备注
|
|
296
|
+
let notes = "";
|
|
297
|
+
const notesMatch = parsed.content.match(/## 备注\n\n([\s\S]*?)(?=\n## |$)/);
|
|
298
|
+
if (notesMatch) {
|
|
299
|
+
notes = notesMatch[1].trim();
|
|
300
|
+
}
|
|
301
|
+
// 解析结论
|
|
302
|
+
let conclusion = "";
|
|
303
|
+
const conclusionMatch = parsed.content.match(/## 结论\n\n(?:>.*\n\n)?([\s\S]*?)$/);
|
|
304
|
+
if (conclusionMatch) {
|
|
305
|
+
conclusion = conclusionMatch[1].trim();
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
id: parsed.frontmatter.id || nodeDirName,
|
|
309
|
+
type: parsed.frontmatter.type || "execution",
|
|
310
|
+
title: parsed.frontmatter.title || "",
|
|
311
|
+
status: parsed.frontmatter.status || "pending",
|
|
312
|
+
createdAt: parsed.frontmatter.createdAt || "",
|
|
313
|
+
updatedAt: parsed.frontmatter.updatedAt || "",
|
|
314
|
+
requirement,
|
|
315
|
+
docs,
|
|
316
|
+
notes,
|
|
317
|
+
conclusion,
|
|
318
|
+
acceptanceCriteria: acceptanceCriteria.length > 0 ? acceptanceCriteria : undefined
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 写入节点 Info.md
|
|
323
|
+
* @param wsDirName 工作区目录名(非 workspaceId)
|
|
324
|
+
* @param nodeDirName 节点目录名(非 nodeId)
|
|
325
|
+
*/
|
|
326
|
+
async writeNodeInfo(projectRoot, wsDirName, nodeDirName, data) {
|
|
327
|
+
// 验证内容格式
|
|
328
|
+
validateSingleLineContent(data.title, "节点标题");
|
|
329
|
+
if (data.requirement) {
|
|
330
|
+
validateMultilineContent(data.requirement, "需求描述");
|
|
331
|
+
}
|
|
332
|
+
if (data.notes) {
|
|
333
|
+
validateMultilineContent(data.notes, "备注");
|
|
334
|
+
}
|
|
335
|
+
if (data.conclusion) {
|
|
336
|
+
validateMultilineContent(data.conclusion, "结论");
|
|
337
|
+
}
|
|
338
|
+
const infoPath = this.fs.getNodeInfoPath(projectRoot, wsDirName, nodeDirName);
|
|
339
|
+
const docsContent = data.docs.length > 0
|
|
340
|
+
? data.docs.map(doc => `- [${doc.description}](${doc.path})`).join("\n")
|
|
341
|
+
: "";
|
|
342
|
+
// 渲染验收标准表格
|
|
343
|
+
let acceptanceCriteriaContent = "";
|
|
344
|
+
if (data.acceptanceCriteria && data.acceptanceCriteria.length > 0) {
|
|
345
|
+
const rows = data.acceptanceCriteria.map(ac => `| ${escapeTableCell(ac.when)} | ${escapeTableCell(ac.then)} |`).join("\n");
|
|
346
|
+
acceptanceCriteriaContent = `\n## 验收标准
|
|
347
|
+
|
|
348
|
+
| 条件 (WHEN) | 期望结果 (THEN) |
|
|
349
|
+
|-------------|-----------------|
|
|
350
|
+
${rows}
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
const typeLabel = data.type === "planning" ? "规划" : "执行";
|
|
354
|
+
const content = `---
|
|
355
|
+
id: ${data.id}
|
|
356
|
+
type: ${data.type}
|
|
357
|
+
title: ${data.title}
|
|
358
|
+
status: ${data.status}
|
|
359
|
+
createdAt: ${data.createdAt}
|
|
360
|
+
updatedAt: ${data.updatedAt}
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 节点类型
|
|
364
|
+
|
|
365
|
+
${typeLabel}节点
|
|
366
|
+
|
|
367
|
+
## 需求
|
|
368
|
+
|
|
369
|
+
${data.requirement ?? ""}
|
|
370
|
+
${acceptanceCriteriaContent}
|
|
371
|
+
## 文档引用
|
|
372
|
+
|
|
373
|
+
> 格式:- [文件名](路径) - 说明 (状态)
|
|
374
|
+
|
|
375
|
+
${docsContent}
|
|
376
|
+
|
|
377
|
+
## 备注
|
|
378
|
+
|
|
379
|
+
${data.notes}
|
|
380
|
+
|
|
381
|
+
## 结论
|
|
382
|
+
|
|
383
|
+
> 节点完成时填写
|
|
384
|
+
|
|
385
|
+
${data.conclusion}
|
|
386
|
+
`;
|
|
387
|
+
await this.fs.writeFile(infoPath, content);
|
|
388
|
+
}
|
|
389
|
+
// ========== Log.md ==========
|
|
390
|
+
/**
|
|
391
|
+
* 读取日志
|
|
392
|
+
* 增强健壮性:处理各种表格格式变体
|
|
393
|
+
*/
|
|
394
|
+
async readLog(projectRoot, workspaceId, nodeId) {
|
|
395
|
+
const logPath = nodeId
|
|
396
|
+
? this.fs.getNodeLogPath(projectRoot, workspaceId, nodeId)
|
|
397
|
+
: this.fs.getWorkspaceLogPath(projectRoot, workspaceId);
|
|
398
|
+
if (!(await this.fs.exists(logPath))) {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
const content = await this.fs.readFile(logPath);
|
|
403
|
+
return this.parseLogTableSafe(content);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// 文件读取或解析失败,返回空数组
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 安全解析日志表格
|
|
412
|
+
* @param content 日志内容
|
|
413
|
+
* @returns 解析后的日志条目
|
|
414
|
+
*/
|
|
415
|
+
parseLogTableSafe(content) {
|
|
416
|
+
if (!content || typeof content !== "string") {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const logs = [];
|
|
420
|
+
// 规范化换行符
|
|
421
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
422
|
+
// 解析表格行
|
|
423
|
+
const lines = normalized.split("\n");
|
|
424
|
+
for (const line of lines) {
|
|
425
|
+
// 跳过非表格行、表头和分隔线
|
|
426
|
+
if (!line.startsWith("|"))
|
|
427
|
+
continue;
|
|
428
|
+
if (line.includes("时间") || line.includes("Time"))
|
|
429
|
+
continue;
|
|
430
|
+
if (/^\|[\s-:|]+\|$/.test(line))
|
|
431
|
+
continue;
|
|
432
|
+
const parts = line.split("|").map(p => p.trim()).filter(p => p);
|
|
433
|
+
if (parts.length >= 3) {
|
|
434
|
+
// 还原转义的表格内容
|
|
435
|
+
const event = unescapeTableCell(parts[2] || "");
|
|
436
|
+
logs.push({
|
|
437
|
+
time: parts[0] || "",
|
|
438
|
+
operator: parts[1] || "AI",
|
|
439
|
+
event
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return logs;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* 获取日志文件路径(支持归档目录)
|
|
447
|
+
*/
|
|
448
|
+
getLogPath(projectRoot, wsDirName, nodeId, isArchived = false) {
|
|
449
|
+
const basePath = isArchived
|
|
450
|
+
? this.fs.getArchivePath(projectRoot, wsDirName)
|
|
451
|
+
: this.fs.getWorkspacePath(projectRoot, wsDirName);
|
|
452
|
+
if (nodeId) {
|
|
453
|
+
return `${basePath}/nodes/${nodeId}/Log.md`;
|
|
454
|
+
}
|
|
455
|
+
return `${basePath}/Log.md`;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* 追加日志
|
|
459
|
+
* @param isArchived 是否写入归档目录(当 nodeId 为 true 时表示 isArchived)
|
|
460
|
+
*/
|
|
461
|
+
async appendLog(projectRoot, workspaceId, entry, nodeIdOrIsArchived, isArchived = false) {
|
|
462
|
+
// 处理参数重载:appendLog(projectRoot, workspaceId, entry, isArchived) 或 appendLog(projectRoot, workspaceId, entry, nodeId, isArchived)
|
|
463
|
+
let nodeId;
|
|
464
|
+
let archived = isArchived;
|
|
465
|
+
if (typeof nodeIdOrIsArchived === "boolean") {
|
|
466
|
+
archived = nodeIdOrIsArchived;
|
|
467
|
+
}
|
|
468
|
+
else if (typeof nodeIdOrIsArchived === "string") {
|
|
469
|
+
nodeId = nodeIdOrIsArchived;
|
|
470
|
+
}
|
|
471
|
+
const logPath = this.getLogPath(projectRoot, workspaceId, nodeId, archived);
|
|
472
|
+
let content;
|
|
473
|
+
if (await this.fs.exists(logPath)) {
|
|
474
|
+
content = await this.fs.readFile(logPath);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
content = `## 日志
|
|
478
|
+
|
|
479
|
+
| 时间 | 操作者 | 事件 |
|
|
480
|
+
|------|--------|------|
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
const formattedTime = formatShort(entry.time);
|
|
484
|
+
// 转义表格单元格内容(换行符和管道符)
|
|
485
|
+
const escapedEvent = escapeTableCell(entry.event);
|
|
486
|
+
content += `| ${formattedTime} | ${entry.operator} | ${escapedEvent} |\n`;
|
|
487
|
+
await this.fs.writeFile(logPath, content);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* 创建空的日志文件
|
|
491
|
+
*/
|
|
492
|
+
async createEmptyLog(projectRoot, workspaceId, nodeId) {
|
|
493
|
+
const logPath = nodeId
|
|
494
|
+
? this.fs.getNodeLogPath(projectRoot, workspaceId, nodeId)
|
|
495
|
+
: this.fs.getWorkspaceLogPath(projectRoot, workspaceId);
|
|
496
|
+
const content = `## 日志
|
|
497
|
+
|
|
498
|
+
| 时间 | 操作者 | 事件 |
|
|
499
|
+
|------|--------|------|
|
|
500
|
+
`;
|
|
501
|
+
await this.fs.writeFile(logPath, content);
|
|
502
|
+
}
|
|
503
|
+
// ========== Problem.md ==========
|
|
504
|
+
/**
|
|
505
|
+
* 读取问题
|
|
506
|
+
* @param isArchived 是否为归档工作区
|
|
507
|
+
*/
|
|
508
|
+
async readProblem(projectRoot, workspaceId, nodeId, isArchived = false) {
|
|
509
|
+
const problemPath = nodeId
|
|
510
|
+
? (isArchived
|
|
511
|
+
? this.fs.getNodeProblemPathWithArchive(projectRoot, workspaceId, nodeId, true)
|
|
512
|
+
: this.fs.getNodeProblemPath(projectRoot, workspaceId, nodeId))
|
|
513
|
+
: (isArchived
|
|
514
|
+
? this.fs.getWorkspaceProblemPathWithArchive(projectRoot, workspaceId, true)
|
|
515
|
+
: this.fs.getWorkspaceProblemPath(projectRoot, workspaceId));
|
|
516
|
+
if (!(await this.fs.exists(problemPath))) {
|
|
517
|
+
return {
|
|
518
|
+
currentProblem: "(暂无)",
|
|
519
|
+
nextStep: "(暂无)"
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const content = await this.fs.readFile(problemPath);
|
|
523
|
+
let currentProblem = "(暂无)";
|
|
524
|
+
const problemMatch = content.match(/## 当前问题\n\n([\s\S]*?)(?=\n## |$)/);
|
|
525
|
+
if (problemMatch) {
|
|
526
|
+
currentProblem = problemMatch[1].trim();
|
|
527
|
+
}
|
|
528
|
+
let nextStep = "(暂无)";
|
|
529
|
+
const nextMatch = content.match(/## 下一步\n\n([\s\S]*?)$/);
|
|
530
|
+
if (nextMatch) {
|
|
531
|
+
nextStep = nextMatch[1].trim();
|
|
532
|
+
}
|
|
533
|
+
return { currentProblem, nextStep };
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* 写入问题
|
|
537
|
+
*/
|
|
538
|
+
async writeProblem(projectRoot, workspaceId, data, nodeId) {
|
|
539
|
+
// 验证内容格式(跳过默认占位符)
|
|
540
|
+
if (data.currentProblem && data.currentProblem !== "(暂无)") {
|
|
541
|
+
validateMultilineContent(data.currentProblem, "当前问题");
|
|
542
|
+
}
|
|
543
|
+
if (data.nextStep && data.nextStep !== "(暂无)") {
|
|
544
|
+
validateMultilineContent(data.nextStep, "下一步");
|
|
545
|
+
}
|
|
546
|
+
const problemPath = nodeId
|
|
547
|
+
? this.fs.getNodeProblemPath(projectRoot, workspaceId, nodeId)
|
|
548
|
+
: this.fs.getWorkspaceProblemPath(projectRoot, workspaceId);
|
|
549
|
+
const content = `## 当前问题
|
|
550
|
+
|
|
551
|
+
${data.currentProblem}
|
|
552
|
+
|
|
553
|
+
## 下一步
|
|
554
|
+
|
|
555
|
+
${data.nextStep}
|
|
556
|
+
`;
|
|
557
|
+
await this.fs.writeFile(problemPath, content);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* 创建空的问题文件
|
|
561
|
+
*/
|
|
562
|
+
async createEmptyProblem(projectRoot, workspaceId, nodeId) {
|
|
563
|
+
await this.writeProblem(projectRoot, workspaceId, {
|
|
564
|
+
currentProblem: "(暂无)",
|
|
565
|
+
nextStep: "(暂无)"
|
|
566
|
+
}, nodeId);
|
|
567
|
+
}
|
|
568
|
+
// ========== 原始文件读取 ==========
|
|
569
|
+
/**
|
|
570
|
+
* 读取 Workspace.md 原始内容
|
|
571
|
+
* @param isArchived 是否为归档工作区
|
|
572
|
+
*/
|
|
573
|
+
async readWorkspaceMdRaw(projectRoot, workspaceId, isArchived = false) {
|
|
574
|
+
const mdPath = isArchived
|
|
575
|
+
? this.fs.getWorkspaceMdPathWithArchive(projectRoot, workspaceId, true)
|
|
576
|
+
: this.fs.getWorkspaceMdPath(projectRoot, workspaceId);
|
|
577
|
+
return await this.fs.readFile(mdPath);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* 读取节点 Info.md 原始内容
|
|
581
|
+
* @param wsDirName 工作区目录名(非 workspaceId)
|
|
582
|
+
* @param nodeDirName 节点目录名(非 nodeId)
|
|
583
|
+
* @param isArchived 是否为归档工作区
|
|
584
|
+
*/
|
|
585
|
+
async readNodeInfoRaw(projectRoot, wsDirName, nodeDirName, isArchived = false) {
|
|
586
|
+
const infoPath = isArchived
|
|
587
|
+
? this.fs.getNodeInfoPathWithArchive(projectRoot, wsDirName, nodeDirName, true)
|
|
588
|
+
: this.fs.getNodeInfoPath(projectRoot, wsDirName, nodeDirName);
|
|
589
|
+
return await this.fs.readFile(infoPath);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* 读取日志原始内容
|
|
593
|
+
*/
|
|
594
|
+
async readLogRaw(projectRoot, workspaceId, nodeId, isArchived = false) {
|
|
595
|
+
let logPath;
|
|
596
|
+
if (nodeId) {
|
|
597
|
+
logPath = isArchived
|
|
598
|
+
? this.fs.getNodeLogPathWithArchive(projectRoot, workspaceId, nodeId, true)
|
|
599
|
+
: this.fs.getNodeLogPath(projectRoot, workspaceId, nodeId);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// 工作区级别日志暂不支持归档路径(归档工作区通常只读取节点)
|
|
603
|
+
logPath = this.fs.getWorkspaceLogPath(projectRoot, workspaceId);
|
|
604
|
+
}
|
|
605
|
+
if (!(await this.fs.exists(logPath))) {
|
|
606
|
+
return "";
|
|
607
|
+
}
|
|
608
|
+
return await this.fs.readFile(logPath);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* 读取问题原始内容
|
|
612
|
+
*/
|
|
613
|
+
async readProblemRaw(projectRoot, workspaceId, nodeId, isArchived = false) {
|
|
614
|
+
let problemPath;
|
|
615
|
+
if (nodeId) {
|
|
616
|
+
problemPath = isArchived
|
|
617
|
+
? this.fs.getNodeProblemPathWithArchive(projectRoot, workspaceId, nodeId, true)
|
|
618
|
+
: this.fs.getNodeProblemPath(projectRoot, workspaceId, nodeId);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
// 工作区级别问题暂不支持归档路径
|
|
622
|
+
problemPath = this.fs.getWorkspaceProblemPath(projectRoot, workspaceId);
|
|
623
|
+
}
|
|
624
|
+
if (!(await this.fs.exists(problemPath))) {
|
|
625
|
+
return "";
|
|
626
|
+
}
|
|
627
|
+
return await this.fs.readFile(problemPath);
|
|
628
|
+
}
|
|
629
|
+
// ========== Phase 2 扩展方法 ==========
|
|
630
|
+
/**
|
|
631
|
+
* 解析日志表格(返回带类型的日志条目)
|
|
632
|
+
* 增强健壮性:处理各种表格格式变体
|
|
633
|
+
*/
|
|
634
|
+
parseLogTable(content) {
|
|
635
|
+
if (!content || typeof content !== "string") {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
// 规范化换行符
|
|
639
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
640
|
+
const lines = normalized.split("\n");
|
|
641
|
+
const entries = [];
|
|
642
|
+
for (const line of lines) {
|
|
643
|
+
// 跳过非表格行
|
|
644
|
+
if (!line.startsWith("|"))
|
|
645
|
+
continue;
|
|
646
|
+
// 跳过表头和分隔线
|
|
647
|
+
if (line.includes("时间") || line.includes("Time"))
|
|
648
|
+
continue;
|
|
649
|
+
if (/^\|[\s-:|]+\|$/.test(line))
|
|
650
|
+
continue;
|
|
651
|
+
const cells = line.split("|").map(c => c.trim()).filter(Boolean);
|
|
652
|
+
if (cells.length >= 3) {
|
|
653
|
+
const operator = cells[1];
|
|
654
|
+
// 还原转义的表格内容
|
|
655
|
+
const event = unescapeTableCell(cells[2] || "");
|
|
656
|
+
entries.push({
|
|
657
|
+
timestamp: cells[0] || "",
|
|
658
|
+
operator: operator === "AI" || operator === "Human" ? operator : "AI",
|
|
659
|
+
event,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return entries;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* 追加带类型的日志条目
|
|
667
|
+
*/
|
|
668
|
+
async appendTypedLogEntry(projectRoot, workspaceId, entry, nodeId) {
|
|
669
|
+
const logPath = nodeId
|
|
670
|
+
? this.fs.getNodeLogPath(projectRoot, workspaceId, nodeId)
|
|
671
|
+
: this.fs.getWorkspaceLogPath(projectRoot, workspaceId);
|
|
672
|
+
let content;
|
|
673
|
+
if (await this.fs.exists(logPath)) {
|
|
674
|
+
content = await this.fs.readFile(logPath);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
content = `## 日志
|
|
678
|
+
|
|
679
|
+
| 时间 | 操作者 | 事件 |
|
|
680
|
+
|------|--------|------|
|
|
681
|
+
`;
|
|
682
|
+
}
|
|
683
|
+
// 转义表格单元格内容(换行符和管道符)
|
|
684
|
+
const escapedEvent = escapeTableCell(entry.event);
|
|
685
|
+
const newLine = `| ${entry.timestamp} | ${entry.operator} | ${escapedEvent} |`;
|
|
686
|
+
const updatedContent = content.trimEnd() + "\n" + newLine + "\n";
|
|
687
|
+
await this.fs.writeFile(logPath, updatedContent);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 解析文档引用
|
|
691
|
+
* 格式:- [文件名](路径) - 说明
|
|
692
|
+
* 向后兼容:忽略旧数据中的 (active)/(expired) 状态
|
|
693
|
+
*/
|
|
694
|
+
parseDocs(content) {
|
|
695
|
+
const docs = [];
|
|
696
|
+
// 匹配格式:- [name](path) - description
|
|
697
|
+
// 向后兼容:可选的 (active)/(expired) 状态会被忽略
|
|
698
|
+
const regex = /^- \[([^\]]+)\]\(([^)]+)\)(?: - (.+?))?(?:\s*\((?:active|expired)\))?$/gm;
|
|
699
|
+
let match;
|
|
700
|
+
while ((match = regex.exec(content)) !== null) {
|
|
701
|
+
docs.push({
|
|
702
|
+
path: match[2],
|
|
703
|
+
description: (match[3] || match[1]).trim(),
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return docs;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* 序列化文档引用
|
|
710
|
+
*/
|
|
711
|
+
serializeDoc(doc) {
|
|
712
|
+
const name = doc.path.split("/").pop() ?? doc.path;
|
|
713
|
+
return `- [${name}](${doc.path}) - ${doc.description}`;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* 更新 Info.md 的结论部分
|
|
717
|
+
* @param wsDirName 工作区目录名(非 workspaceId)
|
|
718
|
+
* @param nodeDirName 节点目录名(非 nodeId)
|
|
719
|
+
*/
|
|
720
|
+
async updateConclusion(projectRoot, wsDirName, nodeDirName, conclusion) {
|
|
721
|
+
const info = await this.readNodeInfo(projectRoot, wsDirName, nodeDirName);
|
|
722
|
+
// 将字面量 \\n 转换为真正的换行符(MCP 工具调用时可能传入转义字符串)
|
|
723
|
+
info.conclusion = conclusion.replace(/\\n/g, "\n");
|
|
724
|
+
await this.writeNodeInfo(projectRoot, wsDirName, nodeDirName, info);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* 更新 Info.md 的状态
|
|
728
|
+
* @param wsDirName 工作区目录名(非 workspaceId)
|
|
729
|
+
* @param nodeDirName 节点目录名(非 nodeId)
|
|
730
|
+
*/
|
|
731
|
+
async updateNodeStatus(projectRoot, wsDirName, nodeDirName, status) {
|
|
732
|
+
const info = await this.readNodeInfo(projectRoot, wsDirName, nodeDirName);
|
|
733
|
+
info.status = status;
|
|
734
|
+
await this.writeNodeInfo(projectRoot, wsDirName, nodeDirName, info);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* 读取节点 Info.md(包含文档引用)
|
|
738
|
+
* @param wsDirName 工作区目录名(非 workspaceId)
|
|
739
|
+
* @param nodeDirName 节点目录名(非 nodeId)
|
|
740
|
+
* @param isArchived 是否为归档工作区
|
|
741
|
+
*/
|
|
742
|
+
async readNodeInfoFull(projectRoot, wsDirName, nodeDirName, isArchived = false) {
|
|
743
|
+
const infoPath = isArchived
|
|
744
|
+
? this.fs.getNodeInfoPathWithArchive(projectRoot, wsDirName, nodeDirName, true)
|
|
745
|
+
: this.fs.getNodeInfoPath(projectRoot, wsDirName, nodeDirName);
|
|
746
|
+
const content = await this.fs.readFile(infoPath);
|
|
747
|
+
const parsed = this.parse(content);
|
|
748
|
+
// 解析需求
|
|
749
|
+
let requirement = "";
|
|
750
|
+
const reqMatch = parsed.content.match(/## 需求\n\n([\s\S]*?)(?=\n## |$)/);
|
|
751
|
+
if (reqMatch) {
|
|
752
|
+
requirement = reqMatch[1].trim();
|
|
753
|
+
}
|
|
754
|
+
// 解析验收标准
|
|
755
|
+
const acceptanceCriteria = [];
|
|
756
|
+
const acMatch = parsed.content.match(/## 验收标准\n\n\| 条件 \(WHEN\) \| 期望结果 \(THEN\) \|\n\|[-\s|]+\|\n([\s\S]*?)(?=\n## |$)/);
|
|
757
|
+
if (acMatch) {
|
|
758
|
+
const acContent = acMatch[1].trim();
|
|
759
|
+
if (acContent) {
|
|
760
|
+
const acLines = acContent.split("\n").filter(line => line.startsWith("|"));
|
|
761
|
+
for (const line of acLines) {
|
|
762
|
+
const cells = line.split("|").map(c => c.trim()).filter(Boolean);
|
|
763
|
+
if (cells.length >= 2) {
|
|
764
|
+
acceptanceCriteria.push({
|
|
765
|
+
when: unescapeTableCell(cells[0]),
|
|
766
|
+
then: unescapeTableCell(cells[1])
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// 解析文档引用(向后兼容:忽略旧数据中的状态)
|
|
773
|
+
const docs = [];
|
|
774
|
+
const docsMatch = parsed.content.match(/## 文档引用\n\n(?:>.*\n\n)?([\s\S]*?)(?=\n## |$)/);
|
|
775
|
+
if (docsMatch) {
|
|
776
|
+
const docsContent = docsMatch[1].trim();
|
|
777
|
+
if (docsContent) {
|
|
778
|
+
docs.push(...this.parseDocs(docsContent));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// 解析备注
|
|
782
|
+
let notes = "";
|
|
783
|
+
const notesMatch = parsed.content.match(/## 备注\n\n([\s\S]*?)(?=\n## |$)/);
|
|
784
|
+
if (notesMatch) {
|
|
785
|
+
notes = notesMatch[1].trim();
|
|
786
|
+
}
|
|
787
|
+
// 解析结论
|
|
788
|
+
let conclusion = "";
|
|
789
|
+
const conclusionMatch = parsed.content.match(/## 结论\n\n(?:>.*\n\n)?([\s\S]*?)$/);
|
|
790
|
+
if (conclusionMatch) {
|
|
791
|
+
conclusion = conclusionMatch[1].trim();
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
id: parsed.frontmatter.id || nodeDirName,
|
|
795
|
+
type: parsed.frontmatter.type || "execution",
|
|
796
|
+
title: parsed.frontmatter.title || "",
|
|
797
|
+
status: parsed.frontmatter.status || "pending",
|
|
798
|
+
createdAt: parsed.frontmatter.createdAt || "",
|
|
799
|
+
updatedAt: parsed.frontmatter.updatedAt || "",
|
|
800
|
+
requirement,
|
|
801
|
+
docs,
|
|
802
|
+
notes,
|
|
803
|
+
conclusion,
|
|
804
|
+
acceptanceCriteria: acceptanceCriteria.length > 0 ? acceptanceCriteria : undefined
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* 读取 Workspace.md(包含文档引用)
|
|
809
|
+
* @param isArchived 是否为归档工作区
|
|
810
|
+
*/
|
|
811
|
+
async readWorkspaceMdFull(projectRoot, workspaceId, isArchived = false) {
|
|
812
|
+
const mdPath = isArchived
|
|
813
|
+
? this.fs.getWorkspaceMdPathWithArchive(projectRoot, workspaceId, true)
|
|
814
|
+
: this.fs.getWorkspaceMdPath(projectRoot, workspaceId);
|
|
815
|
+
const content = await this.fs.readFile(mdPath);
|
|
816
|
+
const parsed = this.parse(content);
|
|
817
|
+
// 解析规则
|
|
818
|
+
const rules = [];
|
|
819
|
+
const rulesMatch = parsed.content.match(/## 规则\n\n(?:>.*\n\n)?([\s\S]*?)(?=\n## |$)/);
|
|
820
|
+
if (rulesMatch) {
|
|
821
|
+
const rulesContent = rulesMatch[1].trim();
|
|
822
|
+
if (rulesContent) {
|
|
823
|
+
const ruleLines = rulesContent.split("\n").filter(line => line.startsWith("- "));
|
|
824
|
+
for (const line of ruleLines) {
|
|
825
|
+
rules.push(line.substring(2).trim());
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// 解析文档(向后兼容:忽略旧数据中的状态)
|
|
830
|
+
const docs = [];
|
|
831
|
+
const docsMatch = parsed.content.match(/## 文档\n\n(?:>.*\n\n)?([\s\S]*?)(?=\n## |$)/);
|
|
832
|
+
if (docsMatch) {
|
|
833
|
+
const docsContent = docsMatch[1].trim();
|
|
834
|
+
if (docsContent) {
|
|
835
|
+
docs.push(...this.parseDocs(docsContent));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// goal 已移至根节点 requirement,不再从 Workspace.md 读取
|
|
839
|
+
return {
|
|
840
|
+
name: parsed.frontmatter.name || "",
|
|
841
|
+
createdAt: parsed.frontmatter.createdAt || "",
|
|
842
|
+
updatedAt: parsed.frontmatter.updatedAt || "",
|
|
843
|
+
rules,
|
|
844
|
+
docs,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* 写入节点 Info.md(完整版本,用于服务层)
|
|
849
|
+
* @param wsDirName 工作区目录名(非 workspaceId)
|
|
850
|
+
* @param nodeDirName 节点目录名(非 nodeId)
|
|
851
|
+
*/
|
|
852
|
+
async writeNodeInfoFull(projectRoot, wsDirName, nodeDirName, data) {
|
|
853
|
+
// 验证内容格式
|
|
854
|
+
validateSingleLineContent(data.title, "节点标题");
|
|
855
|
+
if (data.requirement) {
|
|
856
|
+
validateMultilineContent(data.requirement, "需求描述");
|
|
857
|
+
}
|
|
858
|
+
if (data.notes) {
|
|
859
|
+
validateMultilineContent(data.notes, "备注");
|
|
860
|
+
}
|
|
861
|
+
if (data.conclusion) {
|
|
862
|
+
validateMultilineContent(data.conclusion, "结论");
|
|
863
|
+
}
|
|
864
|
+
const infoPath = this.fs.getNodeInfoPath(projectRoot, wsDirName, nodeDirName);
|
|
865
|
+
// 序列化文档引用
|
|
866
|
+
let docsContent = "";
|
|
867
|
+
if (data.docs.length > 0) {
|
|
868
|
+
docsContent = data.docs.map(doc => this.serializeDoc(doc)).join("\n");
|
|
869
|
+
}
|
|
870
|
+
// 渲染验收标准表格
|
|
871
|
+
let acceptanceCriteriaContent = "";
|
|
872
|
+
if (data.acceptanceCriteria && data.acceptanceCriteria.length > 0) {
|
|
873
|
+
const rows = data.acceptanceCriteria.map(ac => `| ${escapeTableCell(ac.when)} | ${escapeTableCell(ac.then)} |`).join("\n");
|
|
874
|
+
acceptanceCriteriaContent = `\n## 验收标准
|
|
875
|
+
|
|
876
|
+
| 条件 (WHEN) | 期望结果 (THEN) |
|
|
877
|
+
|-------------|-----------------|
|
|
878
|
+
${rows}
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
881
|
+
const typeLabel = data.type === "planning" ? "规划" : "执行";
|
|
882
|
+
const content = `---
|
|
883
|
+
id: ${data.id}
|
|
884
|
+
type: ${data.type}
|
|
885
|
+
title: ${data.title}
|
|
886
|
+
status: ${data.status}
|
|
887
|
+
createdAt: ${data.createdAt}
|
|
888
|
+
updatedAt: ${data.updatedAt}
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## 节点类型
|
|
892
|
+
|
|
893
|
+
${typeLabel}节点
|
|
894
|
+
|
|
895
|
+
## 需求
|
|
896
|
+
|
|
897
|
+
${data.requirement ?? ""}
|
|
898
|
+
${acceptanceCriteriaContent}
|
|
899
|
+
## 文档引用
|
|
900
|
+
|
|
901
|
+
> 格式:- [文件名](路径) - 说明
|
|
902
|
+
|
|
903
|
+
${docsContent}
|
|
904
|
+
|
|
905
|
+
## 备注
|
|
906
|
+
|
|
907
|
+
${data.notes}
|
|
908
|
+
|
|
909
|
+
## 结论
|
|
910
|
+
|
|
911
|
+
> 节点完成时填写
|
|
912
|
+
|
|
913
|
+
${data.conclusion}
|
|
914
|
+
`;
|
|
915
|
+
await this.fs.writeFile(infoPath, content);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
//# sourceMappingURL=MarkdownStorage.js.map
|