prjct-cli 1.22.0 → 1.23.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 +147 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct.mjs +917 -35845
- package/dist/bin/prjct.mjs.map +7 -0
- package/dist/cli/linear.mjs +16 -0
- package/dist/cli/linear.mjs.map +7 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
package/core/utils/output.ts
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified Output System for prjct-cli
|
|
3
|
-
* Spinner while working → Single line result
|
|
4
|
-
* With prjct branding
|
|
5
|
-
*
|
|
6
|
-
* Supports --quiet mode for CI/CD and scripting
|
|
7
|
-
* Supports output tiers: silent, minimal, compact, verbose
|
|
8
|
-
*
|
|
9
|
-
* @see PRJ-105, PRJ-130
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import chalk from 'chalk'
|
|
13
|
-
import { OUTPUT_LIMITS } from '../constants'
|
|
14
|
-
import type { ErrorCode, ErrorWithHint } from '../types/errors'
|
|
15
|
-
import type { Output, OutputMetrics, OutputTier, TierConfig } from '../types/output'
|
|
16
|
-
import branding from './branding'
|
|
17
|
-
import { getError } from './error-messages'
|
|
18
|
-
|
|
19
|
-
const _FRAMES = branding.spinner.frames
|
|
20
|
-
const SPEED = branding.spinner.speed
|
|
21
|
-
|
|
22
|
-
export type { Output, OutputMetrics, OutputTier, TierConfig } from '../types/output'
|
|
23
|
-
|
|
24
|
-
export const OUTPUT_TIERS: Record<OutputTier, TierConfig> = {
|
|
25
|
-
silent: { maxLines: 0, maxCharsPerLine: 0, showMetrics: false },
|
|
26
|
-
minimal: { maxLines: 1, maxCharsPerLine: 65, showMetrics: false },
|
|
27
|
-
compact: { maxLines: 4, maxCharsPerLine: 80, showMetrics: true },
|
|
28
|
-
verbose: { maxLines: Infinity, maxCharsPerLine: Infinity, showMetrics: true },
|
|
29
|
-
} as const
|
|
30
|
-
|
|
31
|
-
// Current output tier (default: compact for human-readable output)
|
|
32
|
-
let currentTier: OutputTier = 'compact'
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Set the output tier
|
|
36
|
-
*/
|
|
37
|
-
export function setOutputTier(tier: OutputTier): void {
|
|
38
|
-
currentTier = tier
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get current output tier
|
|
43
|
-
*/
|
|
44
|
-
export function getOutputTier(): OutputTier {
|
|
45
|
-
return currentTier
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Get current tier config
|
|
50
|
-
*/
|
|
51
|
-
export function getTierConfig(): TierConfig {
|
|
52
|
-
return OUTPUT_TIERS[currentTier]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Centralized icons for consistent output
|
|
57
|
-
*/
|
|
58
|
-
export const ICONS = {
|
|
59
|
-
success: chalk.green('✓'),
|
|
60
|
-
fail: chalk.red('✗'),
|
|
61
|
-
warn: chalk.yellow('⚠'),
|
|
62
|
-
info: chalk.blue('ℹ'),
|
|
63
|
-
debug: chalk.dim('🔧'),
|
|
64
|
-
bullet: chalk.dim('•'),
|
|
65
|
-
arrow: chalk.dim('→'),
|
|
66
|
-
check: chalk.green('✓'),
|
|
67
|
-
cross: chalk.red('✗'),
|
|
68
|
-
spinner: chalk.cyan('◐'),
|
|
69
|
-
} as const
|
|
70
|
-
|
|
71
|
-
let interval: ReturnType<typeof setInterval> | null = null
|
|
72
|
-
let frame = 0
|
|
73
|
-
|
|
74
|
-
// Quiet mode - suppress all stdout except errors
|
|
75
|
-
let quietMode = false
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Enable quiet mode (no stdout, only stderr for errors)
|
|
79
|
-
*/
|
|
80
|
-
export function setQuietMode(enabled: boolean): void {
|
|
81
|
-
quietMode = enabled
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Check if quiet mode is enabled
|
|
86
|
-
*/
|
|
87
|
-
export function isQuietMode(): boolean {
|
|
88
|
-
return quietMode
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Truncate string to max chars (uses tier config if no max specified)
|
|
93
|
-
*/
|
|
94
|
-
const truncate = (s: string | undefined | null, max?: number): string => {
|
|
95
|
-
const limit = max ?? (getTierConfig().maxCharsPerLine || OUTPUT_LIMITS.FALLBACK_TRUNCATE)
|
|
96
|
-
return s && s.length > limit ? `${s.slice(0, limit - 1)}…` : s || ''
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Limit output to maxLines (respects tier config)
|
|
101
|
-
* Returns truncated content with "...N more lines" indicator
|
|
102
|
-
*/
|
|
103
|
-
/**
|
|
104
|
-
* Limit output to maxLines (respects tier config)
|
|
105
|
-
* Returns truncated content with "...N more lines" indicator
|
|
106
|
-
*/
|
|
107
|
-
export function limitLines(content: string, maxLines?: number): string {
|
|
108
|
-
const limit = maxLines ?? getTierConfig().maxLines
|
|
109
|
-
if (limit === Infinity || limit === 0) return content
|
|
110
|
-
|
|
111
|
-
const lines = content.split('\n')
|
|
112
|
-
if (lines.length <= limit) return content
|
|
113
|
-
|
|
114
|
-
const shown = lines.slice(0, limit)
|
|
115
|
-
const remaining = lines.length - limit
|
|
116
|
-
return `${shown.join('\n')}\n${chalk.dim(`...${remaining} more lines`)}`
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Format data for human-readable output (respects tier)
|
|
121
|
-
* Use this instead of JSON.stringify for CLI output
|
|
122
|
-
*/
|
|
123
|
-
export function formatForHuman(data: unknown): string {
|
|
124
|
-
const tier = getTierConfig()
|
|
125
|
-
|
|
126
|
-
if (currentTier === 'silent') return ''
|
|
127
|
-
if (currentTier === 'verbose') return JSON.stringify(data, null, 2)
|
|
128
|
-
|
|
129
|
-
// For minimal/compact: extract key info
|
|
130
|
-
if (typeof data !== 'object' || data === null) {
|
|
131
|
-
return truncate(String(data), tier.maxCharsPerLine)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const obj = data as Record<string, unknown>
|
|
135
|
-
|
|
136
|
-
// Linear issue format
|
|
137
|
-
if ('identifier' in obj && 'title' in obj) {
|
|
138
|
-
const lines: string[] = []
|
|
139
|
-
lines.push(`${obj.identifier}: ${truncate(String(obj.title), tier.maxCharsPerLine - 10)}`)
|
|
140
|
-
if (obj.status) lines.push(`Status: ${obj.status}`)
|
|
141
|
-
if (obj.priority && obj.priority !== 'none') lines.push(`Priority: ${obj.priority}`)
|
|
142
|
-
if (obj.url && currentTier === 'compact') lines.push(chalk.dim(String(obj.url)))
|
|
143
|
-
return limitLines(lines.join('\n'), tier.maxLines)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Issue list format
|
|
147
|
-
if ('issues' in obj && Array.isArray(obj.issues)) {
|
|
148
|
-
const issues = obj.issues as Array<Record<string, unknown>>
|
|
149
|
-
const lines = issues.slice(0, tier.maxLines).map((i) => {
|
|
150
|
-
const priority = i.priority && i.priority !== 'none' ? ` [${i.priority}]` : ''
|
|
151
|
-
return `${i.identifier} ${truncate(String(i.title), OUTPUT_LIMITS.ISSUE_TITLE)}${priority}`
|
|
152
|
-
})
|
|
153
|
-
if (issues.length > tier.maxLines) {
|
|
154
|
-
lines.push(chalk.dim(`...${issues.length - tier.maxLines} more`))
|
|
155
|
-
}
|
|
156
|
-
return lines.join('\n')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Generic object: show key fields only
|
|
160
|
-
const keyFields = ['id', 'name', 'title', 'status', 'message', 'success', 'error']
|
|
161
|
-
const relevant = keyFields.filter((k) => k in obj)
|
|
162
|
-
if (relevant.length > 0) {
|
|
163
|
-
return limitLines(
|
|
164
|
-
relevant
|
|
165
|
-
.map((k) => `${k}: ${truncate(String(obj[k]), tier.maxCharsPerLine - k.length - 2)}`)
|
|
166
|
-
.join('\n'),
|
|
167
|
-
tier.maxLines
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Fallback: compact JSON
|
|
172
|
-
return limitLines(JSON.stringify(data, null, 2), tier.maxLines)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const clear = (): boolean =>
|
|
176
|
-
process.stdout.isTTY ? process.stdout.write(`\r${' '.repeat(OUTPUT_LIMITS.CLEAR_WIDTH)}\r`) : true
|
|
177
|
-
|
|
178
|
-
const out: Output = {
|
|
179
|
-
// Branding: Show header at start
|
|
180
|
-
start() {
|
|
181
|
-
if (!quietMode) console.log(branding.cli.header())
|
|
182
|
-
return this
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
// Branding: Show footer at end
|
|
186
|
-
end() {
|
|
187
|
-
if (!quietMode) console.log(branding.cli.footer())
|
|
188
|
-
return this
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
// Branded spinner: prjct message...
|
|
192
|
-
// In non-TTY (CI, Claude Code), prints a static line instead of animating
|
|
193
|
-
spin(msg: string) {
|
|
194
|
-
if (quietMode) return this
|
|
195
|
-
this.stop()
|
|
196
|
-
if (!process.stdout.isTTY) {
|
|
197
|
-
process.stdout.write(`${branding.cli.spin(0, truncate(msg, OUTPUT_LIMITS.SPINNER_MSG))}\n`)
|
|
198
|
-
return this
|
|
199
|
-
}
|
|
200
|
-
interval = setInterval(() => {
|
|
201
|
-
process.stdout.write(
|
|
202
|
-
`\r${branding.cli.spin(frame++, truncate(msg, OUTPUT_LIMITS.SPINNER_MSG))}`
|
|
203
|
-
)
|
|
204
|
-
}, SPEED)
|
|
205
|
-
return this
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
done(msg: string, metrics?: OutputMetrics) {
|
|
209
|
-
this.stop()
|
|
210
|
-
if (!quietMode) {
|
|
211
|
-
// Build metrics suffix if provided: [2a | 97% | 45K]
|
|
212
|
-
let suffix = ''
|
|
213
|
-
if (metrics) {
|
|
214
|
-
const parts: string[] = []
|
|
215
|
-
if (metrics.agents !== undefined) parts.push(`${metrics.agents}a`)
|
|
216
|
-
if (metrics.reduction !== undefined) parts.push(`${metrics.reduction}%`)
|
|
217
|
-
if (metrics.tokens !== undefined) parts.push(`${Math.round(metrics.tokens)}K`)
|
|
218
|
-
if (parts.length > 0) {
|
|
219
|
-
suffix = chalk.dim(` [${parts.join(' | ')}]`)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
console.log(`${ICONS.success} ${truncate(msg, OUTPUT_LIMITS.DONE_MSG)}${suffix}`)
|
|
223
|
-
}
|
|
224
|
-
return this
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
// Errors go to stderr even in quiet mode
|
|
228
|
-
fail(msg: string) {
|
|
229
|
-
this.stop()
|
|
230
|
-
console.error(`${ICONS.fail} ${truncate(msg, OUTPUT_LIMITS.FAIL_MSG)}`)
|
|
231
|
-
return this
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
// Rich error with context and recovery hint
|
|
235
|
-
failWithHint(error: ErrorWithHint | ErrorCode) {
|
|
236
|
-
this.stop()
|
|
237
|
-
const err = typeof error === 'string' ? getError(error as ErrorCode) : error
|
|
238
|
-
console.error()
|
|
239
|
-
console.error(`${ICONS.fail} ${err.message}`)
|
|
240
|
-
if (err.file) {
|
|
241
|
-
console.error(chalk.dim(` File: ${err.file}`))
|
|
242
|
-
}
|
|
243
|
-
if (err.hint) {
|
|
244
|
-
console.error(chalk.yellow(` 💡 ${err.hint}`))
|
|
245
|
-
}
|
|
246
|
-
if (err.docs) {
|
|
247
|
-
console.error(chalk.dim(` Docs: ${err.docs}`))
|
|
248
|
-
}
|
|
249
|
-
console.error()
|
|
250
|
-
return this
|
|
251
|
-
},
|
|
252
|
-
|
|
253
|
-
warn(msg: string) {
|
|
254
|
-
this.stop()
|
|
255
|
-
if (!quietMode) console.log(`${ICONS.warn} ${truncate(msg, OUTPUT_LIMITS.WARN_MSG)}`)
|
|
256
|
-
return this
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
// Informational message
|
|
260
|
-
info(msg: string) {
|
|
261
|
-
this.stop()
|
|
262
|
-
if (!quietMode) console.log(`${ICONS.info} ${msg}`)
|
|
263
|
-
return this
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
// Debug message (only if DEBUG=1 or DEBUG=true)
|
|
267
|
-
debug(msg: string) {
|
|
268
|
-
this.stop()
|
|
269
|
-
// DEBUG: Enable debug output (values: '1' or 'true')
|
|
270
|
-
const debugEnabled = process.env.DEBUG === '1' || process.env.DEBUG === 'true'
|
|
271
|
-
if (!quietMode && debugEnabled) {
|
|
272
|
-
console.log(`${ICONS.debug} ${chalk.dim(msg)}`)
|
|
273
|
-
}
|
|
274
|
-
return this
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
// Alias for done - explicit success indicator
|
|
278
|
-
success(msg: string, metrics?: OutputMetrics) {
|
|
279
|
-
return this.done(msg, metrics)
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
// Bulleted list
|
|
283
|
-
list(items: string[], options: { bullet?: string; indent?: number } = {}) {
|
|
284
|
-
this.stop()
|
|
285
|
-
if (quietMode) return this
|
|
286
|
-
const bullet = options.bullet || ICONS.bullet
|
|
287
|
-
const indent = ' '.repeat(options.indent || 0)
|
|
288
|
-
for (const item of items) {
|
|
289
|
-
console.log(`${indent}${bullet} ${item}`)
|
|
290
|
-
}
|
|
291
|
-
return this
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
// Simple table output
|
|
295
|
-
table(rows: Array<Record<string, string | number>>, options: { header?: boolean } = {}) {
|
|
296
|
-
this.stop()
|
|
297
|
-
if (quietMode || rows.length === 0) return this
|
|
298
|
-
|
|
299
|
-
const keys = Object.keys(rows[0])
|
|
300
|
-
const colWidths: Record<string, number> = {}
|
|
301
|
-
|
|
302
|
-
// Calculate column widths
|
|
303
|
-
for (const key of keys) {
|
|
304
|
-
colWidths[key] = key.length
|
|
305
|
-
for (const row of rows) {
|
|
306
|
-
const val = String(row[key] ?? '')
|
|
307
|
-
if (val.length > colWidths[key]) colWidths[key] = val.length
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Print header if requested
|
|
312
|
-
if (options.header !== false) {
|
|
313
|
-
const headerLine = keys.map((k) => k.padEnd(colWidths[k])).join(' ')
|
|
314
|
-
console.log(chalk.dim(headerLine))
|
|
315
|
-
console.log(chalk.dim('─'.repeat(headerLine.length)))
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Print rows
|
|
319
|
-
for (const row of rows) {
|
|
320
|
-
const line = keys.map((k) => String(row[k] ?? '').padEnd(colWidths[k])).join(' ')
|
|
321
|
-
console.log(line)
|
|
322
|
-
}
|
|
323
|
-
return this
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
// Boxed content
|
|
327
|
-
box(title: string, content: string) {
|
|
328
|
-
this.stop()
|
|
329
|
-
if (quietMode) return this
|
|
330
|
-
const lines = content.split('\n')
|
|
331
|
-
const maxLen = Math.max(title.length, ...lines.map((l) => l.length))
|
|
332
|
-
const border = '─'.repeat(maxLen + 2)
|
|
333
|
-
|
|
334
|
-
console.log(chalk.dim(`┌${border}┐`))
|
|
335
|
-
console.log(`${chalk.dim('│')} ${chalk.bold(title.padEnd(maxLen))} ${chalk.dim('│')}`)
|
|
336
|
-
console.log(chalk.dim(`├${border}┤`))
|
|
337
|
-
for (const line of lines) {
|
|
338
|
-
console.log(`${chalk.dim('│')} ${line.padEnd(maxLen)} ${chalk.dim('│')}`)
|
|
339
|
-
}
|
|
340
|
-
console.log(chalk.dim(`└${border}┘`))
|
|
341
|
-
return this
|
|
342
|
-
},
|
|
343
|
-
|
|
344
|
-
// Section header: bold title + underline
|
|
345
|
-
section(title: string) {
|
|
346
|
-
this.stop()
|
|
347
|
-
if (quietMode) return this
|
|
348
|
-
console.log(`\n${chalk.bold(title)}`)
|
|
349
|
-
console.log(chalk.dim('─'.repeat(title.length)))
|
|
350
|
-
return this
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
stop() {
|
|
354
|
-
if (interval) {
|
|
355
|
-
clearInterval(interval)
|
|
356
|
-
interval = null
|
|
357
|
-
clear()
|
|
358
|
-
}
|
|
359
|
-
return this
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
// Step counter: [3/7] Running tests...
|
|
363
|
-
step(current: number, total: number, msg: string) {
|
|
364
|
-
if (quietMode) return this
|
|
365
|
-
this.stop()
|
|
366
|
-
const counter = chalk.dim(`[${current}/${total}]`)
|
|
367
|
-
if (!process.stdout.isTTY) {
|
|
368
|
-
process.stdout.write(
|
|
369
|
-
`${branding.cli.spin(0, `${counter} ${truncate(msg, OUTPUT_LIMITS.STEP_MSG)}`)}\n`
|
|
370
|
-
)
|
|
371
|
-
return this
|
|
372
|
-
}
|
|
373
|
-
interval = setInterval(() => {
|
|
374
|
-
process.stdout.write(
|
|
375
|
-
`\r${branding.cli.spin(frame++, `${counter} ${truncate(msg, OUTPUT_LIMITS.STEP_MSG)}`)}`
|
|
376
|
-
)
|
|
377
|
-
}, SPEED)
|
|
378
|
-
return this
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
// Progress bar: [████░░░░] 50% Analyzing...
|
|
382
|
-
progress(current: number, total: number, msg?: string) {
|
|
383
|
-
if (quietMode) return this
|
|
384
|
-
this.stop()
|
|
385
|
-
const percent = Math.round((current / total) * 100)
|
|
386
|
-
const filled = Math.round(percent / 10)
|
|
387
|
-
const empty = 10 - filled
|
|
388
|
-
const bar = chalk.cyan('█'.repeat(filled)) + chalk.dim('░'.repeat(empty))
|
|
389
|
-
const text = msg ? ` ${truncate(msg, OUTPUT_LIMITS.PROGRESS_TEXT)}` : ''
|
|
390
|
-
if (!process.stdout.isTTY) {
|
|
391
|
-
process.stdout.write(`${branding.cli.spin(0, `[${bar}] ${percent}%${text}`)}\n`)
|
|
392
|
-
return this
|
|
393
|
-
}
|
|
394
|
-
interval = setInterval(() => {
|
|
395
|
-
process.stdout.write(`\r${branding.cli.spin(frame++, `[${bar}] ${percent}%${text}`)}`)
|
|
396
|
-
}, SPEED)
|
|
397
|
-
return this
|
|
398
|
-
},
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export type { ErrorCode, ErrorWithHint } from './error-messages'
|
|
402
|
-
export { createError, ERRORS, getError } from './error-messages'
|
|
403
|
-
export default out
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preserve Sections Utility
|
|
3
|
-
*
|
|
4
|
-
* Extracts and preserves user-customized sections during file regeneration.
|
|
5
|
-
* Users can mark sections with preserve markers to survive sync.
|
|
6
|
-
*
|
|
7
|
-
* Usage in CLAUDE.md or other context files:
|
|
8
|
-
* ```markdown
|
|
9
|
-
* <!-- prjct:preserve -->
|
|
10
|
-
* # My Custom Rules
|
|
11
|
-
* - Always use tabs
|
|
12
|
-
* - Prefer functional patterns
|
|
13
|
-
* <!-- /prjct:preserve -->
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @see PRJ-115
|
|
17
|
-
* @module utils/preserve-sections
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
export interface PreservedSection {
|
|
21
|
-
id: string
|
|
22
|
-
content: string
|
|
23
|
-
startIndex: number
|
|
24
|
-
endIndex: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Markers for preserved sections
|
|
28
|
-
const PRESERVE_START = '<!-- prjct:preserve -->'
|
|
29
|
-
const PRESERVE_END = '<!-- /prjct:preserve -->'
|
|
30
|
-
const PRESERVE_END_PATTERN = '<!-- /prjct:preserve -->'
|
|
31
|
-
|
|
32
|
-
// Named section markers (optional identifier) - create fresh regex each time
|
|
33
|
-
// Uses [\w-]+ to allow hyphens in section names (e.g., "custom-rules")
|
|
34
|
-
function createPreserveStartRegex(): RegExp {
|
|
35
|
-
return /<!-- prjct:preserve(?::([\w-]+))? -->/g
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Extract all preserved sections from content
|
|
40
|
-
*/
|
|
41
|
-
export function extractPreservedSections(content: string): PreservedSection[] {
|
|
42
|
-
const sections: PreservedSection[] = []
|
|
43
|
-
const regex = createPreserveStartRegex()
|
|
44
|
-
|
|
45
|
-
let match: RegExpExecArray | null
|
|
46
|
-
let sectionIndex = 0
|
|
47
|
-
|
|
48
|
-
while ((match = regex.exec(content)) !== null) {
|
|
49
|
-
const startIndex = match.index
|
|
50
|
-
const startTag = match[0]
|
|
51
|
-
const sectionId = match[1] || `section-${sectionIndex++}`
|
|
52
|
-
|
|
53
|
-
// Find the closing tag
|
|
54
|
-
const endTagStart = content.indexOf(PRESERVE_END_PATTERN, startIndex + startTag.length)
|
|
55
|
-
|
|
56
|
-
if (endTagStart === -1) {
|
|
57
|
-
// No closing tag found - skip this section
|
|
58
|
-
continue
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const endIndex = endTagStart + PRESERVE_END_PATTERN.length
|
|
62
|
-
|
|
63
|
-
// Extract the content between markers (including markers)
|
|
64
|
-
const fullContent = content.substring(startIndex, endIndex)
|
|
65
|
-
|
|
66
|
-
sections.push({
|
|
67
|
-
id: sectionId,
|
|
68
|
-
content: fullContent,
|
|
69
|
-
startIndex,
|
|
70
|
-
endIndex,
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return sections
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Extract the inner content of preserved sections (without markers)
|
|
79
|
-
*/
|
|
80
|
-
export function extractPreservedContent(content: string): string[] {
|
|
81
|
-
const sections = extractPreservedSections(content)
|
|
82
|
-
|
|
83
|
-
return sections.map((section) => {
|
|
84
|
-
// Remove the markers to get just the inner content
|
|
85
|
-
let inner = section.content
|
|
86
|
-
inner = inner.replace(createPreserveStartRegex(), '').replace(PRESERVE_END_PATTERN, '')
|
|
87
|
-
return inner.trim()
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Check if content has any preserved sections
|
|
93
|
-
*/
|
|
94
|
-
export function hasPreservedSections(content: string): boolean {
|
|
95
|
-
return content.includes(PRESERVE_START) || createPreserveStartRegex().test(content)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Merge preserved sections from old content into new content
|
|
100
|
-
*
|
|
101
|
-
* Strategy:
|
|
102
|
-
* 1. Extract preserved sections from old content
|
|
103
|
-
* 2. Append them to the end of new content
|
|
104
|
-
* 3. Ensure proper spacing
|
|
105
|
-
*
|
|
106
|
-
* @param newContent - Freshly generated content
|
|
107
|
-
* @param oldContent - Previous content with user customizations
|
|
108
|
-
* @returns Merged content with preserved sections
|
|
109
|
-
*/
|
|
110
|
-
export function mergePreservedSections(newContent: string, oldContent: string): string {
|
|
111
|
-
const preservedSections = extractPreservedSections(oldContent)
|
|
112
|
-
|
|
113
|
-
if (preservedSections.length === 0) {
|
|
114
|
-
return newContent
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Build the merged content
|
|
118
|
-
let merged = newContent.trimEnd()
|
|
119
|
-
|
|
120
|
-
// Add separator before preserved sections
|
|
121
|
-
merged += '\n\n---\n\n'
|
|
122
|
-
merged += '## Your Customizations\n\n'
|
|
123
|
-
merged += '_The sections below are preserved during sync. Edit freely._\n\n'
|
|
124
|
-
|
|
125
|
-
// Append each preserved section
|
|
126
|
-
for (const section of preservedSections) {
|
|
127
|
-
merged += section.content
|
|
128
|
-
merged += '\n\n'
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return `${merged.trimEnd()}\n`
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Create an empty preserve block for users to customize
|
|
136
|
-
*/
|
|
137
|
-
export function createEmptyPreserveBlock(title?: string): string {
|
|
138
|
-
const header = title ? `\n# ${title}\n` : '\n'
|
|
139
|
-
return `${PRESERVE_START}${header}<!-- Add your custom instructions here -->\n${PRESERVE_END}`
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Wrap content in preserve markers
|
|
144
|
-
*/
|
|
145
|
-
export function wrapInPreserveMarkers(content: string, id?: string): string {
|
|
146
|
-
const startTag = id ? `<!-- prjct:preserve:${id} -->` : PRESERVE_START
|
|
147
|
-
return `${startTag}\n${content}\n${PRESERVE_END}`
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Remove all preserved sections from content
|
|
152
|
-
* (Useful for getting the auto-generated portion only)
|
|
153
|
-
*/
|
|
154
|
-
export function stripPreservedSections(content: string): string {
|
|
155
|
-
const sections = extractPreservedSections(content)
|
|
156
|
-
|
|
157
|
-
if (sections.length === 0) {
|
|
158
|
-
return content
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Remove sections in reverse order to preserve indices
|
|
162
|
-
let result = content
|
|
163
|
-
for (let i = sections.length - 1; i >= 0; i--) {
|
|
164
|
-
const section = sections[i]
|
|
165
|
-
result = result.substring(0, section.startIndex) + result.substring(section.endIndex)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Clean up any resulting double newlines
|
|
169
|
-
result = result.replace(/\n{3,}/g, '\n\n')
|
|
170
|
-
|
|
171
|
-
return result.trim()
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Validate that all preserve blocks are properly closed
|
|
176
|
-
*/
|
|
177
|
-
export function validatePreserveBlocks(content: string): {
|
|
178
|
-
valid: boolean
|
|
179
|
-
errors: string[]
|
|
180
|
-
} {
|
|
181
|
-
const errors: string[] = []
|
|
182
|
-
|
|
183
|
-
const startMatches = content.match(/<!-- prjct:preserve(?::\w+)? -->/g) || []
|
|
184
|
-
const endMatches = content.match(/<!-- \/prjct:preserve -->/g) || []
|
|
185
|
-
|
|
186
|
-
if (startMatches.length !== endMatches.length) {
|
|
187
|
-
errors.push(
|
|
188
|
-
`Mismatched preserve markers: ${startMatches.length} opening, ${endMatches.length} closing`
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Check for nested blocks (not supported)
|
|
193
|
-
let depth = 0
|
|
194
|
-
let maxDepth = 0
|
|
195
|
-
const lines = content.split('\n')
|
|
196
|
-
|
|
197
|
-
for (let i = 0; i < lines.length; i++) {
|
|
198
|
-
const line = lines[i]
|
|
199
|
-
if (/<!-- prjct:preserve(?::\w+)? -->/.test(line)) {
|
|
200
|
-
depth++
|
|
201
|
-
maxDepth = Math.max(maxDepth, depth)
|
|
202
|
-
}
|
|
203
|
-
if (line.includes(PRESERVE_END_PATTERN)) {
|
|
204
|
-
depth--
|
|
205
|
-
}
|
|
206
|
-
if (depth > 1) {
|
|
207
|
-
errors.push(`Nested preserve blocks detected at line ${i + 1} (not supported)`)
|
|
208
|
-
}
|
|
209
|
-
if (depth < 0) {
|
|
210
|
-
errors.push(`Unexpected closing marker at line ${i + 1}`)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
valid: errors.length === 0,
|
|
216
|
-
errors,
|
|
217
|
-
}
|
|
218
|
-
}
|