@polymorphism-tech/morph-spec 4.12.0 → 4.14.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.
Files changed (316) hide show
  1. package/CLAUDE.md +7 -7
  2. package/README.md +28 -34
  3. package/bin/morph-spec.js +41 -24
  4. package/bin/task-manager.js +962 -1341
  5. package/bin/validate.js +1 -1
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +14 -16
  8. package/docs/QUICKSTART.md +15 -15
  9. package/framework/CLAUDE.md +5 -2
  10. package/framework/agents/README.md +3 -4
  11. package/framework/agents/ai-agents/ai-system-architect.md +6 -10
  12. package/framework/agents/architecture/seo-growth-hacker.md +166 -326
  13. package/framework/agents/architecture/standards-architect.md +21 -31
  14. package/framework/agents/backend/dotnet-senior.md +160 -86
  15. package/framework/agents/backend/ef-modeler.md +2 -6
  16. package/framework/agents/backend/hangfire-orchestrator.md +8 -9
  17. package/framework/agents/backend/ms-agent-expert.md +1 -1
  18. package/framework/agents/frontend/nextjs-expert.md +198 -133
  19. package/framework/agents/frontend/ui-ux-designer.md +226 -267
  20. package/framework/agents/infrastructure/infra-architect.md +191 -27
  21. package/framework/agents/integrations/clerk-auth.md +19 -26
  22. package/framework/agents/integrations/hangfire-integration.md +1 -4
  23. package/framework/agents.json +1084 -2278
  24. package/framework/commands/morph-apply.md +4 -6
  25. package/framework/commands/morph-preflight.md +192 -269
  26. package/framework/commands/morph-proposal.md +210 -238
  27. package/framework/commands/morph-status.md +72 -92
  28. package/framework/commands/morph-troubleshoot.md +6 -13
  29. package/framework/hooks/README.md +1 -6
  30. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
  31. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
  32. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
  33. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
  34. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
  35. package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
  36. package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
  37. package/framework/hooks/claude-code/statusline.py +4 -5
  38. package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
  39. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
  40. package/framework/hooks/shared/compact-restore.js +28 -5
  41. package/framework/hooks/shared/phase-utils.js +4 -8
  42. package/framework/hooks/shared/state-reader.js +34 -3
  43. package/framework/phases.json +162 -200
  44. package/framework/rules/ai-agents-standards.md +15 -0
  45. package/framework/rules/asaas-standards.md +13 -0
  46. package/framework/rules/clerk-standards.md +13 -0
  47. package/framework/rules/csharp-standards.md +8 -4
  48. package/framework/rules/data-standards.md +18 -0
  49. package/framework/rules/dotnet-templates.md +87 -0
  50. package/framework/rules/frontend-standards.md +13 -13
  51. package/framework/rules/hangfire-standards.md +13 -0
  52. package/framework/rules/infrastructure-standards.md +27 -16
  53. package/framework/rules/morph-workflow.md +73 -13
  54. package/framework/rules/nextjs-standards.md +1 -0
  55. package/framework/rules/nextjs-templates.md +40 -0
  56. package/framework/rules/resend-standards.md +12 -0
  57. package/framework/rules/supabase-standards.md +29 -0
  58. package/framework/skills/README.md +6 -8
  59. package/framework/skills/level-0-meta/mcp-registry.json +21 -99
  60. package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
  61. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
  62. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  63. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
  64. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
  65. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
  66. package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
  67. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
  68. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
  69. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
  70. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
  71. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
  72. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
  73. package/framework/skills/level-1-workflows/README.md +1 -1
  74. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
  75. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +57 -123
  76. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
  77. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
  78. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +1 -1
  80. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
  81. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
  82. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
  83. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
  84. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
  85. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -172
  86. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
  87. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
  88. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  89. package/framework/squad-templates/backend-only.json +6 -6
  90. package/framework/squad-templates/frontend-only.json +17 -8
  91. package/framework/squad-templates/full-stack.json +29 -24
  92. package/framework/standards/STANDARDS.json +1021 -2369
  93. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
  94. package/framework/standards/backend/dotnet/result-pattern.md +213 -0
  95. package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
  96. package/framework/standards/backend/dotnet/scrutor.md +158 -0
  97. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
  98. package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
  99. package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
  100. package/framework/standards/integration/mcp/mcp-tools.md +91 -77
  101. package/framework/templates/IDE-SNIPPETS.md +2 -6
  102. package/framework/templates/README.md +709 -814
  103. package/framework/templates/REGISTRY.json +223 -762
  104. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
  105. package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
  106. package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
  107. package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
  108. package/framework/templates/code/dotnet/backend/repository.cs +148 -141
  109. package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
  110. package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
  111. package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
  112. package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
  113. package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
  114. package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
  115. package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
  116. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
  117. package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
  118. package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
  119. package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
  120. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
  121. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
  122. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
  123. package/framework/templates/code/dotnet/jobs/job.cs +21 -17
  124. package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
  125. package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
  126. package/framework/templates/code/dotnet/test.cs +192 -106
  127. package/framework/templates/context/ARCHITECTURE.md +89 -0
  128. package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
  129. package/framework/templates/context/CONTEXT.md +180 -181
  130. package/framework/templates/context/design-system.md +1 -5
  131. package/framework/templates/docs/onboarding.md +2 -3
  132. package/framework/templates/docs/proposal.md +171 -182
  133. package/framework/templates/docs/spec.md +1 -3
  134. package/framework/templates/docs/ui-components.md +0 -72
  135. package/framework/templates/examples/design-system-examples.md +4 -4
  136. package/framework/templates/feature/decisions.md +0 -66
  137. package/framework/templates/feature/recap.md +1 -2
  138. package/framework/templates/infrastructure/github/README.md +56 -593
  139. package/framework/templates/integrations/clerk-config.cs +0 -41
  140. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
  141. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
  142. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
  143. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
  144. package/framework/templates/project/validate.js +2 -4
  145. package/framework/templates/ui/design-system.css +1 -1
  146. package/framework/workflows/README.md +8 -8
  147. package/framework/workflows/configs/fast-track.json +51 -0
  148. package/framework/workflows/configs/full.json +68 -0
  149. package/framework/workflows/configs/standard.json +7 -3
  150. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
  151. package/framework/workflows/docs/enforcement-pipeline.md +18 -19
  152. package/framework/workflows/docs/full-morph.md +69 -76
  153. package/framework/workflows/docs/standard.md +49 -44
  154. package/framework/workflows/docs/ui-refresh.md +1 -1
  155. package/package.json +3 -3
  156. package/src/commands/agents/dispatch-agents.js +55 -116
  157. package/src/commands/context/context-refresh.js +79 -0
  158. package/src/commands/phase/phase-reset.js +1 -1
  159. package/src/commands/project/doctor.js +10 -32
  160. package/src/commands/project/init.js +12 -0
  161. package/src/commands/project/status.js +6 -12
  162. package/src/commands/scaffold/scaffold.js +131 -0
  163. package/src/commands/scope/escalate.js +1 -1
  164. package/src/commands/state/advance-phase.js +15 -29
  165. package/src/commands/state/state.js +20 -5
  166. package/src/commands/tasks/task.js +0 -30
  167. package/src/commands/validation/validate-feature.js +3 -10
  168. package/src/core/paths/output-schema.js +12 -13
  169. package/src/core/state/phase-state-machine.js +211 -217
  170. package/src/core/state/state-manager.js +9 -9
  171. package/src/lib/agents/agents-manager.js +2 -2
  172. package/src/lib/agents/output-collector.js +1 -2
  173. package/src/lib/detectors/design-system-detector.js +4 -4
  174. package/src/lib/detectors/index.js +1 -11
  175. package/src/lib/generators/architecture-scanner.js +640 -0
  176. package/src/lib/generators/context-generator.js +3 -3
  177. package/src/lib/generators/design-system-generator.js +508 -587
  178. package/src/lib/generators/index.js +12 -10
  179. package/src/lib/generators/metadata-extractor.js +386 -387
  180. package/src/lib/generators/recap-generator.js +4 -4
  181. package/src/lib/generators/scaffold-generator.js +317 -0
  182. package/src/lib/generators/settings-generator.js +0 -1
  183. package/src/lib/phase-chain/phase-validator.js +64 -42
  184. package/src/lib/stack/stack-profile.js +2 -2
  185. package/src/lib/standards/digest-builder.js +72 -285
  186. package/src/lib/tasks/task-parser.js +11 -5
  187. package/src/lib/validators/architecture/architecture-validator.js +259 -387
  188. package/src/lib/validators/content/content-validator.js +287 -287
  189. package/src/lib/validators/css/css-validator.js +340 -352
  190. package/src/lib/validators/packages/package-validator.js +347 -360
  191. package/src/lib/validators/shared/issue-counter.js +14 -18
  192. package/src/lib/validators/validation-runner.js +3 -19
  193. package/src/scripts/setup-infra.js +1 -1
  194. package/src/utils/agents-installer.js +61 -10
  195. package/src/utils/file-copier.js +1 -0
  196. package/src/utils/hooks-installer.js +11 -19
  197. package/src/utils/skills-installer.js +178 -178
  198. package/framework/agents/architecture/prompt-engineer.md +0 -195
  199. package/framework/agents/backend/api-designer.md +0 -106
  200. package/framework/agents/frontend/blazor-builder.md +0 -229
  201. package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
  202. package/framework/agents/infrastructure/azure-architect.md +0 -150
  203. package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
  204. package/framework/agents/infrastructure/bicep-architect.md +0 -132
  205. package/framework/agents/infrastructure/container-specialist.md +0 -137
  206. package/framework/agents/infrastructure/devops-engineer.md +0 -126
  207. package/framework/agents/integrations/azure-identity.md +0 -148
  208. package/framework/agents/quality/code-analyzer.md +0 -239
  209. package/framework/agents/quality/testing-specialist.md +0 -130
  210. package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
  211. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
  212. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
  213. package/framework/index/troubleshooting-index.json +0 -184
  214. package/framework/rules/testing-standards.md +0 -14
  215. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
  216. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
  217. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
  218. package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
  219. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
  220. package/framework/standards/ai-agents/blazor-ui.md +0 -364
  221. package/framework/standards/architecture/ddd/aggregates.md +0 -120
  222. package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
  223. package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
  224. package/framework/standards/architecture/ddd/entities.md +0 -99
  225. package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
  226. package/framework/standards/architecture/ddd/value-objects.md +0 -124
  227. package/framework/standards/backend/api/minimal-api.md +0 -494
  228. package/framework/standards/backend/api/rest.md +0 -492
  229. package/framework/standards/backend/authentication/passkeys.md +0 -428
  230. package/framework/standards/backend/database/ef-core.md +0 -199
  231. package/framework/standards/backend/database/migrations.md +0 -393
  232. package/framework/standards/backend/database/repository-patterns.md +0 -528
  233. package/framework/standards/backend/dotnet/async.md +0 -366
  234. package/framework/standards/backend/dotnet/core.md +0 -117
  235. package/framework/standards/backend/dotnet/di.md +0 -439
  236. package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  237. package/framework/standards/context/analytics.md +0 -96
  238. package/framework/standards/context/bundles.md +0 -110
  239. package/framework/standards/context/priming.md +0 -78
  240. package/framework/standards/core/architecture.md +0 -185
  241. package/framework/standards/core/coding.md +0 -214
  242. package/framework/standards/core/git-branching-strategy.md +0 -403
  243. package/framework/standards/core/git.md +0 -185
  244. package/framework/standards/core/testing.md +0 -295
  245. package/framework/standards/data/nosql/blob-storage.md +0 -102
  246. package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  247. package/framework/standards/frontend/blazor/design-checklist.md +0 -222
  248. package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  249. package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  250. package/framework/standards/frontend/blazor/html-conversion.md +0 -184
  251. package/framework/standards/frontend/blazor/lifecycle.md +0 -195
  252. package/framework/standards/frontend/blazor/pitfalls.md +0 -198
  253. package/framework/standards/frontend/blazor/state.md +0 -191
  254. package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
  255. package/framework/standards/frontend/design-system/design-trends.md +0 -171
  256. package/framework/standards/infrastructure/azure/azure.md +0 -624
  257. package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  258. package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  259. package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  260. package/framework/standards/infrastructure/azure/services/functions.md +0 -486
  261. package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  262. package/framework/standards/infrastructure/azure/services/storage.md +0 -407
  263. package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
  264. package/framework/standards/integration/api/graphql.md +0 -91
  265. package/framework/standards/integration/api/grpc.md +0 -114
  266. package/framework/standards/integration/api/rest-design.md +0 -95
  267. package/framework/standards/integration/event-driven/cqrs.md +0 -101
  268. package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  269. package/framework/standards/integration/event-driven/service-bus.md +0 -95
  270. package/framework/standards/observability/logging.md +0 -131
  271. package/framework/standards/observability/metrics.md +0 -121
  272. package/framework/standards/observability/monitoring.md +0 -114
  273. package/framework/standards/observability/tracing.md +0 -132
  274. package/framework/standards/workflows/parallel-execution.md +0 -112
  275. package/framework/standards/workflows/thread-management.md +0 -113
  276. package/framework/templates/code/dotnet/backend/service.cs +0 -139
  277. package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  278. package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  279. package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  280. package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  281. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
  282. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
  283. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
  284. package/framework/templates/code/dotnet/frontend/component.razor +0 -239
  285. package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  286. package/framework/templates/infrastructure/azure/README.md +0 -286
  287. package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  288. package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  289. package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  290. package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  291. package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  292. package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  293. package/framework/templates/infrastructure/azure/deploy.sh +0 -208
  294. package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  295. package/framework/templates/infrastructure/azure/main.bicep +0 -189
  296. package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  297. package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  298. package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  299. package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  300. package/framework/templates/infrastructure/azure/storage.bicep +0 -106
  301. package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  302. package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  303. package/framework/templates/integrations/azure-identity-config.cs +0 -288
  304. package/framework/templates/project-structure/dotnet-ddd.md +0 -70
  305. package/framework/templates/saas/subscription.cs +0 -347
  306. package/framework/templates/saas/tenant.cs +0 -338
  307. package/framework/templates/ui/FluentDesignTheme.cs +0 -149
  308. package/framework/templates/ui/MudTheme.cs +0 -281
  309. package/framework/workflows/configs/nodejs-cli.json +0 -40
  310. package/src/commands/standards/context-budget.js +0 -89
  311. package/src/lib/standards/index.js +0 -7
  312. package/src/lib/standards/standards-context-injector.js +0 -302
  313. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
  314. package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
  315. package/src/lib/validators/blazor/blazor-validator.js +0 -369
  316. package/src/lib/validators/blazor/index.js +0 -9
