prjct-cli 0.11.4 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +81 -25
- package/bin/dev.js +1 -1
- package/bin/generate-views.js +209 -0
- package/bin/migrate-to-json.js +742 -0
- package/bin/prjct +5 -5
- package/bin/serve.js +246 -54
- package/core/__tests__/agentic/{memory-system.test.js → memory-system.test.ts} +12 -23
- package/core/__tests__/agentic/{plan-mode.test.js → plan-mode.test.ts} +26 -24
- package/core/__tests__/agentic/{prompt-builder.test.js → prompt-builder.test.ts} +3 -8
- package/core/__tests__/utils/date-helper.test.ts +405 -0
- package/core/__tests__/utils/{output.test.js → output.test.ts} +12 -24
- package/core/agentic/agent-router.ts +137 -0
- package/core/agentic/chain-of-thought.ts +228 -0
- package/core/agentic/command-executor/command-executor.ts +384 -0
- package/core/agentic/command-executor/index.ts +16 -0
- package/core/agentic/command-executor/status-signal.ts +38 -0
- package/core/agentic/command-executor/types.ts +79 -0
- package/core/agentic/command-executor.ts +8 -0
- package/core/agentic/{context-builder.js → context-builder.ts} +99 -89
- package/core/agentic/context-filter.ts +365 -0
- package/core/agentic/ground-truth/index.ts +76 -0
- package/core/agentic/ground-truth/types.ts +33 -0
- package/core/agentic/ground-truth/utils.ts +48 -0
- package/core/agentic/ground-truth/verifiers/analyze.ts +54 -0
- package/core/agentic/ground-truth/verifiers/done.ts +75 -0
- package/core/agentic/ground-truth/verifiers/feature.ts +70 -0
- package/core/agentic/ground-truth/verifiers/index.ts +37 -0
- package/core/agentic/ground-truth/verifiers/init.ts +52 -0
- package/core/agentic/ground-truth/verifiers/now.ts +57 -0
- package/core/agentic/ground-truth/verifiers/ship.ts +85 -0
- package/core/agentic/ground-truth/verifiers/spec.ts +45 -0
- package/core/agentic/ground-truth/verifiers/sync.ts +47 -0
- package/core/agentic/ground-truth/verifiers.ts +6 -0
- package/core/agentic/ground-truth.ts +8 -0
- package/core/agentic/loop-detector/error-analysis.ts +97 -0
- package/core/agentic/loop-detector/hallucination.ts +71 -0
- package/core/agentic/loop-detector/index.ts +41 -0
- package/core/agentic/loop-detector/loop-detector.ts +222 -0
- package/core/agentic/loop-detector/types.ts +66 -0
- package/core/agentic/loop-detector.ts +8 -0
- package/core/agentic/memory-system/history.ts +53 -0
- package/core/agentic/memory-system/index.ts +192 -0
- package/core/agentic/memory-system/patterns.ts +156 -0
- package/core/agentic/memory-system/semantic-memories.ts +277 -0
- package/core/agentic/memory-system/session.ts +21 -0
- package/core/agentic/memory-system/types.ts +159 -0
- package/core/agentic/memory-system.ts +8 -0
- package/core/agentic/parallel-tools.ts +165 -0
- package/core/agentic/plan-mode/approval.ts +57 -0
- package/core/agentic/plan-mode/constants.ts +44 -0
- package/core/agentic/plan-mode/index.ts +28 -0
- package/core/agentic/plan-mode/plan-mode.ts +406 -0
- package/core/agentic/plan-mode/types.ts +193 -0
- package/core/agentic/plan-mode.ts +8 -0
- package/core/agentic/prompt-builder.ts +566 -0
- package/core/agentic/response-templates.ts +164 -0
- package/core/agentic/semantic-compression.ts +273 -0
- package/core/agentic/services.ts +206 -0
- package/core/agentic/smart-context.ts +476 -0
- package/core/agentic/{template-loader.js → template-loader.ts} +35 -18
- package/core/agentic/think-blocks.ts +202 -0
- package/core/agentic/tool-registry.ts +119 -0
- package/core/agentic/validation-rules.ts +313 -0
- package/core/agents/index.ts +28 -0
- package/core/agents/performance.ts +444 -0
- package/core/agents/types.ts +126 -0
- package/core/bus/{index.js → index.ts} +57 -61
- package/core/command-registry/categories.ts +23 -0
- package/core/command-registry/commands.ts +15 -0
- package/core/command-registry/core-commands.ts +319 -0
- package/core/command-registry/index.ts +158 -0
- package/core/command-registry/optional-commands.ts +119 -0
- package/core/command-registry/setup-commands.ts +53 -0
- package/core/command-registry/types.ts +59 -0
- package/core/command-registry.ts +9 -0
- package/core/commands/analysis.ts +298 -0
- package/core/commands/analytics.ts +288 -0
- package/core/commands/base.ts +273 -0
- package/core/commands/index.ts +211 -0
- package/core/commands/maintenance.ts +226 -0
- package/core/commands/planning.ts +311 -0
- package/core/commands/setup.ts +309 -0
- package/core/commands/shipping.ts +188 -0
- package/core/commands/types.ts +183 -0
- package/core/commands/workflow.ts +226 -0
- package/core/commands.ts +11 -0
- package/core/constants/formats.ts +187 -0
- package/core/constants/index.ts +7 -0
- package/core/{context-sync.js → context-sync.ts} +59 -26
- package/core/data/agents-manager.ts +76 -0
- package/core/data/analysis-manager.ts +83 -0
- package/core/data/base-manager.ts +156 -0
- package/core/data/ideas-manager.ts +81 -0
- package/core/data/index.ts +32 -0
- package/core/data/outcomes-manager.ts +96 -0
- package/core/data/project-manager.ts +75 -0
- package/core/data/roadmap-manager.ts +118 -0
- package/core/data/shipped-manager.ts +65 -0
- package/core/data/state-manager.ts +214 -0
- package/core/domain/{agent-generator.js → agent-generator.ts} +77 -57
- package/core/domain/{agent-loader.js → agent-loader.ts} +65 -56
- package/core/domain/{agent-matcher.js → agent-matcher.ts} +51 -24
- package/core/domain/{agent-validator.js → agent-validator.ts} +70 -37
- package/core/domain/{analyzer.js → analyzer.ts} +91 -85
- package/core/domain/{architect-session.js → architect-session.ts} +49 -34
- package/core/domain/{architecture-generator.js → architecture-generator.ts} +25 -13
- package/core/domain/{context-estimator.js → context-estimator.ts} +57 -36
- package/core/domain/{product-standards.js → product-standards.ts} +40 -26
- package/core/domain/{smart-cache.js → smart-cache.ts} +39 -30
- package/core/domain/{snapshot-manager.js → snapshot-manager.ts} +103 -100
- package/core/domain/{task-analyzer.js → task-analyzer.ts} +82 -43
- package/core/domain/task-stack/index.ts +19 -0
- package/core/domain/task-stack/parser.ts +86 -0
- package/core/domain/task-stack/storage.ts +123 -0
- package/core/domain/task-stack/task-stack.ts +340 -0
- package/core/domain/task-stack/types.ts +51 -0
- package/core/domain/task-stack.ts +8 -0
- package/core/{index.js → index.ts} +61 -18
- package/core/infrastructure/{agent-detector.js → agent-detector.ts} +62 -23
- package/core/infrastructure/agents/{claude-agent.js → claude-agent.ts} +61 -21
- package/core/infrastructure/{author-detector.js → author-detector.ts} +42 -49
- package/core/infrastructure/{capability-installer.js → capability-installer.ts} +51 -27
- package/core/infrastructure/{command-installer.js → command-installer/command-installer.ts} +43 -144
- package/core/infrastructure/command-installer/global-config.ts +106 -0
- package/core/infrastructure/command-installer/index.ts +25 -0
- package/core/infrastructure/command-installer/types.ts +41 -0
- package/core/infrastructure/command-installer.ts +8 -0
- package/core/infrastructure/{config-manager.js → config-manager.ts} +60 -80
- package/core/infrastructure/{editors-config.js → editors-config.ts} +33 -31
- package/core/infrastructure/legacy-installer-detector/cleanup.ts +216 -0
- package/core/infrastructure/legacy-installer-detector/detection.ts +95 -0
- package/core/infrastructure/legacy-installer-detector/index.ts +171 -0
- package/core/infrastructure/legacy-installer-detector/migration.ts +87 -0
- package/core/infrastructure/legacy-installer-detector/types.ts +42 -0
- package/core/infrastructure/legacy-installer-detector.ts +7 -0
- package/core/infrastructure/migrator/file-operations.ts +125 -0
- package/core/infrastructure/migrator/index.ts +288 -0
- package/core/infrastructure/migrator/project-scanner.ts +89 -0
- package/core/infrastructure/migrator/reports.ts +117 -0
- package/core/infrastructure/migrator/types.ts +124 -0
- package/core/infrastructure/migrator/validation.ts +94 -0
- package/core/infrastructure/migrator/version-migration.ts +117 -0
- package/core/infrastructure/migrator.ts +10 -0
- package/core/infrastructure/{path-manager.js → path-manager.ts} +51 -91
- package/core/infrastructure/session-manager/index.ts +23 -0
- package/core/infrastructure/session-manager/migration.ts +88 -0
- package/core/infrastructure/session-manager/session-manager.ts +307 -0
- package/core/infrastructure/session-manager/types.ts +45 -0
- package/core/infrastructure/session-manager.ts +8 -0
- package/core/infrastructure/{setup.js → setup.ts} +29 -21
- package/core/infrastructure/{update-checker.js → update-checker.ts} +40 -18
- package/core/outcomes/analyzer.ts +333 -0
- package/core/outcomes/index.ts +34 -0
- package/core/outcomes/recorder.ts +194 -0
- package/core/outcomes/types.ts +145 -0
- package/core/plugin/{hooks.js → hooks.ts} +56 -58
- package/core/plugin/{index.js → index.ts} +19 -8
- package/core/plugin/{loader.js → loader.ts} +87 -69
- package/core/plugin/{registry.js → registry.ts} +49 -45
- package/core/plugins/{webhook.js → webhook.ts} +43 -27
- package/core/schemas/agents.ts +27 -0
- package/core/schemas/analysis.ts +41 -0
- package/core/schemas/ideas.ts +83 -0
- package/core/schemas/index.ts +73 -0
- package/core/schemas/outcomes.ts +22 -0
- package/core/schemas/project.ts +26 -0
- package/core/schemas/roadmap.ts +90 -0
- package/core/schemas/shipped.ts +82 -0
- package/core/schemas/state.ts +107 -0
- package/core/session/index.ts +17 -0
- package/core/session/{metrics.js → metrics.ts} +64 -46
- package/core/session/{index.js → session-manager.ts} +51 -117
- package/core/session/types.ts +29 -0
- package/core/session/utils.ts +57 -0
- package/core/state/index.ts +25 -0
- package/core/state/manager.ts +376 -0
- package/core/state/types.ts +185 -0
- package/core/tsconfig.json +22 -0
- package/core/types/index.ts +506 -0
- package/core/utils/{animations.js → animations.ts} +74 -28
- package/core/utils/{branding.js → branding.ts} +29 -4
- package/core/utils/{date-helper.js → date-helper.ts} +31 -74
- package/core/utils/file-helper.ts +262 -0
- package/core/utils/{jsonl-helper.js → jsonl-helper.ts} +71 -107
- package/core/utils/{logger.js → logger.ts} +24 -12
- package/core/utils/{output.js → output.ts} +25 -13
- package/core/utils/{project-capabilities.js → project-capabilities.ts} +31 -18
- package/core/utils/{session-helper.js → session-helper.ts} +79 -66
- package/core/utils/{version.js → version.ts} +23 -31
- package/core/view-generator.ts +536 -0
- package/package.json +23 -17
- package/packages/shared/.turbo/turbo-build.log +14 -0
- package/packages/shared/dist/index.d.ts +8 -613
- package/packages/shared/dist/index.d.ts.map +1 -0
- package/packages/shared/dist/index.js +4110 -118
- package/packages/shared/dist/schemas.d.ts +408 -0
- package/packages/shared/dist/schemas.d.ts.map +1 -0
- package/packages/shared/dist/types.d.ts +144 -0
- package/packages/shared/dist/types.d.ts.map +1 -0
- package/packages/shared/dist/unified.d.ts +139 -0
- package/packages/shared/dist/unified.d.ts.map +1 -0
- package/packages/shared/dist/utils.d.ts +60 -0
- package/packages/shared/dist/utils.d.ts.map +1 -0
- package/packages/shared/package.json +4 -4
- package/packages/shared/src/index.ts +1 -0
- package/packages/shared/src/unified.ts +174 -0
- package/packages/web/app/api/claude/sessions/route.ts +1 -1
- package/packages/web/app/api/claude/status/route.ts +1 -1
- package/packages/web/app/api/migrate/route.ts +46 -0
- package/packages/web/app/api/projects/[id]/route.ts +1 -1
- package/packages/web/app/api/projects/[id]/stats/route.ts +30 -2
- package/packages/web/app/api/projects/[id]/status/route.ts +1 -1
- package/packages/web/app/api/projects/route.ts +1 -1
- package/packages/web/app/api/settings/route.ts +97 -0
- package/packages/web/app/api/v2/projects/[id]/unified/route.ts +57 -0
- package/packages/web/app/globals.css +38 -0
- package/packages/web/app/layout.tsx +10 -2
- package/packages/web/app/page.tsx +9 -224
- package/packages/web/app/project/[id]/page.tsx +191 -63
- package/packages/web/app/project/[id]/stats/loading.tsx +43 -0
- package/packages/web/app/project/[id]/stats/page.tsx +203 -403
- package/packages/web/app/settings/page.tsx +222 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +2 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +50 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +8 -0
- package/packages/web/components/ActivityTimeline/hooks/index.ts +2 -0
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +9 -0
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +23 -0
- package/packages/web/components/ActivityTimeline/index.ts +2 -0
- package/packages/web/components/AgentsCard/AgentsCard.tsx +63 -0
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +13 -0
- package/packages/web/components/AgentsCard/index.ts +2 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +134 -0
- package/packages/web/components/AppSidebar/index.ts +1 -0
- package/packages/web/components/BackLink/BackLink.tsx +18 -0
- package/packages/web/components/BackLink/BackLink.types.ts +5 -0
- package/packages/web/components/BackLink/index.ts +2 -0
- package/packages/web/components/BentoCard/BentoCard.constants.ts +16 -0
- package/packages/web/components/BentoCard/BentoCard.tsx +47 -0
- package/packages/web/components/BentoCard/BentoCard.types.ts +15 -0
- package/packages/web/components/BentoCard/index.ts +2 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +9 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +18 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +5 -0
- package/packages/web/components/BentoCardSkeleton/index.ts +2 -0
- package/packages/web/components/BentoGrid/BentoGrid.tsx +18 -0
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +4 -0
- package/packages/web/components/BentoGrid/index.ts +2 -0
- package/packages/web/components/CommandButton/index.ts +1 -0
- package/packages/web/components/ConnectionStatus/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +254 -0
- package/packages/web/components/DashboardContent/index.ts +1 -0
- package/packages/web/components/DateGroup/DateGroup.tsx +18 -0
- package/packages/web/components/DateGroup/DateGroup.types.ts +6 -0
- package/packages/web/components/DateGroup/DateGroup.utils.ts +11 -0
- package/packages/web/components/DateGroup/index.ts +2 -0
- package/packages/web/components/EmptyState/EmptyState.tsx +58 -0
- package/packages/web/components/EmptyState/EmptyState.types.ts +10 -0
- package/packages/web/components/EmptyState/index.ts +2 -0
- package/packages/web/components/EventRow/EventRow.constants.ts +10 -0
- package/packages/web/components/EventRow/EventRow.tsx +49 -0
- package/packages/web/components/EventRow/EventRow.types.ts +7 -0
- package/packages/web/components/EventRow/EventRow.utils.ts +49 -0
- package/packages/web/components/EventRow/index.ts +2 -0
- package/packages/web/components/ExpandButton/ExpandButton.tsx +18 -0
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +6 -0
- package/packages/web/components/ExpandButton/index.ts +2 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +14 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +5 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +13 -0
- package/packages/web/components/HealthGradientBackground/index.ts +2 -0
- package/packages/web/components/HeroSection/HeroSection.tsx +55 -0
- package/packages/web/components/HeroSection/HeroSection.types.ts +14 -0
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -0
- package/packages/web/components/HeroSection/hooks/index.ts +2 -0
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +45 -0
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +18 -0
- package/packages/web/components/HeroSection/index.ts +2 -0
- package/packages/web/components/IdeasCard/IdeasCard.tsx +48 -0
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +9 -0
- package/packages/web/components/IdeasCard/index.ts +2 -0
- package/packages/web/components/InsightMessage/InsightMessage.tsx +9 -0
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +3 -0
- package/packages/web/components/InsightMessage/index.ts +2 -0
- package/packages/web/components/Logo/index.ts +1 -0
- package/packages/web/components/MarkdownContent/index.ts +1 -0
- package/packages/web/components/NowCard/NowCard.tsx +93 -0
- package/packages/web/components/NowCard/NowCard.types.ts +15 -0
- package/packages/web/components/NowCard/index.ts +2 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +20 -0
- package/packages/web/components/ProgressRing/ProgressRing.tsx +51 -0
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +11 -0
- package/packages/web/components/ProgressRing/index.ts +2 -0
- package/packages/web/components/ProjectAvatar/index.ts +1 -0
- package/packages/web/components/Providers/index.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.tsx +72 -0
- package/packages/web/components/QueueCard/QueueCard.types.ts +11 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +12 -0
- package/packages/web/components/QueueCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +77 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +15 -0
- package/packages/web/components/RoadmapCard/index.ts +2 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +52 -0
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +12 -0
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +4 -0
- package/packages/web/components/ShipsCard/index.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +38 -0
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +6 -0
- package/packages/web/components/SparklineChart/index.ts +2 -0
- package/packages/web/components/StreakCard/StreakCard.constants.ts +2 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +53 -0
- package/packages/web/components/StreakCard/StreakCard.types.ts +4 -0
- package/packages/web/components/StreakCard/index.ts +2 -0
- package/packages/web/components/TasksCounter/TasksCounter.tsx +14 -0
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +3 -0
- package/packages/web/components/TasksCounter/index.ts +2 -0
- package/packages/web/components/TechStackBadges/index.ts +1 -0
- package/packages/web/components/{TerminalTab.tsx → TerminalTabs/TerminalTab.tsx} +11 -0
- package/packages/web/components/{TerminalTabs.tsx → TerminalTabs/TerminalTabs.tsx} +29 -28
- package/packages/web/components/TerminalTabs/index.ts +1 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +27 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +3 -0
- package/packages/web/components/VelocityBadge/index.ts +2 -0
- package/packages/web/components/VelocityCard/VelocityCard.tsx +71 -0
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +7 -0
- package/packages/web/components/VelocityCard/index.ts +2 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +13 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +3 -0
- package/packages/web/components/WeeklySparkline/index.ts +2 -0
- package/packages/web/components/ui/input.tsx +21 -0
- package/packages/web/components/ui/tooltip.tsx +2 -2
- package/packages/web/context/TerminalTabsContext.tsx +46 -1
- package/packages/web/hooks/useClaudeTerminal.ts +71 -21
- package/packages/web/hooks/useProjectStats.ts +55 -0
- package/packages/web/hooks/useProjects.ts +6 -6
- package/packages/web/lib/actions/projects.ts +15 -0
- package/packages/web/lib/json-loader.ts +630 -0
- package/packages/web/lib/services/index.ts +9 -0
- package/packages/web/lib/services/migration.server.ts +598 -0
- package/packages/web/lib/services/projects.server.ts +52 -0
- package/packages/web/lib/services/stats.server.ts +264 -0
- package/packages/web/lib/unified-loader.ts +396 -0
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +10 -6
- package/packages/web/server.ts +36 -6
- package/templates/commands/done.md +76 -32
- package/templates/commands/feature.md +121 -47
- package/templates/commands/idea.md +81 -8
- package/templates/commands/now.md +41 -17
- package/templates/commands/ship.md +64 -25
- package/templates/commands/sync.md +28 -3
- package/core/agentic/agent-router.js +0 -128
- package/core/agentic/chain-of-thought.js +0 -578
- package/core/agentic/command-executor.js +0 -421
- package/core/agentic/context-filter.js +0 -354
- package/core/agentic/ground-truth.js +0 -591
- package/core/agentic/loop-detector.js +0 -406
- package/core/agentic/memory-system.js +0 -850
- package/core/agentic/parallel-tools.js +0 -366
- package/core/agentic/plan-mode.js +0 -572
- package/core/agentic/prompt-builder.js +0 -338
- package/core/agentic/response-templates.js +0 -290
- package/core/agentic/semantic-compression.js +0 -517
- package/core/agentic/think-blocks.js +0 -657
- package/core/agentic/tool-registry.js +0 -184
- package/core/agentic/validation-rules.js +0 -380
- package/core/command-registry.js +0 -698
- package/core/commands.js +0 -2237
- package/core/domain/task-stack.js +0 -497
- package/core/infrastructure/legacy-installer-detector.js +0 -546
- package/core/infrastructure/migrator.js +0 -799
- package/core/infrastructure/session-manager.js +0 -390
- package/core/utils/file-helper.js +0 -329
- package/packages/web/app/api/projects/[id]/delete/route.ts +0 -21
- package/packages/web/app/api/stats/route.ts +0 -38
- package/packages/web/components/AppSidebar.tsx +0 -113
- package/packages/web/hooks/useStats.ts +0 -28
- /package/packages/web/components/{CommandButton.tsx → CommandButton/CommandButton.tsx} +0 -0
- /package/packages/web/components/{ConnectionStatus.tsx → ConnectionStatus/ConnectionStatus.tsx} +0 -0
- /package/packages/web/components/{Logo.tsx → Logo/Logo.tsx} +0 -0
- /package/packages/web/components/{MarkdownContent.tsx → MarkdownContent/MarkdownContent.tsx} +0 -0
- /package/packages/web/components/{ProjectAvatar.tsx → ProjectAvatar/ProjectAvatar.tsx} +0 -0
- /package/packages/web/components/{providers.tsx → Providers/Providers.tsx} +0 -0
- /package/packages/web/components/{TechStackBadges.tsx → TechStackBadges/TechStackBadges.tsx} +0 -0
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const pathManager = require('./path-manager')
|
|
3
|
-
const { VERSION } = require('../utils/version')
|
|
4
|
-
const dateHelper = require('../utils/date-helper')
|
|
5
|
-
const jsonlHelper = require('../utils/jsonl-helper')
|
|
6
|
-
const fileHelper = require('../utils/file-helper')
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* SessionManager - Manages temporal fragmentation of logs and progress data
|
|
10
|
-
*
|
|
11
|
-
* Handles:
|
|
12
|
-
* - Daily session creation and rotation
|
|
13
|
-
* - Writing logs to date-specific directories
|
|
14
|
-
* - Reading historical data across multiple sessions
|
|
15
|
-
* - Session consolidation and queries
|
|
16
|
-
* - Automatic migration from legacy single-file logs
|
|
17
|
-
*
|
|
18
|
-
* @version 0.2.1
|
|
19
|
-
*/
|
|
20
|
-
class SessionManager {
|
|
21
|
-
constructor() {
|
|
22
|
-
this.currentSessionCache = new Map() // Cache current session paths
|
|
23
|
-
this.sessionMetadataCache = new Map() // Cache session metadata
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get or create current session directory for a project
|
|
28
|
-
*
|
|
29
|
-
* @param {string} projectId - The project identifier
|
|
30
|
-
* @returns {Promise<string>} - Path to today's session directory
|
|
31
|
-
*/
|
|
32
|
-
async getCurrentSession(projectId) {
|
|
33
|
-
const cacheKey = `${projectId}-${this._getTodayKey()}`
|
|
34
|
-
|
|
35
|
-
if (this.currentSessionCache.has(cacheKey)) {
|
|
36
|
-
return this.currentSessionCache.get(cacheKey)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const sessionPath = await pathManager.ensureSessionPath(projectId)
|
|
40
|
-
this.currentSessionCache.set(cacheKey, sessionPath)
|
|
41
|
-
|
|
42
|
-
await this._ensureSessionMetadata(sessionPath)
|
|
43
|
-
|
|
44
|
-
return sessionPath
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Write log entry to current session
|
|
49
|
-
*
|
|
50
|
-
* @param {string} projectId - The project identifier
|
|
51
|
-
* @param {Object} entry - Log entry object
|
|
52
|
-
* @param {string} filename - Target filename (default: context.jsonl)
|
|
53
|
-
* @returns {Promise<void>}
|
|
54
|
-
*/
|
|
55
|
-
async writeToSession(projectId, entry, filename = 'context.jsonl') {
|
|
56
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
57
|
-
const filePath = path.join(sessionPath, filename)
|
|
58
|
-
|
|
59
|
-
// Use automatic rotation to prevent large files (>10MB)
|
|
60
|
-
await jsonlHelper.appendJsonLineWithRotation(filePath, entry, 10)
|
|
61
|
-
|
|
62
|
-
await this._updateSessionMetadata(sessionPath, {
|
|
63
|
-
lastActivity: dateHelper.getTimestamp(),
|
|
64
|
-
entryCount: await jsonlHelper.countJsonLines(filePath),
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Append content to a session file (for markdown files like shipped.md)
|
|
70
|
-
*
|
|
71
|
-
* @param {string} projectId - The project identifier
|
|
72
|
-
* @param {string} content - Content to append
|
|
73
|
-
* @param {string} filename - Target filename
|
|
74
|
-
* @returns {Promise<void>}
|
|
75
|
-
*/
|
|
76
|
-
async appendToSession(projectId, content, filename) {
|
|
77
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
78
|
-
const filePath = path.join(sessionPath, filename)
|
|
79
|
-
|
|
80
|
-
const exists = await fileHelper.fileExists(filePath)
|
|
81
|
-
if (!exists && filename === 'shipped.md') {
|
|
82
|
-
await fileHelper.writeFile(filePath, '# SHIPPED 🚀\n\n' + content)
|
|
83
|
-
} else {
|
|
84
|
-
await fileHelper.appendToFile(filePath, content)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
await this._updateSessionMetadata(sessionPath, {
|
|
88
|
-
lastActivity: dateHelper.getTimestamp(),
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Read logs from current session
|
|
94
|
-
* Uses streaming for large files (>50MB)
|
|
95
|
-
*
|
|
96
|
-
* @param {string} projectId - The project identifier
|
|
97
|
-
* @param {string} filename - Source filename (default: context.jsonl)
|
|
98
|
-
* @param {number} maxLines - Max lines to read for large files (default: 1000)
|
|
99
|
-
* @returns {Promise<Array<Object>>} - Array of parsed log entries
|
|
100
|
-
*/
|
|
101
|
-
async readCurrentSession(projectId, filename = 'context.jsonl', maxLines = 1000) {
|
|
102
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
103
|
-
const filePath = path.join(sessionPath, filename)
|
|
104
|
-
|
|
105
|
-
// Check file size and warn if large
|
|
106
|
-
const { isLarge } = await jsonlHelper.checkFileSizeWarning(filePath, 50)
|
|
107
|
-
|
|
108
|
-
if (isLarge) {
|
|
109
|
-
// Use streaming for large files
|
|
110
|
-
return await jsonlHelper.readJsonLinesStreaming(filePath, maxLines)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Use normal read for small files
|
|
114
|
-
return await jsonlHelper.readJsonLines(filePath)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Read logs from a specific date range
|
|
119
|
-
*
|
|
120
|
-
* @param {string} projectId - The project identifier
|
|
121
|
-
* @param {Date} fromDate - Start date
|
|
122
|
-
* @param {Date} toDate - End date (defaults to today)
|
|
123
|
-
* @param {string} filename - Source filename (default: context.jsonl)
|
|
124
|
-
* @returns {Promise<Array<Object>>} - Array of parsed log entries from all sessions in range
|
|
125
|
-
*/
|
|
126
|
-
async readSessionRange(projectId, fromDate, toDate = new Date(), filename = 'context.jsonl') {
|
|
127
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
128
|
-
const allEntries = []
|
|
129
|
-
|
|
130
|
-
for (const session of sessions) {
|
|
131
|
-
const filePath = path.join(session.path, filename)
|
|
132
|
-
const entries = await jsonlHelper.readJsonLines(filePath)
|
|
133
|
-
|
|
134
|
-
entries.forEach((entry) => {
|
|
135
|
-
entry._sessionDate = session.date
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
allEntries.push(...entries)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return allEntries
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Read markdown content from sessions in date range
|
|
146
|
-
*
|
|
147
|
-
* @param {string} projectId - The project identifier
|
|
148
|
-
* @param {Date} fromDate - Start date
|
|
149
|
-
* @param {Date} toDate - End date
|
|
150
|
-
* @param {string} filename - Source filename (e.g., 'shipped.md')
|
|
151
|
-
* @returns {Promise<string>} - Concatenated content from all sessions
|
|
152
|
-
*/
|
|
153
|
-
async readMarkdownRange(projectId, fromDate, toDate, filename) {
|
|
154
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
155
|
-
const allContent = []
|
|
156
|
-
|
|
157
|
-
for (const session of sessions) {
|
|
158
|
-
const filePath = path.join(session.path, filename)
|
|
159
|
-
const content = await fileHelper.readFile(filePath, '')
|
|
160
|
-
|
|
161
|
-
if (content.trim()) {
|
|
162
|
-
allContent.push(
|
|
163
|
-
`## Session: ${session.year}-${session.month}-${session.day}\n\n${content}`
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return allContent.join('\n---\n\n')
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Get recent logs (last N days)
|
|
173
|
-
*
|
|
174
|
-
* @param {string} projectId - The project identifier
|
|
175
|
-
* @param {number} days - Number of days to look back
|
|
176
|
-
* @param {string} filename - Source filename
|
|
177
|
-
* @returns {Promise<Array<Object>>} - Recent log entries
|
|
178
|
-
*/
|
|
179
|
-
async getRecentLogs(projectId, days = 7, filename = 'context.jsonl') {
|
|
180
|
-
const toDate = new Date()
|
|
181
|
-
const fromDate = dateHelper.getDaysAgo(days)
|
|
182
|
-
|
|
183
|
-
return await this.readSessionRange(projectId, fromDate, toDate, filename)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Get session statistics
|
|
188
|
-
*
|
|
189
|
-
* @param {string} projectId - The project identifier
|
|
190
|
-
* @param {Date} fromDate - Start date
|
|
191
|
-
* @param {Date} toDate - End date
|
|
192
|
-
* @returns {Promise<Object>} - Statistics object
|
|
193
|
-
*/
|
|
194
|
-
async getSessionStats(projectId, fromDate, toDate) {
|
|
195
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
196
|
-
|
|
197
|
-
let totalEntries = 0
|
|
198
|
-
let totalShips = 0
|
|
199
|
-
let activeDays = 0
|
|
200
|
-
|
|
201
|
-
for (const session of sessions) {
|
|
202
|
-
const metadata = await this._getSessionMetadata(session.path)
|
|
203
|
-
if (metadata) {
|
|
204
|
-
totalEntries += metadata.entryCount || 0
|
|
205
|
-
totalShips += metadata.shipCount || 0
|
|
206
|
-
if (metadata.entryCount > 0) {
|
|
207
|
-
activeDays++
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
totalSessions: sessions.length,
|
|
214
|
-
activeDays,
|
|
215
|
-
totalEntries,
|
|
216
|
-
totalShips,
|
|
217
|
-
averageEntriesPerDay: activeDays > 0 ? Math.round(totalEntries / activeDays) : 0,
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Migrate legacy single-file logs to session structure
|
|
223
|
-
*
|
|
224
|
-
* @param {string} projectId - The project identifier
|
|
225
|
-
* @param {string} legacyFilePath - Path to legacy log file
|
|
226
|
-
* @param {string} sessionFilename - Target filename in sessions
|
|
227
|
-
* @returns {Promise<Object>} - Migration result
|
|
228
|
-
*/
|
|
229
|
-
async migrateLegacyLogs(projectId, legacyFilePath, sessionFilename) {
|
|
230
|
-
try {
|
|
231
|
-
const content = await fileHelper.readFile(legacyFilePath)
|
|
232
|
-
|
|
233
|
-
if (sessionFilename.endsWith('.jsonl')) {
|
|
234
|
-
return await this._migrateLegacyJsonl(projectId, content, sessionFilename)
|
|
235
|
-
} else {
|
|
236
|
-
return await this._migrateLegacyMarkdown(projectId, content, sessionFilename)
|
|
237
|
-
}
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return {
|
|
240
|
-
success: false,
|
|
241
|
-
message: `Migration failed: ${error.message}`,
|
|
242
|
-
entriesMigrated: 0,
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Migrate legacy JSONL file
|
|
249
|
-
* @private
|
|
250
|
-
*/
|
|
251
|
-
async _migrateLegacyJsonl(projectId, content, sessionFilename) {
|
|
252
|
-
const entries = jsonlHelper.parseJsonLines(content)
|
|
253
|
-
const sessionGroups = new Map()
|
|
254
|
-
|
|
255
|
-
for (const entry of entries) {
|
|
256
|
-
const date = new Date(entry.timestamp || entry.data?.timestamp || Date.now())
|
|
257
|
-
const dateKey = dateHelper.getDateKey(date)
|
|
258
|
-
|
|
259
|
-
if (!sessionGroups.has(dateKey)) {
|
|
260
|
-
sessionGroups.set(dateKey, [])
|
|
261
|
-
}
|
|
262
|
-
sessionGroups.get(dateKey).push(entry)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
let migratedCount = 0
|
|
266
|
-
for (const [dateKey, groupEntries] of sessionGroups) {
|
|
267
|
-
const [year, month, day] = dateKey.split('-')
|
|
268
|
-
const date = new Date(year, month - 1, day)
|
|
269
|
-
const sessionPath = await pathManager.ensureSessionPath(projectId, date)
|
|
270
|
-
const filePath = path.join(sessionPath, sessionFilename)
|
|
271
|
-
|
|
272
|
-
await jsonlHelper.writeJsonLines(filePath, groupEntries)
|
|
273
|
-
|
|
274
|
-
migratedCount += groupEntries.length
|
|
275
|
-
|
|
276
|
-
await this._ensureSessionMetadata(sessionPath)
|
|
277
|
-
await this._updateSessionMetadata(sessionPath, {
|
|
278
|
-
entryCount: groupEntries.length,
|
|
279
|
-
migrated: true,
|
|
280
|
-
migratedAt: dateHelper.getTimestamp(),
|
|
281
|
-
})
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
success: true,
|
|
286
|
-
message: `Migrated ${migratedCount} entries to ${sessionGroups.size} sessions`,
|
|
287
|
-
entriesMigrated: migratedCount,
|
|
288
|
-
sessionsCreated: sessionGroups.size,
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Migrate legacy markdown file
|
|
294
|
-
* @private
|
|
295
|
-
*/
|
|
296
|
-
async _migrateLegacyMarkdown(projectId, content, sessionFilename) {
|
|
297
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
298
|
-
const filePath = path.join(sessionPath, sessionFilename)
|
|
299
|
-
|
|
300
|
-
await fileHelper.writeFile(filePath, content)
|
|
301
|
-
|
|
302
|
-
await this._updateSessionMetadata(sessionPath, {
|
|
303
|
-
migrated: true,
|
|
304
|
-
migratedAt: dateHelper.getTimestamp(),
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
success: true,
|
|
309
|
-
message: 'Migrated markdown content to current session',
|
|
310
|
-
entriesMigrated: 1,
|
|
311
|
-
sessionsCreated: 1,
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Get session metadata
|
|
317
|
-
* @private
|
|
318
|
-
*/
|
|
319
|
-
async _getSessionMetadata(sessionPath) {
|
|
320
|
-
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
321
|
-
|
|
322
|
-
if (this.sessionMetadataCache.has(sessionPath)) {
|
|
323
|
-
return this.sessionMetadataCache.get(sessionPath)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const metadata = await fileHelper.readJson(metadataPath, null)
|
|
327
|
-
if (metadata) {
|
|
328
|
-
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
329
|
-
}
|
|
330
|
-
return metadata
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Ensure session metadata exists
|
|
335
|
-
* @private
|
|
336
|
-
*/
|
|
337
|
-
async _ensureSessionMetadata(sessionPath) {
|
|
338
|
-
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
339
|
-
|
|
340
|
-
const exists = await fileHelper.fileExists(metadataPath)
|
|
341
|
-
if (!exists) {
|
|
342
|
-
const metadata = {
|
|
343
|
-
created: dateHelper.getTimestamp(),
|
|
344
|
-
lastActivity: dateHelper.getTimestamp(),
|
|
345
|
-
entryCount: 0,
|
|
346
|
-
shipCount: 0,
|
|
347
|
-
version: VERSION,
|
|
348
|
-
}
|
|
349
|
-
await fileHelper.writeJson(metadataPath, metadata)
|
|
350
|
-
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Update session metadata
|
|
356
|
-
* @private
|
|
357
|
-
*/
|
|
358
|
-
async _updateSessionMetadata(sessionPath, updates) {
|
|
359
|
-
const metadata = (await this._getSessionMetadata(sessionPath)) || {}
|
|
360
|
-
Object.assign(metadata, updates)
|
|
361
|
-
|
|
362
|
-
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
363
|
-
await fileHelper.writeJson(metadataPath, metadata)
|
|
364
|
-
|
|
365
|
-
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Get today's date key (YYYY-MM-DD)
|
|
370
|
-
* @private
|
|
371
|
-
*/
|
|
372
|
-
_getTodayKey() {
|
|
373
|
-
return dateHelper.getTodayKey()
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Get date key for any date (YYYY-MM-DD)
|
|
378
|
-
* @private
|
|
379
|
-
*/
|
|
380
|
-
_getDateKey(date) {
|
|
381
|
-
return dateHelper.getDateKey(date)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
clearCache() {
|
|
385
|
-
this.currentSessionCache.clear()
|
|
386
|
-
this.sessionMetadataCache.clear()
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
module.exports = new SessionManager()
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
const fs = require('fs').promises
|
|
2
|
-
const path = require('path')
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* File Helper - Centralized file operations with error handling
|
|
6
|
-
*
|
|
7
|
-
* Eliminates duplicated fs operations across:
|
|
8
|
-
* - 101 fs.readFile/writeFile calls in 18 files
|
|
9
|
-
* - Consistent error handling
|
|
10
|
-
* - JSON read/write patterns
|
|
11
|
-
*
|
|
12
|
-
* @module file-helper
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Read JSON file and parse
|
|
17
|
-
*
|
|
18
|
-
* @param {string} filePath - Path to JSON file
|
|
19
|
-
* @param {*} defaultValue - Default value if file doesn't exist (default: null)
|
|
20
|
-
* @returns {Promise<Object|*>} - Parsed JSON or default value
|
|
21
|
-
*/
|
|
22
|
-
async function readJson(filePath, defaultValue = null) {
|
|
23
|
-
try {
|
|
24
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
25
|
-
return JSON.parse(content)
|
|
26
|
-
} catch (error) {
|
|
27
|
-
if (error.code === 'ENOENT') {
|
|
28
|
-
return defaultValue
|
|
29
|
-
}
|
|
30
|
-
throw error
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Write object to JSON file (pretty-printed)
|
|
36
|
-
*
|
|
37
|
-
* @param {string} filePath - Path to JSON file
|
|
38
|
-
* @param {Object} data - Data to write
|
|
39
|
-
* @param {number} indent - Indentation spaces (default: 2)
|
|
40
|
-
* @returns {Promise<void>}
|
|
41
|
-
*/
|
|
42
|
-
async function writeJson(filePath, data, indent = 2) {
|
|
43
|
-
const content = JSON.stringify(data, null, indent)
|
|
44
|
-
await fs.writeFile(filePath, content, 'utf-8')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Read text file
|
|
49
|
-
*
|
|
50
|
-
* @param {string} filePath - Path to file
|
|
51
|
-
* @param {string} defaultValue - Default value if file doesn't exist (default: '')
|
|
52
|
-
* @returns {Promise<string>} - File content or default value
|
|
53
|
-
*/
|
|
54
|
-
async function readFile(filePath, defaultValue = '') {
|
|
55
|
-
try {
|
|
56
|
-
return await fs.readFile(filePath, 'utf-8')
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (error.code === 'ENOENT') {
|
|
59
|
-
return defaultValue
|
|
60
|
-
}
|
|
61
|
-
throw error
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Write text file
|
|
67
|
-
*
|
|
68
|
-
* @param {string} filePath - Path to file
|
|
69
|
-
* @param {string} content - Content to write
|
|
70
|
-
* @returns {Promise<void>}
|
|
71
|
-
*/
|
|
72
|
-
async function writeFile(filePath, content) {
|
|
73
|
-
const dir = path.dirname(filePath)
|
|
74
|
-
await fs.mkdir(dir, { recursive: true })
|
|
75
|
-
await fs.writeFile(filePath, content, 'utf-8')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Append to text file
|
|
80
|
-
*
|
|
81
|
-
* @param {string} filePath - Path to file
|
|
82
|
-
* @param {string} content - Content to append
|
|
83
|
-
* @returns {Promise<void>}
|
|
84
|
-
*/
|
|
85
|
-
async function appendToFile(filePath, content) {
|
|
86
|
-
await fs.appendFile(filePath, content, 'utf-8')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Prepend to text file (adds content at beginning)
|
|
91
|
-
*
|
|
92
|
-
* @param {string} filePath - Path to file
|
|
93
|
-
* @param {string} content - Content to prepend
|
|
94
|
-
* @returns {Promise<void>}
|
|
95
|
-
*/
|
|
96
|
-
async function prependToFile(filePath, content) {
|
|
97
|
-
try {
|
|
98
|
-
const existing = await fs.readFile(filePath, 'utf-8')
|
|
99
|
-
await fs.writeFile(filePath, content + existing, 'utf-8')
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (error.code === 'ENOENT') {
|
|
102
|
-
await fs.writeFile(filePath, content, 'utf-8')
|
|
103
|
-
} else {
|
|
104
|
-
throw error
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Check if file exists
|
|
111
|
-
*
|
|
112
|
-
* @param {string} filePath - Path to file
|
|
113
|
-
* @returns {Promise<boolean>} - True if file exists
|
|
114
|
-
*/
|
|
115
|
-
async function fileExists(filePath) {
|
|
116
|
-
try {
|
|
117
|
-
await fs.access(filePath)
|
|
118
|
-
return true
|
|
119
|
-
} catch {
|
|
120
|
-
return false
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Check if directory exists
|
|
126
|
-
*
|
|
127
|
-
* @param {string} dirPath - Path to directory
|
|
128
|
-
* @returns {Promise<boolean>} - True if directory exists
|
|
129
|
-
*/
|
|
130
|
-
async function dirExists(dirPath) {
|
|
131
|
-
try {
|
|
132
|
-
const stats = await fs.stat(dirPath)
|
|
133
|
-
return stats.isDirectory()
|
|
134
|
-
} catch {
|
|
135
|
-
return false
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Ensure directory exists (create if not)
|
|
141
|
-
*
|
|
142
|
-
* @param {string} dirPath - Path to directory
|
|
143
|
-
* @returns {Promise<void>}
|
|
144
|
-
*/
|
|
145
|
-
async function ensureDir(dirPath) {
|
|
146
|
-
await fs.mkdir(dirPath, { recursive: true })
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Delete file if it exists
|
|
151
|
-
*
|
|
152
|
-
* @param {string} filePath - Path to file
|
|
153
|
-
* @returns {Promise<boolean>} - True if file was deleted
|
|
154
|
-
*/
|
|
155
|
-
async function deleteFile(filePath) {
|
|
156
|
-
try {
|
|
157
|
-
await fs.unlink(filePath)
|
|
158
|
-
return true
|
|
159
|
-
} catch (error) {
|
|
160
|
-
if (error.code === 'ENOENT') {
|
|
161
|
-
return false // File didn't exist
|
|
162
|
-
}
|
|
163
|
-
throw error
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Delete directory and all contents
|
|
169
|
-
*
|
|
170
|
-
* @param {string} dirPath - Path to directory
|
|
171
|
-
* @returns {Promise<boolean>} - True if directory was deleted
|
|
172
|
-
*/
|
|
173
|
-
async function deleteDir(dirPath) {
|
|
174
|
-
try {
|
|
175
|
-
await fs.rm(dirPath, { recursive: true, force: true })
|
|
176
|
-
return true
|
|
177
|
-
} catch (error) {
|
|
178
|
-
if (error.code === 'ENOENT') {
|
|
179
|
-
return false
|
|
180
|
-
}
|
|
181
|
-
throw error
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* List files in directory
|
|
187
|
-
*
|
|
188
|
-
* @param {string} dirPath - Path to directory
|
|
189
|
-
* @param {Object} options - Options
|
|
190
|
-
* @param {boolean} options.filesOnly - Only return files (not directories)
|
|
191
|
-
* @param {boolean} options.dirsOnly - Only return directories
|
|
192
|
-
* @param {string} options.extension - Filter by file extension (e.g., '.md')
|
|
193
|
-
* @returns {Promise<Array<string>>} - Array of filenames
|
|
194
|
-
*/
|
|
195
|
-
async function listFiles(dirPath, options = {}) {
|
|
196
|
-
try {
|
|
197
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
198
|
-
let files = entries
|
|
199
|
-
|
|
200
|
-
if (options.filesOnly) {
|
|
201
|
-
files = files.filter((entry) => entry.isFile())
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (options.dirsOnly) {
|
|
205
|
-
files = files.filter((entry) => entry.isDirectory())
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (options.extension) {
|
|
209
|
-
files = files.filter((entry) => entry.name.endsWith(options.extension))
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return files.map((entry) => entry.name)
|
|
213
|
-
} catch (error) {
|
|
214
|
-
if (error.code === 'ENOENT') {
|
|
215
|
-
return []
|
|
216
|
-
}
|
|
217
|
-
throw error
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get file size in bytes
|
|
223
|
-
*
|
|
224
|
-
* @param {string} filePath - Path to file
|
|
225
|
-
* @returns {Promise<number>} - File size in bytes
|
|
226
|
-
*/
|
|
227
|
-
async function getFileSize(filePath) {
|
|
228
|
-
const stats = await fs.stat(filePath)
|
|
229
|
-
return stats.size
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Get file modification time
|
|
234
|
-
*
|
|
235
|
-
* @param {string} filePath - Path to file
|
|
236
|
-
* @returns {Promise<Date>} - Last modification time
|
|
237
|
-
*/
|
|
238
|
-
async function getFileModifiedTime(filePath) {
|
|
239
|
-
const stats = await fs.stat(filePath)
|
|
240
|
-
return stats.mtime
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Copy file
|
|
245
|
-
*
|
|
246
|
-
* @param {string} sourcePath - Source file path
|
|
247
|
-
* @param {string} destPath - Destination file path
|
|
248
|
-
* @returns {Promise<void>}
|
|
249
|
-
*/
|
|
250
|
-
async function copyFile(sourcePath, destPath) {
|
|
251
|
-
await fs.copyFile(sourcePath, destPath)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Move/rename file
|
|
256
|
-
*
|
|
257
|
-
* @param {string} oldPath - Current file path
|
|
258
|
-
* @param {string} newPath - New file path
|
|
259
|
-
* @returns {Promise<void>}
|
|
260
|
-
*/
|
|
261
|
-
async function moveFile(oldPath, newPath) {
|
|
262
|
-
await fs.rename(oldPath, newPath)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Read file and split into lines
|
|
267
|
-
*
|
|
268
|
-
* @param {string} filePath - Path to file
|
|
269
|
-
* @returns {Promise<Array<string>>} - Array of lines
|
|
270
|
-
*/
|
|
271
|
-
async function readLines(filePath) {
|
|
272
|
-
const content = await readFile(filePath, '')
|
|
273
|
-
return content.split('\n')
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Write lines to file
|
|
278
|
-
*
|
|
279
|
-
* @param {string} filePath - Path to file
|
|
280
|
-
* @param {Array<string>} lines - Array of lines
|
|
281
|
-
* @returns {Promise<void>}
|
|
282
|
-
*/
|
|
283
|
-
async function writeLines(filePath, lines) {
|
|
284
|
-
const content = lines.join('\n')
|
|
285
|
-
await writeFile(filePath, content)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Get file extension
|
|
290
|
-
*
|
|
291
|
-
* @param {string} filePath - Path to file
|
|
292
|
-
* @returns {string} - File extension (e.g., '.md')
|
|
293
|
-
*/
|
|
294
|
-
function getFileExtension(filePath) {
|
|
295
|
-
return path.extname(filePath)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Get filename without extension
|
|
300
|
-
*
|
|
301
|
-
* @param {string} filePath - Path to file
|
|
302
|
-
* @returns {string} - Filename without extension
|
|
303
|
-
*/
|
|
304
|
-
function getFileNameWithoutExtension(filePath) {
|
|
305
|
-
return path.basename(filePath, path.extname(filePath))
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
module.exports = {
|
|
309
|
-
readJson,
|
|
310
|
-
writeJson,
|
|
311
|
-
readFile,
|
|
312
|
-
writeFile,
|
|
313
|
-
appendToFile,
|
|
314
|
-
prependToFile,
|
|
315
|
-
fileExists,
|
|
316
|
-
dirExists,
|
|
317
|
-
ensureDir,
|
|
318
|
-
deleteFile,
|
|
319
|
-
deleteDir,
|
|
320
|
-
listFiles,
|
|
321
|
-
getFileSize,
|
|
322
|
-
getFileModifiedTime,
|
|
323
|
-
copyFile,
|
|
324
|
-
moveFile,
|
|
325
|
-
readLines,
|
|
326
|
-
writeLines,
|
|
327
|
-
getFileExtension,
|
|
328
|
-
getFileNameWithoutExtension,
|
|
329
|
-
}
|