@polymorphism-tech/morph-spec 4.12.0 → 4.13.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 (314) hide show
  1. package/README.md +21 -29
  2. package/bin/morph-spec.js +41 -24
  3. package/bin/task-manager.js +962 -1341
  4. package/bin/validate.js +1 -1
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +10 -11
  7. package/docs/QUICKSTART.md +15 -15
  8. package/framework/CLAUDE.md +5 -2
  9. package/framework/agents/README.md +3 -4
  10. package/framework/agents/ai-agents/ai-system-architect.md +6 -10
  11. package/framework/agents/architecture/seo-growth-hacker.md +166 -326
  12. package/framework/agents/architecture/standards-architect.md +21 -31
  13. package/framework/agents/backend/dotnet-senior.md +160 -86
  14. package/framework/agents/backend/ef-modeler.md +2 -6
  15. package/framework/agents/backend/hangfire-orchestrator.md +8 -9
  16. package/framework/agents/backend/ms-agent-expert.md +1 -1
  17. package/framework/agents/frontend/nextjs-expert.md +198 -133
  18. package/framework/agents/frontend/ui-ux-designer.md +226 -267
  19. package/framework/agents/infrastructure/infra-architect.md +191 -27
  20. package/framework/agents/integrations/clerk-auth.md +19 -26
  21. package/framework/agents/integrations/hangfire-integration.md +1 -4
  22. package/framework/agents.json +215 -1453
  23. package/framework/commands/morph-apply.md +4 -4
  24. package/framework/commands/morph-preflight.md +4 -26
  25. package/framework/commands/morph-proposal.md +19 -19
  26. package/framework/commands/morph-status.md +2 -2
  27. package/framework/commands/morph-troubleshoot.md +6 -13
  28. package/framework/hooks/README.md +1 -6
  29. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
  30. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
  31. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
  32. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
  33. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
  34. package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
  35. package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
  36. package/framework/hooks/claude-code/statusline.py +4 -5
  37. package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
  38. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
  39. package/framework/hooks/shared/compact-restore.js +28 -5
  40. package/framework/hooks/shared/phase-utils.js +4 -8
  41. package/framework/hooks/shared/state-reader.js +34 -3
  42. package/framework/phases.json +7 -45
  43. package/framework/rules/ai-agents-standards.md +15 -0
  44. package/framework/rules/asaas-standards.md +13 -0
  45. package/framework/rules/clerk-standards.md +13 -0
  46. package/framework/rules/csharp-standards.md +8 -4
  47. package/framework/rules/data-standards.md +18 -0
  48. package/framework/rules/dotnet-templates.md +87 -0
  49. package/framework/rules/frontend-standards.md +13 -13
  50. package/framework/rules/hangfire-standards.md +13 -0
  51. package/framework/rules/infrastructure-standards.md +27 -16
  52. package/framework/rules/morph-workflow.md +73 -13
  53. package/framework/rules/nextjs-standards.md +1 -0
  54. package/framework/rules/nextjs-templates.md +40 -0
  55. package/framework/rules/resend-standards.md +12 -0
  56. package/framework/rules/supabase-standards.md +29 -0
  57. package/framework/skills/README.md +6 -8
  58. package/framework/skills/level-0-meta/mcp-registry.json +21 -99
  59. package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
  60. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
  61. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  62. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
  63. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
  64. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
  65. package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
  66. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
  67. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
  68. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
  69. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
  70. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
  71. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
  72. package/framework/skills/level-1-workflows/README.md +1 -1
  73. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
  74. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +58 -124
  75. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
  76. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
  77. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
  78. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
  80. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
  81. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
  82. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
  83. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -2
  84. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
  85. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
  86. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  87. package/framework/squad-templates/backend-only.json +6 -6
  88. package/framework/squad-templates/frontend-only.json +17 -8
  89. package/framework/squad-templates/full-stack.json +29 -24
  90. package/framework/standards/STANDARDS.json +1021 -2369
  91. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
  92. package/framework/standards/backend/dotnet/result-pattern.md +213 -0
  93. package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
  94. package/framework/standards/backend/dotnet/scrutor.md +158 -0
  95. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
  96. package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
  97. package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
  98. package/framework/standards/integration/mcp/mcp-tools.md +91 -77
  99. package/framework/templates/IDE-SNIPPETS.md +2 -6
  100. package/framework/templates/README.md +709 -814
  101. package/framework/templates/REGISTRY.json +223 -762
  102. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
  103. package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
  104. package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
  105. package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
  106. package/framework/templates/code/dotnet/backend/repository.cs +148 -141
  107. package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
  108. package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
  109. package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
  110. package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
  111. package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
  112. package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
  113. package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
  114. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
  115. package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
  116. package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
  117. package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
  118. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
  119. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
  120. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
  121. package/framework/templates/code/dotnet/jobs/job.cs +21 -17
  122. package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
  123. package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
  124. package/framework/templates/code/dotnet/test.cs +192 -106
  125. package/framework/templates/context/ARCHITECTURE.md +89 -0
  126. package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
  127. package/framework/templates/context/CONTEXT.md +180 -181
  128. package/framework/templates/context/design-system.md +1 -5
  129. package/framework/templates/docs/onboarding.md +2 -3
  130. package/framework/templates/docs/proposal.md +171 -182
  131. package/framework/templates/docs/spec.md +1 -3
  132. package/framework/templates/docs/ui-components.md +0 -72
  133. package/framework/templates/examples/design-system-examples.md +4 -4
  134. package/framework/templates/feature/decisions.md +0 -66
  135. package/framework/templates/feature/recap.md +1 -2
  136. package/framework/templates/infrastructure/github/README.md +56 -593
  137. package/framework/templates/integrations/clerk-config.cs +0 -41
  138. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
  139. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
  140. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
  141. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
  142. package/framework/templates/project/validate.js +2 -4
  143. package/framework/templates/ui/design-system.css +1 -1
  144. package/framework/workflows/README.md +8 -8
  145. package/framework/workflows/configs/fast-track.json +51 -0
  146. package/framework/workflows/configs/full.json +68 -0
  147. package/framework/workflows/configs/standard.json +7 -3
  148. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
  149. package/framework/workflows/docs/enforcement-pipeline.md +18 -19
  150. package/framework/workflows/docs/full-morph.md +69 -76
  151. package/framework/workflows/docs/standard.md +49 -44
  152. package/framework/workflows/docs/ui-refresh.md +1 -1
  153. package/package.json +3 -3
  154. package/src/commands/agents/dispatch-agents.js +55 -116
  155. package/src/commands/context/context-refresh.js +79 -0
  156. package/src/commands/phase/phase-reset.js +1 -1
  157. package/src/commands/project/doctor.js +10 -32
  158. package/src/commands/project/init.js +12 -0
  159. package/src/commands/project/status.js +6 -12
  160. package/src/commands/scaffold/scaffold.js +131 -0
  161. package/src/commands/scope/escalate.js +1 -1
  162. package/src/commands/state/advance-phase.js +15 -29
  163. package/src/commands/state/state.js +20 -5
  164. package/src/commands/tasks/task.js +0 -30
  165. package/src/commands/validation/validate-feature.js +3 -10
  166. package/src/core/paths/output-schema.js +12 -13
  167. package/src/core/state/phase-state-machine.js +211 -217
  168. package/src/core/state/state-manager.js +9 -9
  169. package/src/lib/agents/agents-manager.js +2 -2
  170. package/src/lib/agents/output-collector.js +1 -2
  171. package/src/lib/detectors/design-system-detector.js +4 -4
  172. package/src/lib/detectors/index.js +1 -11
  173. package/src/lib/generators/architecture-scanner.js +640 -0
  174. package/src/lib/generators/context-generator.js +3 -3
  175. package/src/lib/generators/design-system-generator.js +508 -587
  176. package/src/lib/generators/index.js +12 -10
  177. package/src/lib/generators/metadata-extractor.js +386 -387
  178. package/src/lib/generators/recap-generator.js +4 -4
  179. package/src/lib/generators/scaffold-generator.js +317 -0
  180. package/src/lib/generators/settings-generator.js +0 -1
  181. package/src/lib/phase-chain/phase-validator.js +64 -42
  182. package/src/lib/stack/stack-profile.js +2 -2
  183. package/src/lib/standards/digest-builder.js +72 -285
  184. package/src/lib/tasks/task-parser.js +11 -5
  185. package/src/lib/validators/architecture/architecture-validator.js +259 -387
  186. package/src/lib/validators/content/content-validator.js +287 -287
  187. package/src/lib/validators/css/css-validator.js +340 -352
  188. package/src/lib/validators/packages/package-validator.js +347 -360
  189. package/src/lib/validators/shared/issue-counter.js +14 -18
  190. package/src/lib/validators/validation-runner.js +3 -19
  191. package/src/scripts/setup-infra.js +1 -1
  192. package/src/utils/agents-installer.js +24 -3
  193. package/src/utils/file-copier.js +1 -0
  194. package/src/utils/hooks-installer.js +11 -19
  195. package/src/utils/skills-installer.js +178 -178
  196. package/framework/agents/architecture/prompt-engineer.md +0 -195
  197. package/framework/agents/backend/api-designer.md +0 -106
  198. package/framework/agents/frontend/blazor-builder.md +0 -229
  199. package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
  200. package/framework/agents/infrastructure/azure-architect.md +0 -150
  201. package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
  202. package/framework/agents/infrastructure/bicep-architect.md +0 -132
  203. package/framework/agents/infrastructure/container-specialist.md +0 -137
  204. package/framework/agents/infrastructure/devops-engineer.md +0 -126
  205. package/framework/agents/integrations/azure-identity.md +0 -148
  206. package/framework/agents/quality/code-analyzer.md +0 -239
  207. package/framework/agents/quality/testing-specialist.md +0 -130
  208. package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
  209. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
  210. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
  211. package/framework/index/troubleshooting-index.json +0 -184
  212. package/framework/rules/testing-standards.md +0 -14
  213. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
  214. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
  215. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
  216. package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
  217. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
  218. package/framework/standards/ai-agents/blazor-ui.md +0 -364
  219. package/framework/standards/architecture/ddd/aggregates.md +0 -120
  220. package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
  221. package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
  222. package/framework/standards/architecture/ddd/entities.md +0 -99
  223. package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
  224. package/framework/standards/architecture/ddd/value-objects.md +0 -124
  225. package/framework/standards/backend/api/minimal-api.md +0 -494
  226. package/framework/standards/backend/api/rest.md +0 -492
  227. package/framework/standards/backend/authentication/passkeys.md +0 -428
  228. package/framework/standards/backend/database/ef-core.md +0 -199
  229. package/framework/standards/backend/database/migrations.md +0 -393
  230. package/framework/standards/backend/database/repository-patterns.md +0 -528
  231. package/framework/standards/backend/dotnet/async.md +0 -366
  232. package/framework/standards/backend/dotnet/core.md +0 -117
  233. package/framework/standards/backend/dotnet/di.md +0 -439
  234. package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  235. package/framework/standards/context/analytics.md +0 -96
  236. package/framework/standards/context/bundles.md +0 -110
  237. package/framework/standards/context/priming.md +0 -78
  238. package/framework/standards/core/architecture.md +0 -185
  239. package/framework/standards/core/coding.md +0 -214
  240. package/framework/standards/core/git-branching-strategy.md +0 -403
  241. package/framework/standards/core/git.md +0 -185
  242. package/framework/standards/core/testing.md +0 -295
  243. package/framework/standards/data/nosql/blob-storage.md +0 -102
  244. package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  245. package/framework/standards/frontend/blazor/design-checklist.md +0 -222
  246. package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  247. package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  248. package/framework/standards/frontend/blazor/html-conversion.md +0 -184
  249. package/framework/standards/frontend/blazor/lifecycle.md +0 -195
  250. package/framework/standards/frontend/blazor/pitfalls.md +0 -198
  251. package/framework/standards/frontend/blazor/state.md +0 -191
  252. package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
  253. package/framework/standards/frontend/design-system/design-trends.md +0 -171
  254. package/framework/standards/infrastructure/azure/azure.md +0 -624
  255. package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  256. package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  257. package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  258. package/framework/standards/infrastructure/azure/services/functions.md +0 -486
  259. package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  260. package/framework/standards/infrastructure/azure/services/storage.md +0 -407
  261. package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
  262. package/framework/standards/integration/api/graphql.md +0 -91
  263. package/framework/standards/integration/api/grpc.md +0 -114
  264. package/framework/standards/integration/api/rest-design.md +0 -95
  265. package/framework/standards/integration/event-driven/cqrs.md +0 -101
  266. package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  267. package/framework/standards/integration/event-driven/service-bus.md +0 -95
  268. package/framework/standards/observability/logging.md +0 -131
  269. package/framework/standards/observability/metrics.md +0 -121
  270. package/framework/standards/observability/monitoring.md +0 -114
  271. package/framework/standards/observability/tracing.md +0 -132
  272. package/framework/standards/workflows/parallel-execution.md +0 -112
  273. package/framework/standards/workflows/thread-management.md +0 -113
  274. package/framework/templates/code/dotnet/backend/service.cs +0 -139
  275. package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  276. package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  277. package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  278. package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  279. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
  280. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
  281. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
  282. package/framework/templates/code/dotnet/frontend/component.razor +0 -239
  283. package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  284. package/framework/templates/infrastructure/azure/README.md +0 -286
  285. package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  286. package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  287. package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  288. package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  289. package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  290. package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  291. package/framework/templates/infrastructure/azure/deploy.sh +0 -208
  292. package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  293. package/framework/templates/infrastructure/azure/main.bicep +0 -189
  294. package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  295. package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  296. package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  297. package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  298. package/framework/templates/infrastructure/azure/storage.bicep +0 -106
  299. package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  300. package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  301. package/framework/templates/integrations/azure-identity-config.cs +0 -288
  302. package/framework/templates/project-structure/dotnet-ddd.md +0 -70
  303. package/framework/templates/saas/subscription.cs +0 -347
  304. package/framework/templates/saas/tenant.cs +0 -338
  305. package/framework/templates/ui/FluentDesignTheme.cs +0 -149
  306. package/framework/templates/ui/MudTheme.cs +0 -281
  307. package/framework/workflows/configs/nodejs-cli.json +0 -40
  308. package/src/commands/standards/context-budget.js +0 -89
  309. package/src/lib/standards/index.js +0 -7
  310. package/src/lib/standards/standards-context-injector.js +0 -302
  311. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
  312. package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
  313. package/src/lib/validators/blazor/blazor-validator.js +0 -369
  314. 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
+ }