@@ -1,387 +1,386 @@
1
- import { readFileSync, existsSync } from 'fs';
2
-
3
- /**
4
- * Metadata Extractor - Parse markdown documents into structured JSON
5
- *
6
- * Generates quick summaries for LLM to parse, reducing token usage
7
- * by 60-80% compared to reading full markdown files.
8
- */
9
-
10
- /**
11
- * Extract a field value from markdown content (table or colon format)
12
- * @param {string} content - Markdown content
13
- * @param {string} fieldName - Field name to extract
14
- * @returns {string|null} Field value or null
15
- */
16
- function extractField(content, fieldName) {
17
- // Try table row format first: | FieldName | Value |
18
- const tableRegex = new RegExp(`\\|\\s*${fieldName}\\s*\\|\\s*(.+?)\\s*\\|`, 'i');
19
- const tableMatch = content.match(tableRegex);
20
- if (tableMatch) {
21
- return tableMatch[1].trim();
22
- }
23
-
24
- // Try colon format: FieldName: Value
25
- const colonRegex = new RegExp(`^${fieldName}\\s*:\\s*(.+)$`, 'im');
26
- const colonMatch = content.match(colonRegex);
27
- return colonMatch ? colonMatch[1].trim() : null;
28
- }
29
-
30
- /**
31
- * Extract first paragraph from a section
32
- * @param {string} content - Markdown content
33
- * @param {string} sectionHeader - Section header (e.g., "## Overview")
34
- * @returns {string|null} First paragraph or null
35
- */
36
- function extractFirstParagraph(content, sectionHeader) {
37
- const regex = new RegExp(`${sectionHeader}\\s+([^\\n]+(?:\\n(?!##)[^\\n]+)*)`, 'i');
38
- const match = content.match(regex);
39
-
40
- if (!match) return null;
41
-
42
- // Get first paragraph (up to first blank line or next section)
43
- const text = match[1].trim();
44
- const firstPara = text.split('\n\n')[0];
45
- return firstPara.substring(0, 300); // Limit to 300 chars
46
- }
47
-
48
- /**
49
- * Extract bullet points from a section
50
- * @param {string} content - Markdown content
51
- * @param {string} sectionHeader - Section header
52
- * @returns {string[]} Array of bullet points
53
- */
54
- function extractBulletPoints(content, sectionHeader) {
55
- const regex = new RegExp(`${sectionHeader}([\\s\\S]*?)(?=\\n## |$)`, 'i');
56
- const match = content.match(regex);
57
-
58
- if (!match) return [];
59
-
60
- const sectionContent = match[1];
61
- const bullets = [];
62
-
63
- // Match lines starting with -, *, or numbered list
64
- const bulletRegex = /^[\s]*[-*][\s]+(.+)$/gm;
65
- let bulletMatch;
66
-
67
- while ((bulletMatch = bulletRegex.exec(sectionContent)) !== null) {
68
- bullets.push(bulletMatch[1].trim());
69
- }
70
-
71
- return bullets.slice(0, 10); // Limit to 10 items
72
- }
73
-
74
- /**
75
- * Extract table or list from a section
76
- * @param {string} content - Markdown content
77
- * @param {string} sectionHeader - Section header
78
- * @returns {string[]} Array of items
79
- */
80
- function extractTableOrList(content, sectionHeader) {
81
- const items = extractBulletPoints(content, sectionHeader);
82
-
83
- if (items.length > 0) return items;
84
-
85
- // Try extracting table rows
86
- const regex = new RegExp(`${sectionHeader}([\\s\\S]*?)(?=\\n## |$)`, 'i');
87
- const match = content.match(regex);
88
-
89
- if (!match) return [];
90
-
91
- const sectionContent = match[1];
92
- const tableRows = [];
93
-
94
- // Match table rows (| col1 | col2 |)
95
- const rowRegex = /^\|(.+)\|$/gm;
96
- let rowMatch;
97
-
98
- while ((rowMatch = rowRegex.exec(sectionContent)) !== null) {
99
- const row = rowMatch[1].trim();
100
- // Skip header separator rows
101
- if (!row.match(/^[-:|\s]+$/)) {
102
- tableRows.push(row);
103
- }
104
- }
105
-
106
- return tableRows.slice(0, 10); // Limit to 10 rows
107
- }
108
-
109
- /**
110
- * Auto-generate tags from content
111
- * @param {string} content - Markdown content
112
- * @returns {string[]} Array of tags
113
- */
114
- function autoGenerateTags(content) {
115
- const tags = new Set();
116
- const lowerContent = content.toLowerCase();
117
-
118
- // Technology tags
119
- const techKeywords = {
120
- 'blazor': 'blazor',
121
- 'react': 'react',
122
- 'nextjs': 'nextjs',
123
- 'next.js': 'nextjs',
124
- 'azure': 'azure',
125
- 'sql': 'database',
126
- 'entity framework': 'ef-core',
127
- 'hangfire': 'background-jobs',
128
- 'signalr': 'realtime',
129
- 'docker': 'container',
130
- 'bicep': 'infrastructure-as-code',
131
- 'api': 'api',
132
- 'rest': 'rest-api',
133
- 'authentication': 'auth',
134
- 'authorization': 'auth'
135
- };
136
-
137
- Object.entries(techKeywords).forEach(([keyword, tag]) => {
138
- if (lowerContent.includes(keyword)) {
139
- tags.add(tag);
140
- }
141
- });
142
-
143
- return Array.from(tags).slice(0, 8); // Limit to 8 tags
144
- }
145
-
146
- /**
147
- * Extract spec.md metadata
148
- * @param {string} specPath - Path to spec.md file
149
- * @returns {Object} Spec metadata
150
- */
151
- export function extractSpecMetadata(specPath) {
152
- if (!existsSync(specPath)) {
153
- return {
154
- error: 'Spec file not found',
155
- path: specPath
156
- };
157
- }
158
-
159
- try {
160
- const content = readFileSync(specPath, 'utf8');
161
-
162
- return {
163
- id: extractField(content, 'ID') || extractField(content, 'Feature'),
164
- status: extractField(content, 'Status'),
165
- complexity: extractField(content, 'Complexity'),
166
- estimatedCost: extractField(content, 'Estimated Cost') || extractField(content, 'Cost'),
167
-
168
- summary: {
169
- problem: extractFirstParagraph(content, '## Overview') ||
170
- extractFirstParagraph(content, '## Problem Statement'),
171
- solution: extractFirstParagraph(content, '## Proposed Solution') ||
172
- extractFirstParagraph(content, '## Solution'),
173
- tags: autoGenerateTags(content)
174
- },
175
-
176
- requirements: extractBulletPoints(content, '## Requirements').slice(0, 5),
177
- dataModel: extractTableOrList(content, '## Data Model').slice(0, 5),
178
- apiContracts: extractTableOrList(content, '## API Contracts').slice(0, 5),
179
-
180
- stats: {
181
- length: content.length,
182
- sections: (content.match(/^## /gm) || []).length,
183
- codeBlocks: (content.match(/```/g) || []).length / 2
184
- },
185
-
186
- generatedAt: new Date().toISOString(),
187
- source: specPath
188
- };
189
- } catch (error) {
190
- return {
191
- error: error.message,
192
- path: specPath
193
- };
194
- }
195
- }
196
-
197
- /**
198
- * Extract proposal.md metadata
199
- * @param {string} proposalPath - Path to proposal.md file
200
- * @returns {Object} Proposal metadata
201
- */
202
- export function extractProposalMetadata(proposalPath) {
203
- if (!existsSync(proposalPath)) {
204
- return {
205
- error: 'Proposal file not found',
206
- path: proposalPath
207
- };
208
- }
209
-
210
- try {
211
- const content = readFileSync(proposalPath, 'utf8');
212
-
213
- return {
214
- id: extractField(content, 'ID') || extractField(content, 'Feature'),
215
- status: extractField(content, 'Status'),
216
-
217
- summary: {
218
- problem: extractFirstParagraph(content, '## Problem Statement'),
219
- solution: extractFirstParagraph(content, '## Proposed Solution'),
220
- tags: autoGenerateTags(content)
221
- },
222
-
223
- successMetrics: extractBulletPoints(content, '## Success Metrics').slice(0, 5),
224
-
225
- generatedAt: new Date().toISOString(),
226
- source: proposalPath
227
- };
228
- } catch (error) {
229
- return {
230
- error: error.message,
231
- path: proposalPath
232
- };
233
- }
234
- }
235
-
236
- /**
237
- * Extract decisions.md metadata (ADRs)
238
- * @param {string} decisionsPath - Path to decisions.md file
239
- * @returns {Object} Decisions metadata
240
- */
241
- export function extractDecisionsMetadata(decisionsPath) {
242
- if (!existsSync(decisionsPath)) {
243
- return {
244
- error: 'Decisions file not found',
245
- path: decisionsPath
246
- };
247
- }
248
-
249
- try {
250
- const content = readFileSync(decisionsPath, 'utf8');
251
- const decisions = [];
252
-
253
- // Match ADR pattern: ### ADR-001: Title
254
- const adrRegex = /### (ADR-\d+): (.+?)\n([\s\S]*?)(?=### ADR-|\n## |$)/g;
255
- let match;
256
-
257
- while ((match = adrRegex.exec(content)) !== null) {
258
- const [, id, title, body] = match;
259
-
260
- decisions.push({
261
- id,
262
- title: title.trim(),
263
- summary: body.trim().substring(0, 200), // First 200 chars
264
- fullPath: `${decisionsPath}#${id.toLowerCase()}`
265
- });
266
- }
267
-
268
- return {
269
- decisions,
270
- total: decisions.length,
271
- generatedAt: new Date().toISOString(),
272
- source: decisionsPath
273
- };
274
- } catch (error) {
275
- return {
276
- error: error.message,
277
- path: decisionsPath
278
- };
279
- }
280
- }
281
-
282
- /**
283
- * Extract tasks.json metadata (summary only)
284
- * @param {string} tasksPath - Path to tasks.json file
285
- * @returns {Object} Tasks metadata
286
- */
287
- export function extractTasksMetadata(tasksPath) {
288
- if (!existsSync(tasksPath)) {
289
- return {
290
- error: 'Tasks file not found',
291
- path: tasksPath
292
- };
293
- }
294
-
295
- try {
296
- const content = readFileSync(tasksPath, 'utf8');
297
- const tasks = JSON.parse(content);
298
-
299
- const regularTasks = tasks.tasks?.filter(t => t.id?.startsWith('T')) || [];
300
- const checkpoints = tasks.tasks?.filter(t => t.id?.startsWith('CHECKPOINT')) || [];
301
-
302
- return {
303
- total: tasks.tasks?.length || 0,
304
- regularTasks: regularTasks.length,
305
- checkpoints: checkpoints.length,
306
-
307
- categories: tasks.categories || {},
308
-
309
- byStatus: {
310
- pending: regularTasks.filter(t => t.status === 'pending').length,
311
- inProgress: regularTasks.filter(t => t.status === 'in_progress').length,
312
- done: regularTasks.filter(t => t.status === 'done').length,
313
- blocked: regularTasks.filter(t => t.status === 'blocked').length
314
- },
315
-
316
- estimatedTime: regularTasks.reduce((sum, t) => sum + (t.estimatedMinutes || 0), 0),
317
-
318
- nextTask: regularTasks.find(t => t.status === 'pending' &&
319
- (!t.dependencies || t.dependencies.length === 0)),
320
-
321
- generatedAt: new Date().toISOString(),
322
- source: tasksPath
323
- };
324
- } catch (error) {
325
- return {
326
- error: error.message,
327
- path: tasksPath
328
- };
329
- }
330
- }
331
-
332
- /**
333
- * Extract comprehensive feature metadata
334
- * @param {Object} featureState - Feature state from state.json
335
- * @returns {Object} Complete feature metadata
336
- */
337
- export function extractFeatureMetadata(featureState) {
338
- const metadata = {
339
- version: "1.0.0",
340
- feature: featureState.name || 'unknown',
341
- status: featureState.status,
342
- phase: featureState.phase,
343
- workflow: featureState.workflow,
344
-
345
- agents: featureState.activeAgents || [],
346
- checkpoints: featureState.checkpoints || [],
347
-
348
- outputs: {},
349
- quickLinks: {}
350
- };
351
-
352
- // Extract metadata from each output
353
- if (featureState.outputs?.proposal?.created) {
354
- metadata.outputs.proposal = extractProposalMetadata(featureState.outputs.proposal.path);
355
- metadata.quickLinks.proposal = featureState.outputs.proposal.path;
356
- }
357
-
358
- if (featureState.outputs?.spec?.created) {
359
- metadata.outputs.spec = extractSpecMetadata(featureState.outputs.spec.path);
360
- metadata.quickLinks.spec = featureState.outputs.spec.path;
361
- }
362
-
363
- if (featureState.outputs?.decisions?.created) {
364
- metadata.outputs.decisions = extractDecisionsMetadata(featureState.outputs.decisions.path);
365
- metadata.quickLinks.decisions = featureState.outputs.decisions.path;
366
- }
367
-
368
- if (featureState.outputs?.tasks?.created) {
369
- metadata.outputs.tasks = extractTasksMetadata(featureState.outputs.tasks.path);
370
- metadata.quickLinks.tasks = featureState.outputs.tasks.path;
371
- }
372
-
373
- // Calculate progress
374
- if (featureState.tasks) {
375
- metadata.progress = {
376
- total: featureState.tasks.total || 0,
377
- completed: featureState.tasks.completed || 0,
378
- percentage: featureState.tasks.total > 0
379
- ? Math.round((featureState.tasks.completed / featureState.tasks.total) * 100)
380
- : 0
381
- };
382
- }
383
-
384
- metadata.generatedAt = new Date().toISOString();
385
-
386
- return metadata;
387
- }
1
+ import { readFileSync, existsSync } from 'fs';
2
+
3
+ /**
4
+ * Metadata Extractor - Parse markdown documents into structured JSON
5
+ *
6
+ * Generates quick summaries for LLM to parse, reducing token usage
7
+ * by 60-80% compared to reading full markdown files.
8
+ */
9
+
10
+ /**
11
+ * Extract a field value from markdown content (table or colon format)
12
+ * @param {string} content - Markdown content
13
+ * @param {string} fieldName - Field name to extract
14
+ * @returns {string|null} Field value or null
15
+ */
16
+ function extractField(content, fieldName) {
17
+ // Try table row format first: | FieldName | Value |
18
+ const tableRegex = new RegExp(`\\|\\s*${fieldName}\\s*\\|\\s*(.+?)\\s*\\|`, 'i');
19
+ const tableMatch = content.match(tableRegex);
20
+ if (tableMatch) {
21
+ return tableMatch[1].trim();
22
+ }
23
+
24
+ // Try colon format: FieldName: Value
25
+ const colonRegex = new RegExp(`^${fieldName}\\s*:\\s*(.+)$`, 'im');
26
+ const colonMatch = content.match(colonRegex);
27
+ return colonMatch ? colonMatch[1].trim() : null;
28
+ }
29
+
30
+ /**
31
+ * Extract first paragraph from a section
32
+ * @param {string} content - Markdown content
33
+ * @param {string} sectionHeader - Section header (e.g., "## Overview")
34
+ * @returns {string|null} First paragraph or null
35
+ */
36
+ function extractFirstParagraph(content, sectionHeader) {
37
+ const regex = new RegExp(`${sectionHeader}\\s+([^\\n]+(?:\\n(?!##)[^\\n]+)*)`, 'i');
38
+ const match = content.match(regex);
39
+
40
+ if (!match) return null;
41
+
42
+ // Get first paragraph (up to first blank line or next section)
43
+ const text = match[1].trim();
44
+ const firstPara = text.split('\n\n')[0];
45
+ return firstPara.substring(0, 300); // Limit to 300 chars
46
+ }
47
+
48
+ /**
49
+ * Extract bullet points from a section
50
+ * @param {string} content - Markdown content
51
+ * @param {string} sectionHeader - Section header
52
+ * @returns {string[]} Array of bullet points
53
+ */
54
+ function extractBulletPoints(content, sectionHeader) {
55
+ const regex = new RegExp(`${sectionHeader}([\\s\\S]*?)(?=\\n## |$)`, 'i');
56
+ const match = content.match(regex);
57
+
58
+ if (!match) return [];
59
+
60
+ const sectionContent = match[1];
61
+ const bullets = [];
62
+
63
+ // Match lines starting with -, *, or numbered list
64
+ const bulletRegex = /^[\s]*[-*][\s]+(.+)$/gm;
65
+ let bulletMatch;
66
+
67
+ while ((bulletMatch = bulletRegex.exec(sectionContent)) !== null) {
68
+ bullets.push(bulletMatch[1].trim());
69
+ }
70
+
71
+ return bullets.slice(0, 10); // Limit to 10 items
72
+ }
73
+
74
+ /**
75
+ * Extract table or list from a section
76
+ * @param {string} content - Markdown content
77
+ * @param {string} sectionHeader - Section header
78
+ * @returns {string[]} Array of items
79
+ */
80
+ function extractTableOrList(content, sectionHeader) {
81
+ const items = extractBulletPoints(content, sectionHeader);
82
+
83
+ if (items.length > 0) return items;
84
+
85
+ // Try extracting table rows
86
+ const regex = new RegExp(`${sectionHeader}([\\s\\S]*?)(?=\\n## |$)`, 'i');
87
+ const match = content.match(regex);
88
+
89
+ if (!match) return [];
90
+
91
+ const sectionContent = match[1];
92
+ const tableRows = [];
93
+
94
+ // Match table rows (| col1 | col2 |)
95
+ const rowRegex = /^\|(.+)\|$/gm;
96
+ let rowMatch;
97
+
98
+ while ((rowMatch = rowRegex.exec(sectionContent)) !== null) {
99
+ const row = rowMatch[1].trim();
100
+ // Skip header separator rows
101
+ if (!row.match(/^[-:|\s]+$/)) {
102
+ tableRows.push(row);
103
+ }
104
+ }
105
+
106
+ return tableRows.slice(0, 10); // Limit to 10 rows
107
+ }
108
+
109
+ /**
110
+ * Auto-generate tags from content
111
+ * @param {string} content - Markdown content
112
+ * @returns {string[]} Array of tags
113
+ */
114
+ function autoGenerateTags(content) {
115
+ const tags = new Set();
116
+ const lowerContent = content.toLowerCase();
117
+
118
+ // Technology tags
119
+ const techKeywords = {
120
+ 'react': 'react',
121
+ 'nextjs': 'nextjs',
122
+ 'next.js': 'nextjs',
123
+ 'azure': 'azure',
124
+ 'sql': 'database',
125
+ 'entity framework': 'ef-core',
126
+ 'hangfire': 'background-jobs',
127
+ 'signalr': 'realtime',
128
+ 'docker': 'container',
129
+ 'bicep': 'infrastructure-as-code',
130
+ 'api': 'api',
131
+ 'rest': 'rest-api',
132
+ 'authentication': 'auth',
133
+ 'authorization': 'auth'
134
+ };
135
+
136
+ Object.entries(techKeywords).forEach(([keyword, tag]) => {
137
+ if (lowerContent.includes(keyword)) {
138
+ tags.add(tag);
139
+ }
140
+ });
141
+
142
+ return Array.from(tags).slice(0, 8); // Limit to 8 tags
143
+ }
144
+
145
+ /**
146
+ * Extract spec.md metadata
147
+ * @param {string} specPath - Path to spec.md file
148
+ * @returns {Object} Spec metadata
149
+ */
150
+ export function extractSpecMetadata(specPath) {
151
+ if (!existsSync(specPath)) {
152
+ return {
153
+ error: 'Spec file not found',
154
+ path: specPath
155
+ };
156
+ }
157
+
158
+ try {
159
+ const content = readFileSync(specPath, 'utf8');
160
+
161
+ return {
162
+ id: extractField(content, 'ID') || extractField(content, 'Feature'),
163
+ status: extractField(content, 'Status'),
164
+ complexity: extractField(content, 'Complexity'),
165
+ estimatedCost: extractField(content, 'Estimated Cost') || extractField(content, 'Cost'),
166
+
167
+ summary: {
168
+ problem: extractFirstParagraph(content, '## Overview') ||
169
+ extractFirstParagraph(content, '## Problem Statement'),
170
+ solution: extractFirstParagraph(content, '## Proposed Solution') ||
171
+ extractFirstParagraph(content, '## Solution'),
172
+ tags: autoGenerateTags(content)
173
+ },
174
+
175
+ requirements: extractBulletPoints(content, '## Requirements').slice(0, 5),
176
+ dataModel: extractTableOrList(content, '## Data Model').slice(0, 5),
177
+ apiContracts: extractTableOrList(content, '## API Contracts').slice(0, 5),
178
+
179
+ stats: {
180
+ length: content.length,
181
+ sections: (content.match(/^## /gm) || []).length,
182
+ codeBlocks: (content.match(/```/g) || []).length / 2
183
+ },
184
+
185
+ generatedAt: new Date().toISOString(),
186
+ source: specPath
187
+ };
188
+ } catch (error) {
189
+ return {
190
+ error: error.message,
191
+ path: specPath
192
+ };
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Extract proposal.md metadata
198
+ * @param {string} proposalPath - Path to proposal.md file
199
+ * @returns {Object} Proposal metadata
200
+ */
201
+ export function extractProposalMetadata(proposalPath) {
202
+ if (!existsSync(proposalPath)) {
203
+ return {
204
+ error: 'Proposal file not found',
205
+ path: proposalPath
206
+ };
207
+ }
208
+
209
+ try {
210
+ const content = readFileSync(proposalPath, 'utf8');
211
+
212
+ return {
213
+ id: extractField(content, 'ID') || extractField(content, 'Feature'),
214
+ status: extractField(content, 'Status'),
215
+
216
+ summary: {
217
+ problem: extractFirstParagraph(content, '## Problem Statement'),
218
+ solution: extractFirstParagraph(content, '## Proposed Solution'),
219
+ tags: autoGenerateTags(content)
220
+ },
221
+
222
+ successMetrics: extractBulletPoints(content, '## Success Metrics').slice(0, 5),
223
+
224
+ generatedAt: new Date().toISOString(),
225
+ source: proposalPath
226
+ };
227
+ } catch (error) {
228
+ return {
229
+ error: error.message,
230
+ path: proposalPath
231
+ };
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Extract decisions.md metadata (ADRs)
237
+ * @param {string} decisionsPath - Path to decisions.md file
238
+ * @returns {Object} Decisions metadata
239
+ */
240
+ export function extractDecisionsMetadata(decisionsPath) {
241
+ if (!existsSync(decisionsPath)) {
242
+ return {
243
+ error: 'Decisions file not found',
244
+ path: decisionsPath
245
+ };
246
+ }
247
+
248
+ try {
249
+ const content = readFileSync(decisionsPath, 'utf8');
250
+ const decisions = [];
251
+
252
+ // Match ADR pattern: ### ADR-001: Title
253
+ const adrRegex = /### (ADR-\d+): (.+?)\n([\s\S]*?)(?=### ADR-|\n## |$)/g;
254
+ let match;
255
+
256
+ while ((match = adrRegex.exec(content)) !== null) {
257
+ const [, id, title, body] = match;
258
+
259
+ decisions.push({
260
+ id,
261
+ title: title.trim(),
262
+ summary: body.trim().substring(0, 200), // First 200 chars
263
+ fullPath: `${decisionsPath}#${id.toLowerCase()}`
264
+ });
265
+ }
266
+
267
+ return {
268
+ decisions,
269
+ total: decisions.length,
270
+ generatedAt: new Date().toISOString(),
271
+ source: decisionsPath
272
+ };
273
+ } catch (error) {
274
+ return {
275
+ error: error.message,
276
+ path: decisionsPath
277
+ };
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Extract tasks.json metadata (summary only)
283
+ * @param {string} tasksPath - Path to tasks.json file
284
+ * @returns {Object} Tasks metadata
285
+ */
286
+ export function extractTasksMetadata(tasksPath) {
287
+ if (!existsSync(tasksPath)) {
288
+ return {
289
+ error: 'Tasks file not found',
290
+ path: tasksPath
291
+ };
292
+ }
293
+
294
+ try {
295
+ const content = readFileSync(tasksPath, 'utf8');
296
+ const tasks = JSON.parse(content);
297
+
298
+ const regularTasks = tasks.tasks?.filter(t => t.id?.startsWith('T')) || [];
299
+ const checkpoints = tasks.tasks?.filter(t => t.id?.startsWith('CHECKPOINT')) || [];
300
+
301
+ return {
302
+ total: tasks.tasks?.length || 0,
303
+ regularTasks: regularTasks.length,
304
+ checkpoints: checkpoints.length,
305
+
306
+ categories: tasks.categories || {},
307
+
308
+ byStatus: {
309
+ pending: regularTasks.filter(t => t.status === 'pending').length,
310
+ inProgress: regularTasks.filter(t => t.status === 'in_progress').length,
311
+ done: regularTasks.filter(t => t.status === 'done').length,
312
+ blocked: regularTasks.filter(t => t.status === 'blocked').length
313
+ },
314
+
315
+ estimatedTime: regularTasks.reduce((sum, t) => sum + (t.estimatedMinutes || 0), 0),
316
+
317
+ nextTask: regularTasks.find(t => t.status === 'pending' &&
318
+ (!t.dependencies || t.dependencies.length === 0)),
319
+
320
+ generatedAt: new Date().toISOString(),
321
+ source: tasksPath
322
+ };
323
+ } catch (error) {
324
+ return {
325
+ error: error.message,
326
+ path: tasksPath
327
+ };
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Extract comprehensive feature metadata
333
+ * @param {Object} featureState - Feature state from state.json
334
+ * @returns {Object} Complete feature metadata
335
+ */
336
+ export function extractFeatureMetadata(featureState) {
337
+ const metadata = {
338
+ version: "1.0.0",
339
+ feature: featureState.name || 'unknown',
340
+ status: featureState.status,
341
+ phase: featureState.phase,
342
+ workflow: featureState.workflow,
343
+
344
+ agents: featureState.activeAgents || [],
345
+ checkpoints: featureState.checkpoints || [],
346
+
347
+ outputs: {},
348
+ quickLinks: {}
349
+ };
350
+
351
+ // Extract metadata from each output
352
+ if (featureState.outputs?.proposal?.created) {
353
+ metadata.outputs.proposal = extractProposalMetadata(featureState.outputs.proposal.path);
354
+ metadata.quickLinks.proposal = featureState.outputs.proposal.path;
355
+ }
356
+
357
+ if (featureState.outputs?.spec?.created) {
358
+ metadata.outputs.spec = extractSpecMetadata(featureState.outputs.spec.path);
359
+ metadata.quickLinks.spec = featureState.outputs.spec.path;
360
+ }
361
+
362
+ if (featureState.outputs?.decisions?.created) {
363
+ metadata.outputs.decisions = extractDecisionsMetadata(featureState.outputs.decisions.path);
364
+ metadata.quickLinks.decisions = featureState.outputs.decisions.path;
365
+ }
366
+
367
+ if (featureState.outputs?.tasks?.created) {
368
+ metadata.outputs.tasks = extractTasksMetadata(featureState.outputs.tasks.path);
369
+ metadata.quickLinks.tasks = featureState.outputs.tasks.path;
370
+ }
371
+
372
+ // Calculate progress
373
+ if (featureState.tasks) {
374
+ metadata.progress = {
375
+ total: featureState.tasks.total || 0,
376
+ completed: featureState.tasks.completed || 0,
377
+ percentage: featureState.tasks.total > 0
378
+ ? Math.round((featureState.tasks.completed / featureState.tasks.total) * 100)
379
+ : 0
380
+ };
381
+ }
382
+
383
+ metadata.generatedAt = new Date().toISOString();
384
+
385
+ return metadata;
386
+ }