agileflow 3.4.3 → 4.0.0-alpha.2
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 +238 -473
- package/README.md +22 -114
- package/bin/agileflow.js +15 -0
- package/bin/hooks/pre-bash.js +35 -0
- package/bin/hooks/pre-compact.js +34 -0
- package/bin/hooks/pre-edit.js +32 -0
- package/bin/hooks/pre-write.js +32 -0
- package/bin/hooks/session-start.js +42 -0
- package/bin/hooks/stop.js +34 -0
- package/content/plugins/ads/plugin.yaml +14 -0
- package/content/plugins/audit/plugin.yaml +14 -0
- package/content/plugins/core/hooks/babysit-mentor-injector.js +55 -0
- package/content/plugins/core/hooks/context-loader.js +169 -0
- package/content/plugins/core/hooks/damage-control-bash.js +78 -0
- package/content/plugins/core/hooks/damage-control-edit.js +76 -0
- package/content/plugins/core/hooks/damage-control-patterns.yaml +100 -0
- package/content/plugins/core/hooks/damage-control-write.js +72 -0
- package/content/plugins/core/hooks/pre-compact-state.js +90 -0
- package/content/plugins/core/hooks/session-welcome.js +19 -0
- package/content/plugins/core/plugin.yaml +82 -0
- package/content/plugins/core/skills/agileflow-adr/SKILL.md +179 -0
- package/content/plugins/core/skills/agileflow-babysit-mentor/SKILL.md +144 -0
- package/content/plugins/core/skills/agileflow-epic-planner/SKILL.md +179 -0
- package/content/plugins/core/skills/agileflow-status-updater/SKILL.md +132 -0
- package/content/plugins/core/skills/agileflow-story-writer/SKILL.md +200 -0
- package/content/plugins/council/plugin.yaml +14 -0
- package/content/plugins/seo/plugin.yaml +14 -0
- package/package.json +29 -49
- package/src/cli/commands/doctor.js +159 -0
- package/src/cli/commands/hook.js +80 -0
- package/src/cli/commands/setup.js +292 -0
- package/src/cli/commands/status.js +47 -0
- package/src/cli/commands/update.js +83 -0
- package/src/cli/index.js +73 -0
- package/src/cli/wizard/behaviors-picker.js +108 -0
- package/src/cli/wizard/ide-picker.js +57 -0
- package/src/cli/wizard/personalization.js +64 -0
- package/src/cli/wizard/plugin-picker.js +106 -0
- package/src/lib/hash.js +41 -0
- package/src/runtime/config/defaults.js +61 -0
- package/src/runtime/config/loader.js +117 -0
- package/src/runtime/config/schema.json +99 -0
- package/src/runtime/config/writer.js +55 -0
- package/src/runtime/hooks/aggregator.js +157 -0
- package/src/runtime/hooks/chain.js +93 -0
- package/src/runtime/hooks/logger.js +68 -0
- package/src/runtime/hooks/manifest-loader.js +228 -0
- package/src/runtime/hooks/orchestrator.js +322 -0
- package/src/runtime/ide/capabilities.js +111 -0
- package/src/runtime/ide/claude-code-settings.js +234 -0
- package/src/runtime/ide/claude-code-skills.js +202 -0
- package/src/runtime/installer/file-index.js +112 -0
- package/src/runtime/installer/install.js +329 -0
- package/src/runtime/installer/stash.js +61 -0
- package/src/runtime/installer/sync-engine.js +205 -0
- package/src/runtime/plugins/registry.js +132 -0
- package/src/runtime/plugins/resolver.js +138 -0
- package/src/runtime/plugins/validator.js +196 -0
- package/src/runtime/skills/validator.js +335 -0
- package/lib/README.md +0 -178
- package/lib/api-routes.js +0 -625
- package/lib/api-server.js +0 -278
- package/lib/cache-provider.js +0 -155
- package/lib/codebase-indexer.js +0 -819
- package/lib/colors.generated.js +0 -117
- package/lib/colors.js +0 -341
- package/lib/consent.js +0 -232
- package/lib/content-sanitizer.js +0 -464
- package/lib/correlation.js +0 -277
- package/lib/drivers/claude-driver.ts +0 -312
- package/lib/drivers/codex-driver.ts +0 -464
- package/lib/drivers/driver-manager.ts +0 -159
- package/lib/drivers/gemini-driver.ts +0 -498
- package/lib/drivers/index.ts +0 -17
- package/lib/error-codes.js +0 -590
- package/lib/errors.js +0 -670
- package/lib/feature-flags.js +0 -171
- package/lib/feedback.js +0 -595
- package/lib/file-cache.js +0 -541
- package/lib/flag-detection.js +0 -344
- package/lib/format-error.js +0 -156
- package/lib/gate-runner.js +0 -282
- package/lib/generator-factory.js +0 -333
- package/lib/git-operations.js +0 -266
- package/lib/lazy-require.js +0 -59
- package/lib/lock-file.js +0 -144
- package/lib/logger.js +0 -106
- package/lib/merge-operations.js +0 -1006
- package/lib/path-resolver.js +0 -544
- package/lib/path-utils.js +0 -49
- package/lib/paths.js +0 -291
- package/lib/placeholder-registry.js +0 -822
- package/lib/process-executor.js +0 -214
- package/lib/progress.js +0 -334
- package/lib/protocol/driver.ts +0 -354
- package/lib/protocol/index.ts +0 -12
- package/lib/protocol/ir.ts +0 -271
- package/lib/registry-cache.js +0 -80
- package/lib/registry-di.js +0 -358
- package/lib/result-schema.js +0 -363
- package/lib/result.js +0 -210
- package/lib/session-display.js +0 -331
- package/lib/session-operations.js +0 -611
- package/lib/session-registry.js +0 -484
- package/lib/session-state-machine.js +0 -465
- package/lib/session-switching.js +0 -191
- package/lib/skill-loader.js +0 -213
- package/lib/smart-json-file.js +0 -682
- package/lib/state-machine.js +0 -286
- package/lib/table-formatter.js +0 -519
- package/lib/template-loader.js +0 -143
- package/lib/transient-status.js +0 -374
- package/lib/ui-manager.js +0 -612
- package/lib/validate-args.js +0 -213
- package/lib/validate-commands.js +0 -308
- package/lib/validate-names.js +0 -143
- package/lib/validate-paths.js +0 -434
- package/lib/validate.js +0 -134
- package/lib/worktree-operations.js +0 -201
- package/lib/yaml-utils.js +0 -164
- package/scripts/README.md +0 -267
- package/scripts/af +0 -34
- package/scripts/agent-loop.js +0 -879
- package/scripts/agileflow-configure.js +0 -368
- package/scripts/agileflow-statusline.sh +0 -857
- package/scripts/agileflow-welcome.js +0 -2246
- package/scripts/api-server-runner.js +0 -177
- package/scripts/archive-completed-stories.sh +0 -308
- package/scripts/auto-self-improve.js +0 -326
- package/scripts/automation-run-due.js +0 -128
- package/scripts/babysit-clear-restore.js +0 -154
- package/scripts/babysit-context-restore.js +0 -89
- package/scripts/backfill-ideation-status.js +0 -128
- package/scripts/batch-pmap-loop.js +0 -551
- package/scripts/check-sessions.js +0 -116
- package/scripts/check-update.js +0 -282
- package/scripts/ci-summary.js +0 -294
- package/scripts/claude-smart.sh +0 -85
- package/scripts/claude-tmux.sh +0 -737
- package/scripts/claude-watchdog.sh +0 -225
- package/scripts/clear-active-command.js +0 -48
- package/scripts/compress-status.sh +0 -116
- package/scripts/context-loader.js +0 -310
- package/scripts/damage-control/bash-tool-damage-control.js +0 -22
- package/scripts/damage-control/edit-tool-damage-control.js +0 -19
- package/scripts/damage-control/patterns.yaml +0 -227
- package/scripts/damage-control/write-tool-damage-control.js +0 -19
- package/scripts/damage-control-bash.js +0 -51
- package/scripts/damage-control-edit.js +0 -48
- package/scripts/damage-control-multi-agent.js +0 -231
- package/scripts/damage-control-write.js +0 -48
- package/scripts/dependency-check.js +0 -311
- package/scripts/document-repl.js +0 -793
- package/scripts/expertise-metrics.sh +0 -264
- package/scripts/generate-all.sh +0 -77
- package/scripts/generate-colors.js +0 -314
- package/scripts/generators/agent-registry.js +0 -183
- package/scripts/generators/command-registry.js +0 -166
- package/scripts/generators/index.js +0 -85
- package/scripts/generators/inject-babysit.js +0 -191
- package/scripts/generators/inject-help.js +0 -125
- package/scripts/generators/inject-readme.js +0 -166
- package/scripts/generators/skill-registry.js +0 -188
- package/scripts/get-env.js +0 -225
- package/scripts/init.sh +0 -76
- package/scripts/lib/README-portable-tasks.md +0 -424
- package/scripts/lib/ac-test-matcher.js +0 -452
- package/scripts/lib/audit-cleanup.js +0 -250
- package/scripts/lib/audit-registry.js +0 -340
- package/scripts/lib/automation-registry.js +0 -544
- package/scripts/lib/automation-runner.js +0 -476
- package/scripts/lib/browser-qa-evidence.js +0 -409
- package/scripts/lib/browser-qa-status.js +0 -192
- package/scripts/lib/bus-utils.js +0 -473
- package/scripts/lib/colors.generated.sh +0 -82
- package/scripts/lib/colors.sh +0 -46
- package/scripts/lib/command-prereqs.js +0 -280
- package/scripts/lib/concurrency-limiter.js +0 -511
- package/scripts/lib/configure-detect.js +0 -596
- package/scripts/lib/configure-features.js +0 -1927
- package/scripts/lib/configure-repair.js +0 -327
- package/scripts/lib/configure-utils.js +0 -114
- package/scripts/lib/context-formatter.js +0 -1158
- package/scripts/lib/context-loader.js +0 -840
- package/scripts/lib/counter.js +0 -103
- package/scripts/lib/damage-control-utils.js +0 -619
- package/scripts/lib/feature-catalog.js +0 -332
- package/scripts/lib/file-lock.js +0 -392
- package/scripts/lib/file-tracking.js +0 -735
- package/scripts/lib/frontmatter-parser.js +0 -133
- package/scripts/lib/gate-enforcer.js +0 -295
- package/scripts/lib/hook-metrics.js +0 -324
- package/scripts/lib/ideation-index.js +0 -1205
- package/scripts/lib/json-utils.sh +0 -162
- package/scripts/lib/lifecycle-detector.js +0 -125
- package/scripts/lib/model-profiles.js +0 -118
- package/scripts/lib/portable-tasks-cli.js +0 -274
- package/scripts/lib/portable-tasks.js +0 -479
- package/scripts/lib/process-cleanup.js +0 -527
- package/scripts/lib/quality-gates.js +0 -788
- package/scripts/lib/scale-detector.js +0 -396
- package/scripts/lib/sessionRegistry.js +0 -678
- package/scripts/lib/signal-detectors.js +0 -867
- package/scripts/lib/skill-catalog.js +0 -557
- package/scripts/lib/skill-recommender.js +0 -311
- package/scripts/lib/state-migrator.js +0 -353
- package/scripts/lib/status-task-bridge.js +0 -522
- package/scripts/lib/status-writer.js +0 -255
- package/scripts/lib/story-claiming.js +0 -704
- package/scripts/lib/story-state-machine.js +0 -437
- package/scripts/lib/sync-ideation-status.js +0 -291
- package/scripts/lib/task-registry-cache.js +0 -490
- package/scripts/lib/task-registry.js +0 -1191
- package/scripts/lib/task-sync.js +0 -230
- package/scripts/lib/tdd-phase-manager.js +0 -455
- package/scripts/lib/team-events.js +0 -510
- package/scripts/lib/tmux-audit-monitor.js +0 -612
- package/scripts/lib/tmux-group-colors.js +0 -113
- package/scripts/lib/tool-registry.yaml +0 -241
- package/scripts/lib/tool-shed.js +0 -441
- package/scripts/lib/validation-registry.js +0 -177
- package/scripts/messaging-bridge.js +0 -561
- package/scripts/migrate-ideation-index.js +0 -553
- package/scripts/native-team-observer.js +0 -219
- package/scripts/obtain-context.js +0 -272
- package/scripts/pre-push-check.sh +0 -46
- package/scripts/precompact-context.sh +0 -306
- package/scripts/query-codebase.js +0 -543
- package/scripts/ralph-loop.js +0 -1278
- package/scripts/resume-session.sh +0 -121
- package/scripts/screenshot-verifier.js +0 -215
- package/scripts/session-boundary.js +0 -138
- package/scripts/session-coordinator.sh +0 -232
- package/scripts/session-manager.js +0 -546
- package/scripts/smart-detect.js +0 -449
- package/scripts/spawn-audit-sessions.js +0 -877
- package/scripts/spawn-parallel.js +0 -751
- package/scripts/strip-ai-attribution.js +0 -63
- package/scripts/task-completed-gate.js +0 -237
- package/scripts/team-manager.js +0 -596
- package/scripts/team-status-display.js +0 -200
- package/scripts/teammate-idle-gate.js +0 -237
- package/scripts/test-session-boundary.js +0 -80
- package/scripts/tmux-close-windows.sh +0 -180
- package/scripts/tmux-restore-window.sh +0 -67
- package/scripts/tmux-save-closed-window.sh +0 -35
- package/scripts/tui/App.js +0 -151
- package/scripts/tui/Dashboard.js +0 -277
- package/scripts/tui/blessed/data/watcher.js +0 -180
- package/scripts/tui/blessed/index.js +0 -244
- package/scripts/tui/blessed/panels/output.js +0 -101
- package/scripts/tui/blessed/panels/sessions.js +0 -150
- package/scripts/tui/blessed/panels/trace.js +0 -97
- package/scripts/tui/blessed/ui/help.js +0 -77
- package/scripts/tui/blessed/ui/screen.js +0 -52
- package/scripts/tui/blessed/ui/statusbar.js +0 -47
- package/scripts/tui/blessed/ui/tabbar.js +0 -99
- package/scripts/tui/index.js +0 -70
- package/scripts/tui/lib/crashRecovery.js +0 -304
- package/scripts/tui/lib/eventStream.js +0 -309
- package/scripts/tui/lib/keyboard.js +0 -261
- package/scripts/tui/lib/loopControl.js +0 -371
- package/scripts/tui/panels/OutputPanel.js +0 -240
- package/scripts/tui/panels/SessionPanel.js +0 -170
- package/scripts/tui/panels/TracePanel.js +0 -298
- package/scripts/tui/simple-tui.js +0 -510
- package/scripts/validate-expertise.sh +0 -263
- package/scripts/validate-tokens.sh +0 -73
- package/scripts/validators/README.md +0 -143
- package/scripts/validators/component-validator.js +0 -239
- package/scripts/validators/json-schema-validator.js +0 -186
- package/scripts/validators/markdown-validator.js +0 -152
- package/scripts/validators/migration-validator.js +0 -129
- package/scripts/validators/security-validator.js +0 -380
- package/scripts/validators/story-format-validator.js +0 -197
- package/scripts/validators/test-result-validator.js +0 -114
- package/scripts/validators/workflow-validator.js +0 -247
- package/scripts/welcome-deferred.js +0 -437
- package/scripts/worktree-create.sh +0 -111
- package/src/core/agents/a11y-analyzer-aria.md +0 -155
- package/src/core/agents/a11y-analyzer-forms.md +0 -162
- package/src/core/agents/a11y-analyzer-keyboard.md +0 -175
- package/src/core/agents/a11y-analyzer-semantic.md +0 -153
- package/src/core/agents/a11y-analyzer-visual.md +0 -158
- package/src/core/agents/a11y-consensus.md +0 -248
- package/src/core/agents/accessibility.md +0 -515
- package/src/core/agents/adr-writer.md +0 -463
- package/src/core/agents/ads-audit-budget.md +0 -181
- package/src/core/agents/ads-audit-compliance.md +0 -169
- package/src/core/agents/ads-audit-creative.md +0 -164
- package/src/core/agents/ads-audit-google.md +0 -226
- package/src/core/agents/ads-audit-meta.md +0 -183
- package/src/core/agents/ads-audit-tracking.md +0 -197
- package/src/core/agents/ads-consensus.md +0 -396
- package/src/core/agents/ads-generate.md +0 -145
- package/src/core/agents/ads-performance-tracker.md +0 -197
- package/src/core/agents/analytics.md +0 -617
- package/src/core/agents/api-quality-analyzer-conventions.md +0 -148
- package/src/core/agents/api-quality-analyzer-docs.md +0 -176
- package/src/core/agents/api-quality-analyzer-errors.md +0 -183
- package/src/core/agents/api-quality-analyzer-pagination.md +0 -171
- package/src/core/agents/api-quality-analyzer-versioning.md +0 -143
- package/src/core/agents/api-quality-consensus.md +0 -214
- package/src/core/agents/api-validator.md +0 -183
- package/src/core/agents/api.md +0 -665
- package/src/core/agents/arch-analyzer-circular.md +0 -148
- package/src/core/agents/arch-analyzer-complexity.md +0 -171
- package/src/core/agents/arch-analyzer-coupling.md +0 -146
- package/src/core/agents/arch-analyzer-layering.md +0 -151
- package/src/core/agents/arch-analyzer-patterns.md +0 -162
- package/src/core/agents/arch-consensus.md +0 -227
- package/src/core/agents/brainstorm-analyzer-features.md +0 -169
- package/src/core/agents/brainstorm-analyzer-growth.md +0 -161
- package/src/core/agents/brainstorm-analyzer-integration.md +0 -172
- package/src/core/agents/brainstorm-analyzer-market.md +0 -147
- package/src/core/agents/brainstorm-analyzer-ux.md +0 -167
- package/src/core/agents/brainstorm-consensus.md +0 -237
- package/src/core/agents/browser-qa.md +0 -328
- package/src/core/agents/ci.md +0 -511
- package/src/core/agents/code-reviewer.md +0 -288
- package/src/core/agents/codebase-query.md +0 -266
- package/src/core/agents/completeness-analyzer-api.md +0 -190
- package/src/core/agents/completeness-analyzer-conditional.md +0 -201
- package/src/core/agents/completeness-analyzer-handlers.md +0 -159
- package/src/core/agents/completeness-analyzer-imports.md +0 -159
- package/src/core/agents/completeness-analyzer-routes.md +0 -182
- package/src/core/agents/completeness-analyzer-state.md +0 -188
- package/src/core/agents/completeness-analyzer-stubs.md +0 -198
- package/src/core/agents/completeness-consensus.md +0 -286
- package/src/core/agents/compliance.md +0 -509
- package/src/core/agents/council-advocate.md +0 -206
- package/src/core/agents/council-analyst.md +0 -252
- package/src/core/agents/council-optimist.md +0 -170
- package/src/core/agents/database.md +0 -601
- package/src/core/agents/datamigration.md +0 -699
- package/src/core/agents/design.md +0 -525
- package/src/core/agents/devops.md +0 -720
- package/src/core/agents/documentation.md +0 -504
- package/src/core/agents/epic-planner.md +0 -480
- package/src/core/agents/error-analyzer.md +0 -201
- package/src/core/agents/integrations.md +0 -603
- package/src/core/agents/legal-analyzer-a11y.md +0 -110
- package/src/core/agents/legal-analyzer-ai.md +0 -117
- package/src/core/agents/legal-analyzer-consumer.md +0 -108
- package/src/core/agents/legal-analyzer-content.md +0 -113
- package/src/core/agents/legal-analyzer-international.md +0 -115
- package/src/core/agents/legal-analyzer-licensing.md +0 -115
- package/src/core/agents/legal-analyzer-privacy.md +0 -108
- package/src/core/agents/legal-analyzer-security.md +0 -112
- package/src/core/agents/legal-analyzer-terms.md +0 -111
- package/src/core/agents/legal-consensus.md +0 -242
- package/src/core/agents/logic-analyzer-edge.md +0 -170
- package/src/core/agents/logic-analyzer-flow.md +0 -253
- package/src/core/agents/logic-analyzer-invariant.md +0 -206
- package/src/core/agents/logic-analyzer-race.md +0 -266
- package/src/core/agents/logic-analyzer-type.md +0 -217
- package/src/core/agents/logic-consensus.md +0 -253
- package/src/core/agents/mentor.md +0 -654
- package/src/core/agents/mobile.md +0 -501
- package/src/core/agents/monitoring.md +0 -537
- package/src/core/agents/multi-expert.md +0 -311
- package/src/core/agents/orchestrator.md +0 -749
- package/src/core/agents/perf-analyzer-assets.md +0 -174
- package/src/core/agents/perf-analyzer-bundle.md +0 -165
- package/src/core/agents/perf-analyzer-caching.md +0 -160
- package/src/core/agents/perf-analyzer-compute.md +0 -165
- package/src/core/agents/perf-analyzer-memory.md +0 -182
- package/src/core/agents/perf-analyzer-network.md +0 -157
- package/src/core/agents/perf-analyzer-queries.md +0 -155
- package/src/core/agents/perf-analyzer-rendering.md +0 -156
- package/src/core/agents/perf-consensus.md +0 -280
- package/src/core/agents/performance.md +0 -492
- package/src/core/agents/product.md +0 -535
- package/src/core/agents/qa.md +0 -765
- package/src/core/agents/readme-updater.md +0 -579
- package/src/core/agents/refactor.md +0 -558
- package/src/core/agents/research.md +0 -453
- package/src/core/agents/rlm-subcore.md +0 -207
- package/src/core/agents/schema-validator.md +0 -454
- package/src/core/agents/security-analyzer-api.md +0 -199
- package/src/core/agents/security-analyzer-auth.md +0 -160
- package/src/core/agents/security-analyzer-authz.md +0 -168
- package/src/core/agents/security-analyzer-deps.md +0 -147
- package/src/core/agents/security-analyzer-infra.md +0 -176
- package/src/core/agents/security-analyzer-injection.md +0 -148
- package/src/core/agents/security-analyzer-input.md +0 -191
- package/src/core/agents/security-analyzer-secrets.md +0 -175
- package/src/core/agents/security-consensus.md +0 -276
- package/src/core/agents/security.md +0 -486
- package/src/core/agents/seo-analyzer-content.md +0 -167
- package/src/core/agents/seo-analyzer-images.md +0 -187
- package/src/core/agents/seo-analyzer-performance.md +0 -206
- package/src/core/agents/seo-analyzer-schema.md +0 -176
- package/src/core/agents/seo-analyzer-sitemap.md +0 -172
- package/src/core/agents/seo-analyzer-technical.md +0 -144
- package/src/core/agents/seo-consensus.md +0 -289
- package/src/core/agents/team-coordinator.md +0 -333
- package/src/core/agents/team-lead.md +0 -171
- package/src/core/agents/test-analyzer-assertions.md +0 -181
- package/src/core/agents/test-analyzer-coverage.md +0 -183
- package/src/core/agents/test-analyzer-fragility.md +0 -185
- package/src/core/agents/test-analyzer-integration.md +0 -155
- package/src/core/agents/test-analyzer-maintenance.md +0 -173
- package/src/core/agents/test-analyzer-mocking.md +0 -178
- package/src/core/agents/test-analyzer-patterns.md +0 -189
- package/src/core/agents/test-analyzer-structure.md +0 -177
- package/src/core/agents/test-consensus.md +0 -294
- package/src/core/agents/testing.md +0 -527
- package/src/core/agents/ui-validator.md +0 -331
- package/src/core/agents/ui.md +0 -1227
- package/src/core/commands/adr/list.md +0 -191
- package/src/core/commands/adr/update.md +0 -258
- package/src/core/commands/adr/view.md +0 -274
- package/src/core/commands/adr.md +0 -394
- package/src/core/commands/ads/audit.md +0 -453
- package/src/core/commands/ads/budget.md +0 -97
- package/src/core/commands/ads/competitor.md +0 -112
- package/src/core/commands/ads/creative.md +0 -85
- package/src/core/commands/ads/generate.md +0 -238
- package/src/core/commands/ads/google.md +0 -112
- package/src/core/commands/ads/health.md +0 -327
- package/src/core/commands/ads/landing.md +0 -119
- package/src/core/commands/ads/linkedin.md +0 -112
- package/src/core/commands/ads/meta.md +0 -91
- package/src/core/commands/ads/microsoft.md +0 -115
- package/src/core/commands/ads/plan.md +0 -321
- package/src/core/commands/ads/test-plan.md +0 -317
- package/src/core/commands/ads/tiktok.md +0 -129
- package/src/core/commands/ads/track.md +0 -288
- package/src/core/commands/ads/youtube.md +0 -124
- package/src/core/commands/ads.md +0 -140
- package/src/core/commands/agent.md +0 -256
- package/src/core/commands/api.md +0 -267
- package/src/core/commands/assign.md +0 -369
- package/src/core/commands/audit.md +0 -531
- package/src/core/commands/auto.md +0 -556
- package/src/core/commands/automate.md +0 -415
- package/src/core/commands/babysit.md +0 -643
- package/src/core/commands/baseline.md +0 -743
- package/src/core/commands/batch.md +0 -551
- package/src/core/commands/blockers.md +0 -602
- package/src/core/commands/board.md +0 -509
- package/src/core/commands/browser-qa.md +0 -240
- package/src/core/commands/changelog.md +0 -582
- package/src/core/commands/choose.md +0 -430
- package/src/core/commands/ci.md +0 -330
- package/src/core/commands/code/accessibility.md +0 -363
- package/src/core/commands/code/api.md +0 -313
- package/src/core/commands/code/architecture.md +0 -313
- package/src/core/commands/code/completeness.md +0 -519
- package/src/core/commands/code/legal.md +0 -509
- package/src/core/commands/code/logic.md +0 -432
- package/src/core/commands/code/performance.md +0 -506
- package/src/core/commands/code/security.md +0 -509
- package/src/core/commands/code/test.md +0 -505
- package/src/core/commands/compress.md +0 -408
- package/src/core/commands/configure.md +0 -1159
- package/src/core/commands/context/export.md +0 -296
- package/src/core/commands/context/full.md +0 -353
- package/src/core/commands/context/note.md +0 -380
- package/src/core/commands/council.md +0 -592
- package/src/core/commands/debt.md +0 -491
- package/src/core/commands/deploy.md +0 -864
- package/src/core/commands/deps.md +0 -728
- package/src/core/commands/diagnose.md +0 -404
- package/src/core/commands/docs.md +0 -469
- package/src/core/commands/epic/edit.md +0 -213
- package/src/core/commands/epic/list.md +0 -190
- package/src/core/commands/epic/view.md +0 -267
- package/src/core/commands/epic.md +0 -477
- package/src/core/commands/export.md +0 -238
- package/src/core/commands/feedback.md +0 -603
- package/src/core/commands/handoff.md +0 -386
- package/src/core/commands/help.md +0 -194
- package/src/core/commands/ideate/brief.md +0 -363
- package/src/core/commands/ideate/discover.md +0 -399
- package/src/core/commands/ideate/features.md +0 -497
- package/src/core/commands/ideate/history.md +0 -403
- package/src/core/commands/ideate/new.md +0 -900
- package/src/core/commands/impact.md +0 -407
- package/src/core/commands/install.md +0 -529
- package/src/core/commands/learn/explain.md +0 -118
- package/src/core/commands/learn/glossary.md +0 -135
- package/src/core/commands/learn/patterns.md +0 -138
- package/src/core/commands/learn/tour.md +0 -126
- package/src/core/commands/maintain.md +0 -558
- package/src/core/commands/metrics.md +0 -844
- package/src/core/commands/migrate/codemods.md +0 -151
- package/src/core/commands/migrate/plan.md +0 -131
- package/src/core/commands/migrate/scan.md +0 -114
- package/src/core/commands/migrate/validate.md +0 -119
- package/src/core/commands/multi-expert.md +0 -447
- package/src/core/commands/packages.md +0 -535
- package/src/core/commands/pr.md +0 -337
- package/src/core/commands/readme-sync.md +0 -329
- package/src/core/commands/research/analyze.md +0 -798
- package/src/core/commands/research/ask.md +0 -864
- package/src/core/commands/research/import.md +0 -1025
- package/src/core/commands/research/list.md +0 -273
- package/src/core/commands/research/synthesize.md +0 -928
- package/src/core/commands/research/view.md +0 -323
- package/src/core/commands/retro.md +0 -795
- package/src/core/commands/review.md +0 -694
- package/src/core/commands/rlm.md +0 -446
- package/src/core/commands/roadmap/analyze.md +0 -400
- package/src/core/commands/rpi.md +0 -633
- package/src/core/commands/seo/audit.md +0 -444
- package/src/core/commands/seo/competitor.md +0 -174
- package/src/core/commands/seo/content.md +0 -107
- package/src/core/commands/seo/geo.md +0 -229
- package/src/core/commands/seo/hreflang.md +0 -140
- package/src/core/commands/seo/images.md +0 -96
- package/src/core/commands/seo/page.md +0 -198
- package/src/core/commands/seo/plan.md +0 -163
- package/src/core/commands/seo/programmatic.md +0 -131
- package/src/core/commands/seo/references/cwv-thresholds.md +0 -64
- package/src/core/commands/seo/references/eeat-framework.md +0 -110
- package/src/core/commands/seo/references/quality-gates.md +0 -91
- package/src/core/commands/seo/references/schema-types.md +0 -102
- package/src/core/commands/seo/schema.md +0 -183
- package/src/core/commands/seo/sitemap.md +0 -97
- package/src/core/commands/seo/technical.md +0 -100
- package/src/core/commands/seo.md +0 -107
- package/src/core/commands/session/cleanup.md +0 -452
- package/src/core/commands/session/end.md +0 -865
- package/src/core/commands/session/history.md +0 -293
- package/src/core/commands/session/init.md +0 -210
- package/src/core/commands/session/new.md +0 -827
- package/src/core/commands/session/resume.md +0 -291
- package/src/core/commands/session/spawn.md +0 -205
- package/src/core/commands/session/status.md +0 -274
- package/src/core/commands/skill/list.md +0 -139
- package/src/core/commands/skill/recommend.md +0 -216
- package/src/core/commands/sprint.md +0 -714
- package/src/core/commands/status/undo.md +0 -191
- package/src/core/commands/status.md +0 -423
- package/src/core/commands/story/edit.md +0 -204
- package/src/core/commands/story/list.md +0 -199
- package/src/core/commands/story/view.md +0 -312
- package/src/core/commands/story-validate.md +0 -491
- package/src/core/commands/story.md +0 -465
- package/src/core/commands/tdd-next.md +0 -238
- package/src/core/commands/tdd.md +0 -211
- package/src/core/commands/team/guide.md +0 -688
- package/src/core/commands/team/list.md +0 -59
- package/src/core/commands/team/start.md +0 -130
- package/src/core/commands/team/status.md +0 -66
- package/src/core/commands/team/stop.md +0 -78
- package/src/core/commands/template.md +0 -644
- package/src/core/commands/tests.md +0 -731
- package/src/core/commands/update.md +0 -591
- package/src/core/commands/validate-expertise.md +0 -305
- package/src/core/commands/velocity.md +0 -630
- package/src/core/commands/verify.md +0 -534
- package/src/core/commands/whats-new.md +0 -201
- package/src/core/commands/workflow.md +0 -449
- package/src/core/council/sessions/.gitkeep +0 -0
- package/src/core/council/shared_reasoning.template.md +0 -106
- package/src/core/experts/README.md +0 -236
- package/src/core/experts/_core-expertise.yaml +0 -105
- package/src/core/experts/accessibility/expertise.yaml +0 -115
- package/src/core/experts/accessibility/question.md +0 -41
- package/src/core/experts/accessibility/self-improve.md +0 -45
- package/src/core/experts/accessibility/workflow.md +0 -59
- package/src/core/experts/adr-writer/expertise.yaml +0 -138
- package/src/core/experts/adr-writer/question.md +0 -56
- package/src/core/experts/adr-writer/self-improve.md +0 -106
- package/src/core/experts/adr-writer/workflow.md +0 -184
- package/src/core/experts/analytics/expertise.yaml +0 -119
- package/src/core/experts/analytics/question.md +0 -74
- package/src/core/experts/analytics/self-improve.md +0 -163
- package/src/core/experts/analytics/workflow.md +0 -272
- package/src/core/experts/api/expertise.yaml +0 -124
- package/src/core/experts/api/question.md +0 -74
- package/src/core/experts/api/self-improve.md +0 -122
- package/src/core/experts/api/workflow.md +0 -248
- package/src/core/experts/ci/expertise.yaml +0 -106
- package/src/core/experts/ci/question.md +0 -69
- package/src/core/experts/ci/self-improve.md +0 -100
- package/src/core/experts/ci/workflow.md +0 -145
- package/src/core/experts/codebase-query/expertise.yaml +0 -121
- package/src/core/experts/codebase-query/question.md +0 -73
- package/src/core/experts/codebase-query/self-improve.md +0 -105
- package/src/core/experts/compliance/expertise.yaml +0 -101
- package/src/core/experts/compliance/question.md +0 -56
- package/src/core/experts/compliance/self-improve.md +0 -106
- package/src/core/experts/compliance/workflow.md +0 -184
- package/src/core/experts/database/expertise.yaml +0 -109
- package/src/core/experts/database/question.md +0 -74
- package/src/core/experts/database/self-improve.md +0 -121
- package/src/core/experts/database/workflow.md +0 -234
- package/src/core/experts/datamigration/expertise.yaml +0 -141
- package/src/core/experts/datamigration/question.md +0 -56
- package/src/core/experts/datamigration/self-improve.md +0 -106
- package/src/core/experts/datamigration/workflow.md +0 -184
- package/src/core/experts/design/expertise.yaml +0 -116
- package/src/core/experts/design/question.md +0 -56
- package/src/core/experts/design/self-improve.md +0 -106
- package/src/core/experts/design/workflow.md +0 -184
- package/src/core/experts/devops/expertise.yaml +0 -116
- package/src/core/experts/devops/question.md +0 -68
- package/src/core/experts/devops/self-improve.md +0 -102
- package/src/core/experts/devops/workflow.md +0 -142
- package/src/core/experts/documentation/expertise.yaml +0 -126
- package/src/core/experts/documentation/question.md +0 -41
- package/src/core/experts/documentation/self-improve.md +0 -45
- package/src/core/experts/documentation/workflow.md +0 -55
- package/src/core/experts/epic-planner/expertise.yaml +0 -144
- package/src/core/experts/epic-planner/question.md +0 -56
- package/src/core/experts/epic-planner/self-improve.md +0 -106
- package/src/core/experts/epic-planner/workflow.md +0 -184
- package/src/core/experts/integrations/expertise.yaml +0 -113
- package/src/core/experts/integrations/question.md +0 -74
- package/src/core/experts/integrations/self-improve.md +0 -151
- package/src/core/experts/integrations/workflow.md +0 -246
- package/src/core/experts/mentor/expertise.yaml +0 -125
- package/src/core/experts/mentor/question.md +0 -56
- package/src/core/experts/mentor/self-improve.md +0 -106
- package/src/core/experts/mentor/workflow.md +0 -184
- package/src/core/experts/mobile/expertise.yaml +0 -136
- package/src/core/experts/mobile/question.md +0 -72
- package/src/core/experts/mobile/self-improve.md +0 -140
- package/src/core/experts/mobile/workflow.md +0 -240
- package/src/core/experts/monitoring/expertise.yaml +0 -132
- package/src/core/experts/monitoring/question.md +0 -76
- package/src/core/experts/monitoring/self-improve.md +0 -150
- package/src/core/experts/monitoring/workflow.md +0 -264
- package/src/core/experts/performance/expertise.yaml +0 -68
- package/src/core/experts/performance/question.md +0 -41
- package/src/core/experts/performance/self-improve.md +0 -45
- package/src/core/experts/performance/workflow.md +0 -61
- package/src/core/experts/product/expertise.yaml +0 -143
- package/src/core/experts/product/question.md +0 -56
- package/src/core/experts/product/self-improve.md +0 -106
- package/src/core/experts/product/workflow.md +0 -184
- package/src/core/experts/qa/expertise.yaml +0 -110
- package/src/core/experts/qa/question.md +0 -56
- package/src/core/experts/qa/self-improve.md +0 -106
- package/src/core/experts/qa/workflow.md +0 -184
- package/src/core/experts/readme-updater/expertise.yaml +0 -141
- package/src/core/experts/readme-updater/question.md +0 -56
- package/src/core/experts/readme-updater/self-improve.md +0 -106
- package/src/core/experts/readme-updater/workflow.md +0 -184
- package/src/core/experts/refactor/expertise.yaml +0 -135
- package/src/core/experts/refactor/question.md +0 -41
- package/src/core/experts/refactor/self-improve.md +0 -45
- package/src/core/experts/refactor/workflow.md +0 -57
- package/src/core/experts/research/expertise.yaml +0 -143
- package/src/core/experts/research/question.md +0 -56
- package/src/core/experts/research/self-improve.md +0 -106
- package/src/core/experts/research/workflow.md +0 -184
- package/src/core/experts/security/expertise.yaml +0 -117
- package/src/core/experts/security/question.md +0 -77
- package/src/core/experts/security/self-improve.md +0 -102
- package/src/core/experts/security/workflow.md +0 -152
- package/src/core/experts/templates/expertise-template.yaml +0 -67
- package/src/core/experts/templates/question-template.md +0 -56
- package/src/core/experts/templates/self-improve-template.md +0 -106
- package/src/core/experts/templates/workflow-template.md +0 -184
- package/src/core/experts/testing/expertise.yaml +0 -112
- package/src/core/experts/testing/question.md +0 -68
- package/src/core/experts/testing/self-improve.md +0 -102
- package/src/core/experts/testing/workflow.md +0 -143
- package/src/core/experts/ui/expertise.yaml +0 -133
- package/src/core/experts/ui/question.md +0 -74
- package/src/core/experts/ui/self-improve.md +0 -122
- package/src/core/experts/ui/workflow.md +0 -262
- package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +0 -424
- package/src/core/knowledge/ads/ad-optimization-logic.md +0 -590
- package/src/core/knowledge/ads/ad-technical-specifications.md +0 -385
- package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +0 -506
- package/src/core/knowledge/ads/paid-advertising-research-2026.md +0 -445
- package/src/core/profiles/COMPARISON.md +0 -170
- package/src/core/profiles/README.md +0 -178
- package/src/core/profiles/claude-code.yaml +0 -111
- package/src/core/profiles/codex.yaml +0 -103
- package/src/core/profiles/cursor.yaml +0 -134
- package/src/core/profiles/examples.js +0 -250
- package/src/core/profiles/loader.js +0 -235
- package/src/core/profiles/windsurf.yaml +0 -159
- package/src/core/skills/_learnings/README.md +0 -91
- package/src/core/skills/_learnings/_template.yaml +0 -106
- package/src/core/skills/_learnings/code-review.yaml +0 -118
- package/src/core/skills/_learnings/commit.yaml +0 -69
- package/src/core/skills/_learnings/story-writer.yaml +0 -71
- package/src/core/teams/backend.json +0 -41
- package/src/core/teams/builder-validator.json +0 -51
- package/src/core/teams/code-review.json +0 -41
- package/src/core/teams/frontend.json +0 -41
- package/src/core/teams/fullstack.json +0 -41
- package/src/core/teams/logic-audit.json +0 -53
- package/src/core/teams/perf-audit.json +0 -71
- package/src/core/teams/qa.json +0 -41
- package/src/core/teams/security-audit.json +0 -71
- package/src/core/teams/solo.json +0 -35
- package/src/core/teams/test-audit.json +0 -71
- package/src/core/templates/CONTEXT.md.example +0 -49
- package/src/core/templates/README-template.md +0 -16
- package/src/core/templates/adr-template.md +0 -28
- package/src/core/templates/agent-coordination-pattern.md +0 -38
- package/src/core/templates/agent-profile-template.md +0 -51
- package/src/core/templates/agileflow-metadata.json +0 -150
- package/src/core/templates/browser-qa-spec.yaml +0 -94
- package/src/core/templates/ci-workflow.yml +0 -74
- package/src/core/templates/claude-settings.advanced.example.json +0 -75
- package/src/core/templates/claude-settings.example.json +0 -26
- package/src/core/templates/command-documentation.md +0 -187
- package/src/core/templates/command-prerequisites.yaml +0 -169
- package/src/core/templates/comms-note-template.md +0 -24
- package/src/core/templates/damage-control-patterns.yaml +0 -243
- package/src/core/templates/environment.json +0 -18
- package/src/core/templates/epic-template.md +0 -27
- package/src/core/templates/plan-template.md +0 -125
- package/src/core/templates/preserve-rules-common.md +0 -107
- package/src/core/templates/preserve-rules.json +0 -42
- package/src/core/templates/proactive-action-spec.md +0 -29
- package/src/core/templates/product-brief.md +0 -136
- package/src/core/templates/quality-gate-priorities.md +0 -34
- package/src/core/templates/research-template.md +0 -44
- package/src/core/templates/session-harness-protocol.md +0 -128
- package/src/core/templates/session-state.json +0 -56
- package/src/core/templates/story-lifecycle.md +0 -213
- package/src/core/templates/story-template.md +0 -92
- package/src/core/templates/tdd-test-template.js +0 -241
- package/src/core/templates/worktrees-guide.md +0 -231
- package/tools/agileflow-npx.js +0 -52
- package/tools/cli/agileflow-cli.js +0 -72
- package/tools/cli/commands/config.js +0 -285
- package/tools/cli/commands/doctor.js +0 -496
- package/tools/cli/commands/list.js +0 -385
- package/tools/cli/commands/session.js +0 -1176
- package/tools/cli/commands/setup.js +0 -255
- package/tools/cli/commands/status.js +0 -101
- package/tools/cli/commands/tui.js +0 -56
- package/tools/cli/commands/uninstall.js +0 -155
- package/tools/cli/commands/update.js +0 -299
- package/tools/cli/installers/core/installer.js +0 -892
- package/tools/cli/installers/ide/_base-ide.js +0 -518
- package/tools/cli/installers/ide/_interface.js +0 -238
- package/tools/cli/installers/ide/claude-code.js +0 -432
- package/tools/cli/installers/ide/codex.js +0 -426
- package/tools/cli/installers/ide/cursor.js +0 -217
- package/tools/cli/installers/ide/manager.js +0 -222
- package/tools/cli/installers/ide/windsurf.js +0 -282
- package/tools/cli/lib/command-context.js +0 -382
- package/tools/cli/lib/config-manager.js +0 -446
- package/tools/cli/lib/content-injector.js +0 -969
- package/tools/cli/lib/content-transformer.js +0 -496
- package/tools/cli/lib/docs-setup.js +0 -464
- package/tools/cli/lib/error-handler.js +0 -165
- package/tools/cli/lib/ide-error-factory.js +0 -421
- package/tools/cli/lib/ide-errors.js +0 -367
- package/tools/cli/lib/ide-generator.js +0 -357
- package/tools/cli/lib/ide-health-monitor.js +0 -364
- package/tools/cli/lib/ide-registry.js +0 -297
- package/tools/cli/lib/npm-utils.js +0 -103
- package/tools/cli/lib/self-update.js +0 -148
- package/tools/cli/lib/ui.js +0 -211
- package/tools/cli/lib/utils.js +0 -87
- package/tools/cli/lib/validation-middleware.js +0 -491
- package/tools/cli/lib/version-checker.js +0 -95
- package/tools/postinstall.js +0 -190
|
@@ -1,2246 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* agileflow-welcome.js - Beautiful SessionStart welcome display
|
|
5
|
-
*
|
|
6
|
-
* Shows a transparent ASCII table with:
|
|
7
|
-
* - Project info (name, version, branch, commit)
|
|
8
|
-
* - Story stats (WIP, blocked, completed)
|
|
9
|
-
* - Archival status
|
|
10
|
-
* - Session cleanup status
|
|
11
|
-
* - Last commit
|
|
12
|
-
*
|
|
13
|
-
* PERFORMANCE OPTIMIZATION (US-0356):
|
|
14
|
-
* Phase 1: Table display + instant notifications (~300-350ms)
|
|
15
|
-
* Phase 2: Cached update check + instant post-table notifications
|
|
16
|
-
* Phase 3: Background deferred work via welcome-deferred.js
|
|
17
|
-
* - npm update check (with cache write)
|
|
18
|
-
* - Session health warnings
|
|
19
|
-
* - Duplicate process detection
|
|
20
|
-
* - Story claiming/file tracking cleanup
|
|
21
|
-
* - Epic completion, ideation sync, automations
|
|
22
|
-
* Deferred warnings saved to session-state.json, displayed next session.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
const fs = require('fs');
|
|
26
|
-
const path = require('path');
|
|
27
|
-
const { executeCommandSync, git, spawnBackground } = require('../lib/process-executor');
|
|
28
|
-
|
|
29
|
-
// Shared utilities
|
|
30
|
-
const { c, box } = require('../lib/colors');
|
|
31
|
-
const {
|
|
32
|
-
getProjectRoot,
|
|
33
|
-
getStatusPath,
|
|
34
|
-
getMetadataPath,
|
|
35
|
-
getSessionStatePath,
|
|
36
|
-
getAgileflowDir,
|
|
37
|
-
getClaudeDir,
|
|
38
|
-
} = require('../lib/paths');
|
|
39
|
-
const { readJSONCached, readFileCached } = require('../lib/file-cache');
|
|
40
|
-
const { tryOptional } = require('../lib/errors');
|
|
41
|
-
const { createLogger } = require('../lib/logger');
|
|
42
|
-
const log = createLogger('welcome');
|
|
43
|
-
|
|
44
|
-
// PERFORMANCE OPTIMIZATION (US-0356): Profiling helper
|
|
45
|
-
// Only active when AGILEFLOW_DEBUG=welcome is set
|
|
46
|
-
const _profiling = process.env.AGILEFLOW_DEBUG === 'welcome';
|
|
47
|
-
const _timings = {};
|
|
48
|
-
function _mark(label) {
|
|
49
|
-
if (_profiling) _timings[label] = process.hrtime.bigint();
|
|
50
|
-
}
|
|
51
|
-
function _elapsed(from, to) {
|
|
52
|
-
if (!_profiling) return '';
|
|
53
|
-
const ns = Number((_timings[to] || process.hrtime.bigint()) - (_timings[from] || 0n));
|
|
54
|
-
return `${(ns / 1e6).toFixed(1)}ms`;
|
|
55
|
-
}
|
|
56
|
-
function _logTimings() {
|
|
57
|
-
if (!_profiling) return;
|
|
58
|
-
const labels = Object.keys(_timings);
|
|
59
|
-
const pairs = [];
|
|
60
|
-
for (let i = 1; i < labels.length; i++) {
|
|
61
|
-
pairs.push(`${labels[i]}=${_elapsed(labels[i - 1], labels[i])}`);
|
|
62
|
-
}
|
|
63
|
-
if (labels.length >= 2) {
|
|
64
|
-
pairs.push(`TOTAL=${_elapsed(labels[0], labels[labels.length - 1])}`);
|
|
65
|
-
}
|
|
66
|
-
// Output to stderr so it doesn't mix with the welcome table
|
|
67
|
-
console.error(`[welcome:timing] ${pairs.join(' ')}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Hook metrics module (kept at top level - needed early for timer)
|
|
71
|
-
let hookMetrics;
|
|
72
|
-
try {
|
|
73
|
-
hookMetrics = require('./lib/hook-metrics.js');
|
|
74
|
-
} catch (e) {
|
|
75
|
-
// Hook metrics not available
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* PERFORMANCE OPTIMIZATION: Load all project files using LRU cache
|
|
80
|
-
* Uses file-cache module for automatic caching with 15s TTL.
|
|
81
|
-
* Files are cached across script invocations within TTL window.
|
|
82
|
-
* Estimated savings: 60-120ms on cache hits
|
|
83
|
-
*
|
|
84
|
-
* Additional optimizations in this file (US-0356):
|
|
85
|
-
* - Git batching: 3 subprocess calls → 1 (~20-40ms savings)
|
|
86
|
-
* - Session-manager inline: subprocess → direct require() (~50-150ms savings)
|
|
87
|
-
* - Tmux cache: subprocess → session-state lookup (~10-20ms after first run)
|
|
88
|
-
* Total estimated savings: ~130-260ms
|
|
89
|
-
*/
|
|
90
|
-
function loadProjectFiles(rootDir) {
|
|
91
|
-
const paths = {
|
|
92
|
-
status: getStatusPath(rootDir),
|
|
93
|
-
metadata: getMetadataPath(rootDir),
|
|
94
|
-
settings: path.join(getClaudeDir(rootDir), 'settings.json'),
|
|
95
|
-
sessionState: getSessionStatePath(rootDir),
|
|
96
|
-
configYaml: path.join(getAgileflowDir(rootDir), 'config.yaml'),
|
|
97
|
-
cliPackage: path.join(rootDir, 'packages', 'cli', 'package.json'),
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
status: readJSONCached(paths.status),
|
|
102
|
-
metadata: readJSONCached(paths.metadata),
|
|
103
|
-
settings: readJSONCached(paths.settings),
|
|
104
|
-
sessionState: readJSONCached(paths.sessionState),
|
|
105
|
-
configYaml: readFileCached(paths.configYaml),
|
|
106
|
-
cliPackage: readJSONCached(paths.cliPackage),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Detect the user's platform for install guidance
|
|
112
|
-
*/
|
|
113
|
-
function detectPlatform() {
|
|
114
|
-
const platform = process.platform;
|
|
115
|
-
|
|
116
|
-
if (platform === 'darwin') {
|
|
117
|
-
return { os: 'macOS', installCmd: 'brew install tmux', hasSudo: true };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (platform === 'linux') {
|
|
121
|
-
// Try to detect Linux distribution
|
|
122
|
-
try {
|
|
123
|
-
const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
|
|
124
|
-
if (
|
|
125
|
-
osRelease.includes('Ubuntu') ||
|
|
126
|
-
osRelease.includes('Debian') ||
|
|
127
|
-
osRelease.includes('Pop!_OS') ||
|
|
128
|
-
osRelease.includes('Mint')
|
|
129
|
-
) {
|
|
130
|
-
return { os: 'Ubuntu/Debian', installCmd: 'sudo apt install tmux', hasSudo: true };
|
|
131
|
-
}
|
|
132
|
-
if (
|
|
133
|
-
osRelease.includes('Fedora') ||
|
|
134
|
-
osRelease.includes('Red Hat') ||
|
|
135
|
-
osRelease.includes('CentOS') ||
|
|
136
|
-
osRelease.includes('Rocky')
|
|
137
|
-
) {
|
|
138
|
-
return { os: 'Fedora/RHEL', installCmd: 'sudo dnf install tmux', hasSudo: true };
|
|
139
|
-
}
|
|
140
|
-
if (osRelease.includes('Arch')) {
|
|
141
|
-
return { os: 'Arch', installCmd: 'sudo pacman -S tmux', hasSudo: true };
|
|
142
|
-
}
|
|
143
|
-
} catch (e) {
|
|
144
|
-
// Can't read /etc/os-release
|
|
145
|
-
}
|
|
146
|
-
return { os: 'Linux', installCmd: 'sudo apt install tmux', hasSudo: true };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Windows WSL or unknown
|
|
150
|
-
return { os: 'Unknown', installCmd: null, hasSudo: false };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if tmux is installed
|
|
155
|
-
* PERFORMANCE OPTIMIZATION: Caches result in session-state.json (~10-20ms savings on subsequent runs)
|
|
156
|
-
* Returns object with availability info and platform-specific install suggestion
|
|
157
|
-
*/
|
|
158
|
-
function checkTmuxAvailability(cache) {
|
|
159
|
-
// Check session state cache first (tmux availability doesn't change within a session)
|
|
160
|
-
if (cache?.sessionState?.tmux_available !== undefined) {
|
|
161
|
-
if (cache.sessionState.tmux_available) return { available: true };
|
|
162
|
-
return {
|
|
163
|
-
available: false,
|
|
164
|
-
platform: detectPlatform(),
|
|
165
|
-
noSudoCmd: 'conda install -c conda-forge tmux',
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Actually check (first run or no cache)
|
|
170
|
-
const result = executeCommandSync('which', ['tmux'], { fallback: null });
|
|
171
|
-
const available = result.data !== null;
|
|
172
|
-
|
|
173
|
-
// Cache in session state for next invocation
|
|
174
|
-
try {
|
|
175
|
-
const rootDir = getProjectRoot();
|
|
176
|
-
const sessionStatePath = getSessionStatePath(rootDir);
|
|
177
|
-
if (fs.existsSync(sessionStatePath)) {
|
|
178
|
-
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
179
|
-
state.tmux_available = available;
|
|
180
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
181
|
-
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
// Cache write failed, non-critical
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (available) return { available: true };
|
|
187
|
-
return {
|
|
188
|
-
available: false,
|
|
189
|
-
platform: detectPlatform(),
|
|
190
|
-
noSudoCmd: 'conda install -c conda-forge tmux',
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* PERFORMANCE OPTIMIZATION: Batch git commands into single call
|
|
196
|
-
* Uses `git log -1 --format=%D%n%h%n%s` to get branch, short hash, and subject
|
|
197
|
-
* in a single subprocess instead of 3 separate calls.
|
|
198
|
-
* Savings: ~20-40ms (eliminates 2 subprocess spawns)
|
|
199
|
-
*/
|
|
200
|
-
function getGitInfo(rootDir) {
|
|
201
|
-
const opts = { cwd: rootDir, timeout: 5000, fallback: '' };
|
|
202
|
-
const result = git(['log', '-1', '--format=%D%n%h%n%s'], opts);
|
|
203
|
-
if (result.data) {
|
|
204
|
-
const lines = result.data.split('\n');
|
|
205
|
-
// %D gives decorations like "HEAD -> main, origin/main, tag: v3.0.0"
|
|
206
|
-
const branchMatch = (lines[0] || '').match(/HEAD -> ([^,\s]+)/);
|
|
207
|
-
return {
|
|
208
|
-
branch: branchMatch ? branchMatch[1] : 'detached',
|
|
209
|
-
commit: (lines[1] || 'unknown').trim(),
|
|
210
|
-
lastCommit: lines.slice(2).join('\n').trim(),
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
return { branch: 'unknown', commit: 'unknown', lastCommit: '' };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function getProjectInfo(rootDir, cache = null) {
|
|
217
|
-
const info = {
|
|
218
|
-
name: 'agileflow',
|
|
219
|
-
version: 'unknown',
|
|
220
|
-
branch: 'unknown',
|
|
221
|
-
commit: 'unknown',
|
|
222
|
-
lastCommit: '',
|
|
223
|
-
wipCount: 0,
|
|
224
|
-
blockedCount: 0,
|
|
225
|
-
completedCount: 0,
|
|
226
|
-
readyCount: 0,
|
|
227
|
-
totalStories: 0,
|
|
228
|
-
currentStory: null,
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
// Get AgileFlow version (check multiple sources in priority order)
|
|
232
|
-
// 1. .agileflow/config.yaml (installed user projects - primary source)
|
|
233
|
-
// 2. AgileFlow metadata (installed user projects - legacy)
|
|
234
|
-
// 3. packages/cli/package.json (AgileFlow dev project)
|
|
235
|
-
try {
|
|
236
|
-
// Primary: .agileflow/config.yaml (use cache if available)
|
|
237
|
-
if (cache?.configYaml) {
|
|
238
|
-
const versionMatch = cache.configYaml.match(/^version:\s*['"]?([0-9.]+)/m);
|
|
239
|
-
if (versionMatch) {
|
|
240
|
-
info.version = versionMatch[1];
|
|
241
|
-
}
|
|
242
|
-
} else if (cache?.metadata?.version) {
|
|
243
|
-
// Fallback: metadata from cache
|
|
244
|
-
info.version = cache.metadata.version;
|
|
245
|
-
} else if (cache?.cliPackage?.version) {
|
|
246
|
-
// Dev project: from cache
|
|
247
|
-
info.version = cache.cliPackage.version;
|
|
248
|
-
} else {
|
|
249
|
-
// No cache - fall back to file reads (for backwards compatibility)
|
|
250
|
-
const configPath = path.join(getAgileflowDir(rootDir), 'config.yaml');
|
|
251
|
-
if (fs.existsSync(configPath)) {
|
|
252
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
253
|
-
const versionMatch = content.match(/^version:\s*['"]?([0-9.]+)/m);
|
|
254
|
-
if (versionMatch) {
|
|
255
|
-
info.version = versionMatch[1];
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
const metadataPath = getMetadataPath(rootDir);
|
|
259
|
-
if (fs.existsSync(metadataPath)) {
|
|
260
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
261
|
-
info.version = metadata.version || info.version;
|
|
262
|
-
} else {
|
|
263
|
-
const pkg = JSON.parse(
|
|
264
|
-
fs.readFileSync(path.join(rootDir, 'packages/cli/package.json'), 'utf8')
|
|
265
|
-
);
|
|
266
|
-
info.version = pkg.version || info.version;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
} catch (e) {
|
|
271
|
-
// Silently fail - version will remain 'unknown'
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Get git info (batched into single command for performance)
|
|
275
|
-
const gitInfo = getGitInfo(rootDir);
|
|
276
|
-
info.branch = gitInfo.branch;
|
|
277
|
-
info.commit = gitInfo.commit;
|
|
278
|
-
info.lastCommit = gitInfo.lastCommit;
|
|
279
|
-
|
|
280
|
-
// Get status info (use cache if available)
|
|
281
|
-
try {
|
|
282
|
-
const status = cache?.status;
|
|
283
|
-
if (status?.stories) {
|
|
284
|
-
for (const [id, story] of Object.entries(status.stories)) {
|
|
285
|
-
info.totalStories++;
|
|
286
|
-
if (story.status === 'in_progress') {
|
|
287
|
-
info.wipCount++;
|
|
288
|
-
if (!info.currentStory) {
|
|
289
|
-
info.currentStory = { id, title: story.title };
|
|
290
|
-
}
|
|
291
|
-
} else if (story.status === 'blocked') {
|
|
292
|
-
info.blockedCount++;
|
|
293
|
-
} else if (story.status === 'completed') {
|
|
294
|
-
info.completedCount++;
|
|
295
|
-
} else if (story.status === 'ready') {
|
|
296
|
-
info.readyCount++;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
} else if (!cache) {
|
|
300
|
-
// No cache - fall back to file read
|
|
301
|
-
const statusPath = getStatusPath(rootDir);
|
|
302
|
-
if (fs.existsSync(statusPath)) {
|
|
303
|
-
const statusData = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
304
|
-
if (statusData.stories) {
|
|
305
|
-
for (const [id, story] of Object.entries(statusData.stories)) {
|
|
306
|
-
info.totalStories++;
|
|
307
|
-
if (story.status === 'in_progress') {
|
|
308
|
-
info.wipCount++;
|
|
309
|
-
if (!info.currentStory) {
|
|
310
|
-
info.currentStory = { id, title: story.title };
|
|
311
|
-
}
|
|
312
|
-
} else if (story.status === 'blocked') {
|
|
313
|
-
info.blockedCount++;
|
|
314
|
-
} else if (story.status === 'completed') {
|
|
315
|
-
info.completedCount++;
|
|
316
|
-
} else if (story.status === 'ready') {
|
|
317
|
-
info.readyCount++;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
} catch (e) {
|
|
324
|
-
log.debug('getProjectInfo:', e?.message || String(e));
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return info;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function runArchival(rootDir, cache = null) {
|
|
331
|
-
const result = { ran: false, threshold: 7, archived: 0, remaining: 0 };
|
|
332
|
-
|
|
333
|
-
try {
|
|
334
|
-
// Use cached metadata if available
|
|
335
|
-
const metadata = cache?.metadata;
|
|
336
|
-
if (metadata) {
|
|
337
|
-
if (metadata.archival?.enabled === false) {
|
|
338
|
-
result.disabled = true;
|
|
339
|
-
return result;
|
|
340
|
-
}
|
|
341
|
-
result.threshold = metadata.archival?.threshold_days || 7;
|
|
342
|
-
} else {
|
|
343
|
-
// No cache - fall back to file read
|
|
344
|
-
const metadataPath = getMetadataPath(rootDir);
|
|
345
|
-
if (fs.existsSync(metadataPath)) {
|
|
346
|
-
const metadataData = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
347
|
-
if (metadataData.archival?.enabled === false) {
|
|
348
|
-
result.disabled = true;
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
result.threshold = metadataData.archival?.threshold_days || 7;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Use cached status if available
|
|
356
|
-
const status = cache?.status;
|
|
357
|
-
if (!status && !cache) {
|
|
358
|
-
const statusPath = getStatusPath(rootDir);
|
|
359
|
-
if (!fs.existsSync(statusPath)) return result;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const stories = (status || {}).stories || {};
|
|
363
|
-
|
|
364
|
-
const cutoffDate = new Date();
|
|
365
|
-
cutoffDate.setDate(cutoffDate.getDate() - result.threshold);
|
|
366
|
-
|
|
367
|
-
let toArchiveCount = 0;
|
|
368
|
-
for (const [id, story] of Object.entries(stories)) {
|
|
369
|
-
if (story.status === 'completed' && story.completed_at) {
|
|
370
|
-
if (new Date(story.completed_at) < cutoffDate) {
|
|
371
|
-
toArchiveCount++;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
result.ran = true;
|
|
377
|
-
result.remaining = Object.keys(stories).length;
|
|
378
|
-
|
|
379
|
-
if (toArchiveCount > 0) {
|
|
380
|
-
// Run archival
|
|
381
|
-
const archiveResult = executeCommandSync('bash', ['scripts/archive-completed-stories.sh'], {
|
|
382
|
-
cwd: rootDir,
|
|
383
|
-
});
|
|
384
|
-
if (archiveResult.ok) {
|
|
385
|
-
result.archived = toArchiveCount;
|
|
386
|
-
result.remaining -= toArchiveCount;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
} catch (e) {
|
|
390
|
-
log.debug('runArchival:', e?.message || String(e));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return result;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function clearActiveCommands(rootDir, cache = null) {
|
|
397
|
-
const result = { ran: false, cleared: 0, commandNames: [], preserved: false };
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
const sessionStatePath = getSessionStatePath(rootDir);
|
|
401
|
-
|
|
402
|
-
// Use cached sessionState if available, but we still need to read fresh for clearing
|
|
403
|
-
// because we need to write back. Cache is only useful to check if file exists.
|
|
404
|
-
let state;
|
|
405
|
-
if (cache?.sessionState) {
|
|
406
|
-
state = cache.sessionState;
|
|
407
|
-
result.ran = true;
|
|
408
|
-
} else {
|
|
409
|
-
if (!fs.existsSync(sessionStatePath)) return result;
|
|
410
|
-
state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
411
|
-
result.ran = true;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Check if PreCompact just ran (within last 30 seconds)
|
|
415
|
-
// If so, preserve active_commands instead of clearing them (post-compact session start)
|
|
416
|
-
if (state.last_precompact_at) {
|
|
417
|
-
const precompactTime = new Date(state.last_precompact_at).getTime();
|
|
418
|
-
const now = Date.now();
|
|
419
|
-
const secondsSincePrecompact = (now - precompactTime) / 1000;
|
|
420
|
-
|
|
421
|
-
if (secondsSincePrecompact < 600) {
|
|
422
|
-
// 10 minutes - compacts can take a while with background tasks
|
|
423
|
-
// This is a post-compact session start - preserve active commands
|
|
424
|
-
result.preserved = true;
|
|
425
|
-
// Capture command names for display (but don't clear)
|
|
426
|
-
if (state.active_commands && state.active_commands.length > 0) {
|
|
427
|
-
for (const cmd of state.active_commands) {
|
|
428
|
-
if (cmd.name) result.commandNames.push(cmd.name);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// Clear the precompact timestamp so next true session start will clear
|
|
432
|
-
delete state.last_precompact_at;
|
|
433
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
434
|
-
return result;
|
|
435
|
-
}
|
|
436
|
-
// Precompact was too long ago - clear as normal
|
|
437
|
-
delete state.last_precompact_at;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Handle new array format (active_commands)
|
|
441
|
-
if (state.active_commands && state.active_commands.length > 0) {
|
|
442
|
-
result.cleared = state.active_commands.length;
|
|
443
|
-
// Capture command names before clearing
|
|
444
|
-
for (const cmd of state.active_commands) {
|
|
445
|
-
if (cmd.name) result.commandNames.push(cmd.name);
|
|
446
|
-
}
|
|
447
|
-
state.active_commands = [];
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Handle legacy singular format (active_command) - only capture if not already in array
|
|
451
|
-
if (state.active_command !== undefined) {
|
|
452
|
-
const legacyName = state.active_command.name;
|
|
453
|
-
// Only add to count/names if not already captured from array (avoid duplicates)
|
|
454
|
-
if (legacyName && !result.commandNames.includes(legacyName)) {
|
|
455
|
-
result.cleared++;
|
|
456
|
-
result.commandNames.push(legacyName);
|
|
457
|
-
}
|
|
458
|
-
delete state.active_command;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (result.cleared > 0) {
|
|
462
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
463
|
-
}
|
|
464
|
-
} catch (e) {
|
|
465
|
-
log.debug('clearActiveCommands:', e?.message || String(e));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return result;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* PERFORMANCE OPTIMIZATION (US-0356): Lightweight session check
|
|
473
|
-
*
|
|
474
|
-
* Replaces the full session-manager require chain (~3,600 lines across 8 modules)
|
|
475
|
-
* with direct file I/O on the registry.json and lock files (~30 lines).
|
|
476
|
-
* Only checks active session count and current session info.
|
|
477
|
-
* Full registration, cleanup, and worktree detection deferred to Phase 3 (welcome-deferred.js).
|
|
478
|
-
*
|
|
479
|
-
* Estimated savings: 100-200ms
|
|
480
|
-
*/
|
|
481
|
-
function checkParallelSessionsFast(rootDir) {
|
|
482
|
-
const result = {
|
|
483
|
-
available: false,
|
|
484
|
-
registered: false,
|
|
485
|
-
otherActive: 0,
|
|
486
|
-
currentId: null,
|
|
487
|
-
cleaned: 0,
|
|
488
|
-
cleanedSessions: [],
|
|
489
|
-
isMain: true,
|
|
490
|
-
nickname: null,
|
|
491
|
-
branch: null,
|
|
492
|
-
sessionPath: null,
|
|
493
|
-
mainPath: rootDir,
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
try {
|
|
497
|
-
const sessionsDir = path.join(getAgileflowDir(rootDir), 'sessions');
|
|
498
|
-
const registryPath = path.join(sessionsDir, 'registry.json');
|
|
499
|
-
|
|
500
|
-
if (!fs.existsSync(registryPath)) return result;
|
|
501
|
-
|
|
502
|
-
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
503
|
-
if (!registry.sessions) return result;
|
|
504
|
-
|
|
505
|
-
result.available = true;
|
|
506
|
-
const cwd = process.cwd();
|
|
507
|
-
const staleLocks = [];
|
|
508
|
-
|
|
509
|
-
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
510
|
-
if (session.path === cwd) {
|
|
511
|
-
// Found current session
|
|
512
|
-
result.registered = true;
|
|
513
|
-
result.currentId = id;
|
|
514
|
-
result.isMain = session.is_main === true;
|
|
515
|
-
result.nickname = session.nickname || null;
|
|
516
|
-
result.branch = session.branch || null;
|
|
517
|
-
result.sessionPath = session.path;
|
|
518
|
-
} else {
|
|
519
|
-
// Check if other session is alive via lock file
|
|
520
|
-
const lockPath = path.join(sessionsDir, `${id}.lock`);
|
|
521
|
-
if (fs.existsSync(lockPath)) {
|
|
522
|
-
try {
|
|
523
|
-
const content = fs.readFileSync(lockPath, 'utf8');
|
|
524
|
-
const pidMatch = content.match(/^pid=(\d+)/m);
|
|
525
|
-
if (pidMatch) {
|
|
526
|
-
const pid = parseInt(pidMatch[1], 10);
|
|
527
|
-
try {
|
|
528
|
-
process.kill(pid, 0); // Signal 0 = check alive
|
|
529
|
-
result.otherActive++;
|
|
530
|
-
} catch (e) {
|
|
531
|
-
// Process dead - collect for cleanup
|
|
532
|
-
staleLocks.push({ id, lockPath, pid });
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
} catch (e) {
|
|
536
|
-
// Lock read failed
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Quick cleanup of stale locks (prevents ghost session counts)
|
|
543
|
-
for (const stale of staleLocks) {
|
|
544
|
-
try {
|
|
545
|
-
fs.unlinkSync(stale.lockPath);
|
|
546
|
-
result.cleaned++;
|
|
547
|
-
result.cleanedSessions.push({
|
|
548
|
-
id: stale.id,
|
|
549
|
-
pid: stale.pid,
|
|
550
|
-
reason: 'pid_dead',
|
|
551
|
-
});
|
|
552
|
-
} catch (e) {
|
|
553
|
-
// Cleanup failed, deferred will retry
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
} catch (e) {
|
|
557
|
-
log.debug('checkParallelSessionsFast:', e?.message || String(e));
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
return result;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function checkPreCompact(rootDir, cache = null) {
|
|
564
|
-
const result = { configured: false, scriptExists: false, version: null, outdated: false };
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
// Check if PreCompact hook is configured in settings (use cache if available)
|
|
568
|
-
const settings = cache?.settings;
|
|
569
|
-
if (settings) {
|
|
570
|
-
if (settings.hooks?.PreCompact?.length > 0) {
|
|
571
|
-
result.configured = true;
|
|
572
|
-
}
|
|
573
|
-
} else {
|
|
574
|
-
// No cache - fall back to file read
|
|
575
|
-
const settingsPath = path.join(getClaudeDir(rootDir), 'settings.json');
|
|
576
|
-
if (fs.existsSync(settingsPath)) {
|
|
577
|
-
const settingsData = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
578
|
-
if (settingsData.hooks?.PreCompact?.length > 0) {
|
|
579
|
-
result.configured = true;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// Check if the script exists (must always check filesystem)
|
|
585
|
-
const scriptPath = path.join(rootDir, 'scripts', 'precompact-context.sh');
|
|
586
|
-
if (fs.existsSync(scriptPath)) {
|
|
587
|
-
result.scriptExists = true;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Check configured version from metadata (use cache if available)
|
|
591
|
-
const metadata = cache?.metadata;
|
|
592
|
-
if (metadata) {
|
|
593
|
-
if (metadata.features?.precompact?.configured_version) {
|
|
594
|
-
result.version = metadata.features.precompact.configured_version;
|
|
595
|
-
// PreCompact v2.40.0+ has multi-command support
|
|
596
|
-
result.outdated = compareVersions(result.version, '2.40.0') < 0;
|
|
597
|
-
} else if (result.configured) {
|
|
598
|
-
// Hook exists but no version tracked = definitely outdated
|
|
599
|
-
result.outdated = true;
|
|
600
|
-
result.version = 'unknown';
|
|
601
|
-
}
|
|
602
|
-
} else if (!cache) {
|
|
603
|
-
// No cache - fall back to file read
|
|
604
|
-
const metadataPath = getMetadataPath(rootDir);
|
|
605
|
-
if (fs.existsSync(metadataPath)) {
|
|
606
|
-
const metadataData = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
607
|
-
if (metadataData.features?.precompact?.configured_version) {
|
|
608
|
-
result.version = metadataData.features.precompact.configured_version;
|
|
609
|
-
result.outdated = compareVersions(result.version, '2.40.0') < 0;
|
|
610
|
-
} else if (result.configured) {
|
|
611
|
-
result.outdated = true;
|
|
612
|
-
result.version = 'unknown';
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
} catch (e) {
|
|
617
|
-
log.debug('checkPreCompact:', e?.message || String(e));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
return result;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function checkDamageControl(rootDir, cache = null) {
|
|
624
|
-
const result = { configured: false, level: 'standard', patternCount: 0, scriptsOk: true };
|
|
625
|
-
|
|
626
|
-
try {
|
|
627
|
-
// Check if PreToolUse hooks are configured in settings (use cache if available)
|
|
628
|
-
let settings = cache?.settings;
|
|
629
|
-
if (!settings && !cache) {
|
|
630
|
-
// No cache - fall back to file read
|
|
631
|
-
const settingsPath = path.join(getClaudeDir(rootDir), 'settings.json');
|
|
632
|
-
if (fs.existsSync(settingsPath)) {
|
|
633
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (settings) {
|
|
638
|
-
if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
|
|
639
|
-
// Check for damage-control hooks
|
|
640
|
-
const hasDamageControlHooks = settings.hooks.PreToolUse.some(h =>
|
|
641
|
-
h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
642
|
-
);
|
|
643
|
-
if (hasDamageControlHooks) {
|
|
644
|
-
result.configured = true;
|
|
645
|
-
|
|
646
|
-
// Count how many hooks are present (should be 3: Bash, Edit, Write)
|
|
647
|
-
const dcHooks = settings.hooks.PreToolUse.filter(h =>
|
|
648
|
-
h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
649
|
-
);
|
|
650
|
-
result.hooksCount = dcHooks.length;
|
|
651
|
-
|
|
652
|
-
// Check for enhanced mode (has prompt hook)
|
|
653
|
-
const hasPromptHook = settings.hooks.PreToolUse.some(h =>
|
|
654
|
-
h.hooks?.some(hk => hk.type === 'prompt')
|
|
655
|
-
);
|
|
656
|
-
if (hasPromptHook) {
|
|
657
|
-
result.level = 'enhanced';
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Check if all required scripts exist (in .claude/hooks/damage-control/)
|
|
661
|
-
const hooksDir = path.join(getClaudeDir(rootDir), 'hooks', 'damage-control');
|
|
662
|
-
const requiredScripts = [
|
|
663
|
-
'bash-tool-damage-control.js',
|
|
664
|
-
'edit-tool-damage-control.js',
|
|
665
|
-
'write-tool-damage-control.js',
|
|
666
|
-
];
|
|
667
|
-
for (const script of requiredScripts) {
|
|
668
|
-
if (!fs.existsSync(path.join(hooksDir, script))) {
|
|
669
|
-
result.scriptsOk = false;
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Count patterns in patterns.yaml
|
|
678
|
-
const patternsLocations = [
|
|
679
|
-
path.join(getClaudeDir(rootDir), 'hooks', 'damage-control', 'patterns.yaml'),
|
|
680
|
-
path.join(getAgileflowDir(rootDir), 'scripts', 'damage-control', 'patterns.yaml'),
|
|
681
|
-
];
|
|
682
|
-
for (const patternsPath of patternsLocations) {
|
|
683
|
-
if (fs.existsSync(patternsPath)) {
|
|
684
|
-
const content = fs.readFileSync(patternsPath, 'utf8');
|
|
685
|
-
// Count pattern entries (lines starting with " - pattern:")
|
|
686
|
-
const patternMatches = content.match(/^\s*-\s*pattern:/gm);
|
|
687
|
-
result.patternCount = patternMatches ? patternMatches.length : 0;
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
} catch (e) {
|
|
692
|
-
log.debug('checkDamageControl:', e?.message || String(e));
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
return result;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Compare semantic versions: returns -1 if a < b, 0 if equal, 1 if a > b
|
|
699
|
-
function compareVersions(a, b) {
|
|
700
|
-
if (!a || !b) return 0;
|
|
701
|
-
const partsA = a.split('.').map(Number);
|
|
702
|
-
const partsB = b.split('.').map(Number);
|
|
703
|
-
for (let i = 0; i < 3; i++) {
|
|
704
|
-
const numA = partsA[i] || 0;
|
|
705
|
-
const numB = partsB[i] || 0;
|
|
706
|
-
if (numA < numB) return -1;
|
|
707
|
-
if (numA > numB) return 1;
|
|
708
|
-
}
|
|
709
|
-
return 0;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* All available config options with their version requirements
|
|
714
|
-
* These are the options that can be configured through /agileflow:configure
|
|
715
|
-
*/
|
|
716
|
-
const ALL_CONFIG_OPTIONS = {
|
|
717
|
-
claudeMdReinforcement: {
|
|
718
|
-
since: '2.92.0',
|
|
719
|
-
description: 'Add /babysit rules to CLAUDE.md',
|
|
720
|
-
autoApplyable: true,
|
|
721
|
-
},
|
|
722
|
-
sessionStartHook: {
|
|
723
|
-
since: '2.35.0',
|
|
724
|
-
description: 'Welcome display on session start',
|
|
725
|
-
autoApplyable: false,
|
|
726
|
-
},
|
|
727
|
-
precompactHook: {
|
|
728
|
-
since: '2.40.0',
|
|
729
|
-
description: 'Context preservation during /compact',
|
|
730
|
-
autoApplyable: false,
|
|
731
|
-
},
|
|
732
|
-
damageControlHooks: {
|
|
733
|
-
since: '2.50.0',
|
|
734
|
-
description: 'Block destructive commands',
|
|
735
|
-
autoApplyable: false,
|
|
736
|
-
},
|
|
737
|
-
statusLine: { since: '2.35.0', description: 'Custom status bar display', autoApplyable: false },
|
|
738
|
-
autoArchival: {
|
|
739
|
-
since: '2.35.0',
|
|
740
|
-
description: 'Auto-archive completed stories',
|
|
741
|
-
autoApplyable: false,
|
|
742
|
-
},
|
|
743
|
-
autoUpdate: {
|
|
744
|
-
since: '2.70.0',
|
|
745
|
-
description: 'Auto-update on session start',
|
|
746
|
-
autoApplyable: false,
|
|
747
|
-
},
|
|
748
|
-
ralphLoop: { since: '2.60.0', description: 'Autonomous story processing', autoApplyable: false },
|
|
749
|
-
tmuxAutoSpawn: {
|
|
750
|
-
since: '2.92.0',
|
|
751
|
-
description: 'Auto-start Claude in tmux session',
|
|
752
|
-
autoApplyable: true,
|
|
753
|
-
},
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Check for new config options that haven't been presented to the user
|
|
758
|
-
* Returns info about outdated config and whether to auto-apply (for "full" profile)
|
|
759
|
-
*/
|
|
760
|
-
function checkConfigStaleness(rootDir, currentVersion, cache = null) {
|
|
761
|
-
const result = {
|
|
762
|
-
outdated: false,
|
|
763
|
-
newOptionsCount: 0,
|
|
764
|
-
newOptions: [],
|
|
765
|
-
configSchemaVersion: null,
|
|
766
|
-
activeProfile: null,
|
|
767
|
-
autoApply: false,
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
try {
|
|
771
|
-
const metadata = cache?.metadata;
|
|
772
|
-
if (!metadata) return result;
|
|
773
|
-
|
|
774
|
-
result.configSchemaVersion = metadata.config_schema_version || null;
|
|
775
|
-
result.activeProfile = metadata.active_profile || null;
|
|
776
|
-
|
|
777
|
-
const configOptions = metadata.agileflow?.config_options || {};
|
|
778
|
-
|
|
779
|
-
// If no config_schema_version, this is an old installation - all options are "new"
|
|
780
|
-
if (!result.configSchemaVersion) {
|
|
781
|
-
// For old installations, detect which features are actually configured via settings.json
|
|
782
|
-
const settings = cache?.settings || {};
|
|
783
|
-
const hooks = settings.hooks || {};
|
|
784
|
-
|
|
785
|
-
// Check each option against actual configuration
|
|
786
|
-
for (const [name, optionInfo] of Object.entries(ALL_CONFIG_OPTIONS)) {
|
|
787
|
-
const isConfigured = isOptionActuallyConfigured(name, hooks, settings);
|
|
788
|
-
if (!isConfigured) {
|
|
789
|
-
result.outdated = true;
|
|
790
|
-
result.newOptionsCount++;
|
|
791
|
-
result.newOptions.push({
|
|
792
|
-
name,
|
|
793
|
-
description: optionInfo.description,
|
|
794
|
-
autoApplyable: optionInfo.autoApplyable,
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
} else {
|
|
799
|
-
// Check for unconfigured options in metadata
|
|
800
|
-
for (const [name, option] of Object.entries(configOptions)) {
|
|
801
|
-
if (option.configured === false) {
|
|
802
|
-
const optionInfo = ALL_CONFIG_OPTIONS[name] || {
|
|
803
|
-
description: name,
|
|
804
|
-
autoApplyable: false,
|
|
805
|
-
};
|
|
806
|
-
result.outdated = true;
|
|
807
|
-
result.newOptionsCount++;
|
|
808
|
-
result.newOptions.push({
|
|
809
|
-
name,
|
|
810
|
-
description: optionInfo.description,
|
|
811
|
-
autoApplyable: optionInfo.autoApplyable,
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Check for options that might not be in metadata yet (added after their install)
|
|
817
|
-
for (const [name, optionInfo] of Object.entries(ALL_CONFIG_OPTIONS)) {
|
|
818
|
-
if (!configOptions[name]) {
|
|
819
|
-
// Option doesn't exist in metadata - check if it was added after their config_schema_version
|
|
820
|
-
if (compareVersions(result.configSchemaVersion, optionInfo.since) < 0) {
|
|
821
|
-
const alreadyAdded = result.newOptions.some(o => o.name === name);
|
|
822
|
-
if (!alreadyAdded) {
|
|
823
|
-
result.outdated = true;
|
|
824
|
-
result.newOptionsCount++;
|
|
825
|
-
result.newOptions.push({
|
|
826
|
-
name,
|
|
827
|
-
description: optionInfo.description,
|
|
828
|
-
autoApplyable: optionInfo.autoApplyable,
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// If profile is "full", auto-apply features that support it
|
|
837
|
-
if (result.outdated && result.activeProfile === 'full') {
|
|
838
|
-
const autoApplyableOptions = result.newOptions.filter(o => o.autoApplyable);
|
|
839
|
-
if (autoApplyableOptions.length > 0) {
|
|
840
|
-
result.autoApply = true;
|
|
841
|
-
// Only auto-apply the auto-applyable ones
|
|
842
|
-
result.autoApplyOptions = autoApplyableOptions;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
} catch (e) {
|
|
846
|
-
// Silently fail - config check is non-critical
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
return result;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* Check if a config option is actually configured in settings
|
|
854
|
-
*/
|
|
855
|
-
function isOptionActuallyConfigured(optionName, hooks, settings) {
|
|
856
|
-
switch (optionName) {
|
|
857
|
-
case 'sessionStartHook':
|
|
858
|
-
return hooks.SessionStart && hooks.SessionStart.length > 0;
|
|
859
|
-
case 'precompactHook':
|
|
860
|
-
return hooks.PreCompact && hooks.PreCompact.length > 0;
|
|
861
|
-
case 'damageControlHooks':
|
|
862
|
-
return (
|
|
863
|
-
hooks.PreToolUse &&
|
|
864
|
-
hooks.PreToolUse.some(h => h.hooks?.some(hk => hk.command?.includes('damage-control')))
|
|
865
|
-
);
|
|
866
|
-
case 'statusLine':
|
|
867
|
-
return settings.statusLine && settings.statusLine.command;
|
|
868
|
-
case 'autoArchival':
|
|
869
|
-
// Archival is tied to SessionStart hook running archive script
|
|
870
|
-
return (
|
|
871
|
-
hooks.SessionStart &&
|
|
872
|
-
hooks.SessionStart.some(h => h.hooks?.some(hk => hk.command?.includes('archive')))
|
|
873
|
-
);
|
|
874
|
-
case 'autoUpdate':
|
|
875
|
-
// Would need to check metadata for autoUpdate setting
|
|
876
|
-
return false; // Default to not configured
|
|
877
|
-
case 'ralphLoop':
|
|
878
|
-
return (
|
|
879
|
-
hooks.Stop && hooks.Stop.some(h => h.hooks?.some(hk => hk.command?.includes('ralph-loop')))
|
|
880
|
-
);
|
|
881
|
-
case 'claudeMdReinforcement':
|
|
882
|
-
// Check if CLAUDE.md has the marker - can't easily check from here
|
|
883
|
-
return false; // Let welcome script handle this
|
|
884
|
-
case 'tmuxAutoSpawn':
|
|
885
|
-
// Check metadata for tmuxAutoSpawn setting (default is enabled)
|
|
886
|
-
return false; // Let welcome script handle this via metadata
|
|
887
|
-
default:
|
|
888
|
-
return false;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Auto-apply new config options for "full" profile
|
|
894
|
-
* Returns true if any options were applied
|
|
895
|
-
*/
|
|
896
|
-
function autoApplyConfigOptions(rootDir, newOptions) {
|
|
897
|
-
let applied = 0;
|
|
898
|
-
|
|
899
|
-
for (const option of newOptions) {
|
|
900
|
-
try {
|
|
901
|
-
if (option.name === 'claudeMdReinforcement') {
|
|
902
|
-
// Apply CLAUDE.md reinforcement
|
|
903
|
-
const claudeMdPath = path.join(rootDir, 'CLAUDE.md');
|
|
904
|
-
const marker = '<!-- AGILEFLOW_BABYSIT_RULES -->';
|
|
905
|
-
const content = `
|
|
906
|
-
|
|
907
|
-
${marker}
|
|
908
|
-
## AgileFlow /babysit Context Preservation Rules
|
|
909
|
-
|
|
910
|
-
When \`/agileflow:babysit\` is active (check session-state.json), these rules are MANDATORY:
|
|
911
|
-
|
|
912
|
-
1. **ALWAYS end responses with the AskUserQuestion tool** - Not text like "What next?" but the ACTUAL TOOL CALL
|
|
913
|
-
2. **Use Plan Mode for non-trivial tasks** - Call \`EnterPlanMode\` before complex implementations
|
|
914
|
-
3. **Delegate complex work to domain experts** - Use \`Task\` tool with appropriate \`subagent_type\`
|
|
915
|
-
4. **Track progress with TaskCreate/TaskUpdate** - For any task with 3+ steps
|
|
916
|
-
|
|
917
|
-
These rules persist across conversation compaction. Check \`docs/09-agents/session-state.json\` for active commands.
|
|
918
|
-
${marker}
|
|
919
|
-
`;
|
|
920
|
-
|
|
921
|
-
let existingContent = '';
|
|
922
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
923
|
-
existingContent = fs.readFileSync(claudeMdPath, 'utf8');
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (!existingContent.includes(marker)) {
|
|
927
|
-
fs.appendFileSync(claudeMdPath, content);
|
|
928
|
-
applied++;
|
|
929
|
-
}
|
|
930
|
-
} else if (option.name === 'tmuxAutoSpawn') {
|
|
931
|
-
// Auto-enable tmux auto-spawn via metadata
|
|
932
|
-
const metadataPath = getMetadataPath(rootDir);
|
|
933
|
-
if (fs.existsSync(metadataPath)) {
|
|
934
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
935
|
-
if (!metadata.features) metadata.features = {};
|
|
936
|
-
if (
|
|
937
|
-
!metadata.features.tmuxAutoSpawn ||
|
|
938
|
-
metadata.features.tmuxAutoSpawn.enabled === undefined
|
|
939
|
-
) {
|
|
940
|
-
metadata.features.tmuxAutoSpawn = {
|
|
941
|
-
enabled: true,
|
|
942
|
-
version: metadata.version || '2.92.0',
|
|
943
|
-
at: new Date().toISOString(),
|
|
944
|
-
};
|
|
945
|
-
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
|
|
946
|
-
applied++;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
// Add more option handlers here as new options are added
|
|
951
|
-
} catch (e) {
|
|
952
|
-
// Silently fail individual options
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// Update metadata to mark options as configured
|
|
957
|
-
if (applied > 0) {
|
|
958
|
-
try {
|
|
959
|
-
const metadataPath = getMetadataPath(rootDir);
|
|
960
|
-
if (fs.existsSync(metadataPath)) {
|
|
961
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
962
|
-
|
|
963
|
-
// Get current CLI version for updating config_schema_version
|
|
964
|
-
let currentVersion = '2.92.0';
|
|
965
|
-
try {
|
|
966
|
-
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
967
|
-
if (fs.existsSync(pkgPath)) {
|
|
968
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
969
|
-
currentVersion = pkg.version;
|
|
970
|
-
}
|
|
971
|
-
} catch (e) {
|
|
972
|
-
log.debug('getPackageVersion:', e?.message || String(e));
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// Update config_schema_version
|
|
976
|
-
metadata.config_schema_version = currentVersion;
|
|
977
|
-
|
|
978
|
-
// Mark applied options as configured
|
|
979
|
-
if (!metadata.agileflow) metadata.agileflow = {};
|
|
980
|
-
if (!metadata.agileflow.config_options) metadata.agileflow.config_options = {};
|
|
981
|
-
|
|
982
|
-
for (const option of newOptions) {
|
|
983
|
-
metadata.agileflow.config_options[option.name] = {
|
|
984
|
-
...metadata.agileflow.config_options[option.name],
|
|
985
|
-
configured: true,
|
|
986
|
-
enabled: true,
|
|
987
|
-
configured_at: new Date().toISOString(),
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
|
|
992
|
-
}
|
|
993
|
-
} catch (e) {
|
|
994
|
-
// Silently fail metadata update
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return applied;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* PERFORMANCE OPTIMIZATION: Check update cache in session-state.json (1-hour TTL)
|
|
1003
|
-
* Avoids ~139ms npm registry network call when cache is fresh.
|
|
1004
|
-
* Returns cached update result or null if stale/missing.
|
|
1005
|
-
*/
|
|
1006
|
-
function getUpdateFromCache(cache) {
|
|
1007
|
-
try {
|
|
1008
|
-
const updateCache = cache?.sessionState?.update_cache;
|
|
1009
|
-
if (!updateCache || typeof updateCache !== 'object') return null;
|
|
1010
|
-
if (!updateCache.checked_at || !updateCache.result) return null;
|
|
1011
|
-
|
|
1012
|
-
const checkedAt = new Date(updateCache.checked_at).getTime();
|
|
1013
|
-
if (!Number.isFinite(checkedAt)) return null;
|
|
1014
|
-
|
|
1015
|
-
const age = Date.now() - checkedAt;
|
|
1016
|
-
const ONE_HOUR = 60 * 60 * 1000;
|
|
1017
|
-
|
|
1018
|
-
// Reject negative age (future timestamps from clock skew) or absurdly old
|
|
1019
|
-
if (age < 0 || age > 24 * ONE_HOUR) return null;
|
|
1020
|
-
|
|
1021
|
-
if (age < ONE_HOUR) {
|
|
1022
|
-
return updateCache.result;
|
|
1023
|
-
}
|
|
1024
|
-
} catch (e) {
|
|
1025
|
-
// Cache read failed
|
|
1026
|
-
}
|
|
1027
|
-
return null;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
/**
|
|
1031
|
-
* Display deferred warnings from previous session's background work.
|
|
1032
|
-
* Warnings are saved by welcome-deferred.js and displayed here on next start.
|
|
1033
|
-
* Clears warnings after display.
|
|
1034
|
-
*/
|
|
1035
|
-
function displayDeferredWarnings(rootDir) {
|
|
1036
|
-
try {
|
|
1037
|
-
const sessionStatePath = getSessionStatePath(rootDir);
|
|
1038
|
-
if (!fs.existsSync(sessionStatePath)) return;
|
|
1039
|
-
|
|
1040
|
-
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
1041
|
-
const deferredWarnings = state.deferred_warnings;
|
|
1042
|
-
if (!deferredWarnings || !Array.isArray(deferredWarnings) || deferredWarnings.length === 0)
|
|
1043
|
-
return;
|
|
1044
|
-
|
|
1045
|
-
for (const warning of deferredWarnings) {
|
|
1046
|
-
if (!warning.lines || warning.lines.length === 0) continue;
|
|
1047
|
-
|
|
1048
|
-
console.log('');
|
|
1049
|
-
switch (warning.type) {
|
|
1050
|
-
case 'update_available':
|
|
1051
|
-
console.log(`${c.amber}↑ ${warning.lines[0]}${c.reset}`);
|
|
1052
|
-
if (warning.lines[1]) console.log(` ${c.skyBlue}${warning.lines[1]}${c.reset}`);
|
|
1053
|
-
break;
|
|
1054
|
-
case 'session_health':
|
|
1055
|
-
for (const line of warning.lines) {
|
|
1056
|
-
if (line.startsWith(' ')) {
|
|
1057
|
-
console.log(`${c.dim} └─ ${line.trim()}${c.reset}`);
|
|
1058
|
-
} else {
|
|
1059
|
-
console.log(`${c.coral}⚠️ ${line}${c.reset}`);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
break;
|
|
1063
|
-
case 'process_cleanup':
|
|
1064
|
-
console.log(`${c.amber}⚠️ ${warning.lines[0]}${c.reset}`);
|
|
1065
|
-
if (warning.lines.length > 1) {
|
|
1066
|
-
for (const line of warning.lines.slice(1)) {
|
|
1067
|
-
console.log(`${c.slate} ${line}${c.reset}`);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
break;
|
|
1071
|
-
case 'epic_completion':
|
|
1072
|
-
for (const line of warning.lines) {
|
|
1073
|
-
console.log(`${c.mintGreen}✅ ${line}${c.reset}`);
|
|
1074
|
-
}
|
|
1075
|
-
break;
|
|
1076
|
-
case 'ideation_sync':
|
|
1077
|
-
console.log(`${c.dim}📊 ${warning.lines[0]}${c.reset}`);
|
|
1078
|
-
break;
|
|
1079
|
-
case 'automations':
|
|
1080
|
-
console.log(`${c.teal}🤖 ${warning.lines[0]}${c.reset}`);
|
|
1081
|
-
for (const line of warning.lines.slice(1)) {
|
|
1082
|
-
console.log(`${c.dim} └─ ${line}${c.reset}`);
|
|
1083
|
-
}
|
|
1084
|
-
break;
|
|
1085
|
-
case 'story_claiming':
|
|
1086
|
-
console.log(`${c.amber}🔒 ${warning.lines[0]}${c.reset}`);
|
|
1087
|
-
for (const line of warning.lines.slice(1)) {
|
|
1088
|
-
console.log(`${c.dim} └─ ${line.trim()}${c.reset}`);
|
|
1089
|
-
}
|
|
1090
|
-
break;
|
|
1091
|
-
case 'file_tracking':
|
|
1092
|
-
console.log(`${c.amber}📁 ${warning.lines[0]}${c.reset}`);
|
|
1093
|
-
break;
|
|
1094
|
-
default:
|
|
1095
|
-
for (const line of warning.lines) {
|
|
1096
|
-
console.log(`${c.dim}${line}${c.reset}`);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
// Clear deferred warnings after display
|
|
1102
|
-
delete state.deferred_warnings;
|
|
1103
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
1104
|
-
} catch (e) {
|
|
1105
|
-
// Display failed, non-critical
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Check for updates (now handled by welcome-deferred.js, kept for backward compatibility)
|
|
1110
|
-
async function checkUpdates() {
|
|
1111
|
-
const result = {
|
|
1112
|
-
available: false,
|
|
1113
|
-
installed: null,
|
|
1114
|
-
latest: null,
|
|
1115
|
-
justUpdated: false,
|
|
1116
|
-
previousVersion: null,
|
|
1117
|
-
autoUpdate: false,
|
|
1118
|
-
changelog: [],
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
const updateChecker = tryOptional(() => require('./check-update.js'), 'check-update');
|
|
1122
|
-
if (!updateChecker) return result;
|
|
1123
|
-
|
|
1124
|
-
try {
|
|
1125
|
-
const updateInfo = await updateChecker.checkForUpdates();
|
|
1126
|
-
result.installed = updateInfo.installed;
|
|
1127
|
-
result.latest = updateInfo.latest;
|
|
1128
|
-
result.available = updateInfo.updateAvailable;
|
|
1129
|
-
result.justUpdated = updateInfo.justUpdated;
|
|
1130
|
-
result.previousVersion = updateInfo.previousVersion;
|
|
1131
|
-
result.autoUpdate = updateInfo.autoUpdate;
|
|
1132
|
-
|
|
1133
|
-
// If just updated, try to get changelog entries
|
|
1134
|
-
if (result.justUpdated && result.installed) {
|
|
1135
|
-
result.changelog = getChangelogEntries(result.installed);
|
|
1136
|
-
}
|
|
1137
|
-
} catch (e) {
|
|
1138
|
-
// Silently fail - update check is non-critical
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
return result;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// Parse CHANGELOG.md for entries of a specific version
|
|
1145
|
-
function getChangelogEntries(version) {
|
|
1146
|
-
const entries = [];
|
|
1147
|
-
|
|
1148
|
-
try {
|
|
1149
|
-
// Look for CHANGELOG.md in .agileflow or package location
|
|
1150
|
-
const possiblePaths = [
|
|
1151
|
-
path.join(__dirname, '..', 'CHANGELOG.md'),
|
|
1152
|
-
path.join(__dirname, 'CHANGELOG.md'),
|
|
1153
|
-
];
|
|
1154
|
-
|
|
1155
|
-
let changelogContent = null;
|
|
1156
|
-
for (const p of possiblePaths) {
|
|
1157
|
-
if (fs.existsSync(p)) {
|
|
1158
|
-
changelogContent = fs.readFileSync(p, 'utf8');
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
if (!changelogContent) return entries;
|
|
1164
|
-
|
|
1165
|
-
// Find the section for this version
|
|
1166
|
-
const versionPattern = new RegExp(`## \\[${version}\\].*?\\n([\\s\\S]*?)(?=## \\[|$)`);
|
|
1167
|
-
const match = changelogContent.match(versionPattern);
|
|
1168
|
-
|
|
1169
|
-
if (match) {
|
|
1170
|
-
// Extract bullet points from Added/Changed/Fixed sections
|
|
1171
|
-
const lines = match[1].split('\n');
|
|
1172
|
-
for (const line of lines) {
|
|
1173
|
-
const bulletMatch = line.match(/^- (.+)$/);
|
|
1174
|
-
if (bulletMatch && entries.length < 3) {
|
|
1175
|
-
entries.push(bulletMatch[1]);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
} catch (e) {
|
|
1180
|
-
// Silently fail
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
return entries;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
// Run auto-update if enabled (quiet mode - minimal output)
|
|
1187
|
-
// DEPRECATED: Use spawnAutoUpdateInBackground() instead for non-blocking updates
|
|
1188
|
-
async function runAutoUpdate(rootDir, fromVersion, toVersion) {
|
|
1189
|
-
const runUpdate = () => {
|
|
1190
|
-
return executeCommandSync('npx', ['agileflow@latest', 'update', '--force'], {
|
|
1191
|
-
cwd: rootDir,
|
|
1192
|
-
timeout: 120000,
|
|
1193
|
-
});
|
|
1194
|
-
};
|
|
1195
|
-
|
|
1196
|
-
console.log(
|
|
1197
|
-
`${c.skyBlue}Updating AgileFlow${c.reset} ${c.dim}v${fromVersion} → v${toVersion}${c.reset}`
|
|
1198
|
-
);
|
|
1199
|
-
const result = runUpdate();
|
|
1200
|
-
if (result.ok) {
|
|
1201
|
-
console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
|
|
1202
|
-
return true;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
// Check if this is a stale npm cache issue (ETARGET = version not found)
|
|
1206
|
-
if (result.error && (result.error.includes('ETARGET') || result.error.includes('notarget'))) {
|
|
1207
|
-
console.log(`${c.dim} Clearing npm cache and retrying...${c.reset}`);
|
|
1208
|
-
executeCommandSync('npm', ['cache', 'clean', '--force'], { timeout: 30000 });
|
|
1209
|
-
const retryResult = runUpdate();
|
|
1210
|
-
if (retryResult.ok) {
|
|
1211
|
-
console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
|
|
1212
|
-
return true;
|
|
1213
|
-
}
|
|
1214
|
-
console.log(`${c.peach}Auto-update failed after cache clean${c.reset}`);
|
|
1215
|
-
console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
|
|
1216
|
-
return false;
|
|
1217
|
-
}
|
|
1218
|
-
console.log(`${c.peach}Auto-update failed${c.reset}`);
|
|
1219
|
-
console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
|
|
1220
|
-
return false;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
/**
|
|
1224
|
-
* Spawn auto-update in a detached background process
|
|
1225
|
-
* This allows the welcome hook to return immediately while the update runs
|
|
1226
|
-
*
|
|
1227
|
-
* @param {string} rootDir - Project root directory
|
|
1228
|
-
* @param {string} fromVersion - Current version
|
|
1229
|
-
* @param {string} toVersion - Target version
|
|
1230
|
-
*/
|
|
1231
|
-
function spawnAutoUpdateInBackground(rootDir, fromVersion, toVersion) {
|
|
1232
|
-
// Track pending update in session-state.json
|
|
1233
|
-
try {
|
|
1234
|
-
const sessionStatePath = getSessionStatePath(rootDir);
|
|
1235
|
-
let state = {};
|
|
1236
|
-
if (fs.existsSync(sessionStatePath)) {
|
|
1237
|
-
state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
1238
|
-
}
|
|
1239
|
-
state.pending_update = {
|
|
1240
|
-
from: fromVersion,
|
|
1241
|
-
to: toVersion,
|
|
1242
|
-
started_at: new Date().toISOString(),
|
|
1243
|
-
};
|
|
1244
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
1245
|
-
} catch (e) {
|
|
1246
|
-
// Silently continue - tracking is optional
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
// Create detached subprocess that survives parent exit
|
|
1250
|
-
spawnBackground('npx', ['agileflow@latest', 'update', '--force'], { cwd: rootDir });
|
|
1251
|
-
|
|
1252
|
-
console.log(`${c.dim} Auto-update starting in background...${c.reset}`);
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* PERFORMANCE OPTIMIZATION: Fast expertise count (directory scan only)
|
|
1257
|
-
* Just counts expert directories without reading/validating each expertise.yaml.
|
|
1258
|
-
* Saves ~50-150ms by avoiding 29 file reads.
|
|
1259
|
-
* Full validation is available via /agileflow:validate-expertise command.
|
|
1260
|
-
*/
|
|
1261
|
-
function getExpertiseCountFast(rootDir) {
|
|
1262
|
-
const result = { total: 0, passed: 0, warnings: 0, failed: 0, issues: [], validated: false };
|
|
1263
|
-
|
|
1264
|
-
// Find experts directory
|
|
1265
|
-
let expertsDir = path.join(getAgileflowDir(rootDir), 'experts');
|
|
1266
|
-
if (!fs.existsSync(expertsDir)) {
|
|
1267
|
-
expertsDir = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'experts');
|
|
1268
|
-
}
|
|
1269
|
-
if (!fs.existsSync(expertsDir)) {
|
|
1270
|
-
return result;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
try {
|
|
1274
|
-
const domains = fs
|
|
1275
|
-
.readdirSync(expertsDir, { withFileTypes: true })
|
|
1276
|
-
.filter(d => d.isDirectory() && d.name !== 'templates');
|
|
1277
|
-
|
|
1278
|
-
result.total = domains.length;
|
|
1279
|
-
|
|
1280
|
-
// Quick check: just verify expertise.yaml exists in each directory
|
|
1281
|
-
// Full validation (staleness, required fields) deferred to separate command
|
|
1282
|
-
for (const domain of domains) {
|
|
1283
|
-
const filePath = path.join(expertsDir, domain.name, 'expertise.yaml');
|
|
1284
|
-
if (!fs.existsSync(filePath)) {
|
|
1285
|
-
result.failed++;
|
|
1286
|
-
result.issues.push(`${domain.name}: missing file`);
|
|
1287
|
-
} else {
|
|
1288
|
-
// Spot-check first few files for staleness (sample 3 max for speed)
|
|
1289
|
-
if (result.passed < 3) {
|
|
1290
|
-
try {
|
|
1291
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
1292
|
-
const lastUpdatedMatch = content.match(/^last_updated:\s*['"]?(\d{4}-\d{2}-\d{2})/m);
|
|
1293
|
-
if (lastUpdatedMatch) {
|
|
1294
|
-
const lastDate = new Date(lastUpdatedMatch[1]);
|
|
1295
|
-
const daysSince = Math.floor(
|
|
1296
|
-
(Date.now() - lastDate.getTime()) / (1000 * 60 * 60 * 24)
|
|
1297
|
-
);
|
|
1298
|
-
if (daysSince > 30) {
|
|
1299
|
-
result.warnings++;
|
|
1300
|
-
result.issues.push(`${domain.name}: stale (${daysSince}d)`);
|
|
1301
|
-
} else {
|
|
1302
|
-
result.passed++;
|
|
1303
|
-
}
|
|
1304
|
-
} else {
|
|
1305
|
-
result.passed++;
|
|
1306
|
-
}
|
|
1307
|
-
} catch (e) {
|
|
1308
|
-
result.passed++;
|
|
1309
|
-
}
|
|
1310
|
-
} else {
|
|
1311
|
-
// Assume rest are ok for fast display
|
|
1312
|
-
result.passed++;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
} catch (e) {
|
|
1317
|
-
// Silently fail
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
return result;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
/**
|
|
1324
|
-
* Check if installed commands are in sync with source.
|
|
1325
|
-
* Counts .md files in three directories:
|
|
1326
|
-
* - .agileflow/commands/ (core install)
|
|
1327
|
-
* - .claude/commands/agileflow/ excluding agents/ (IDE install)
|
|
1328
|
-
* - packages/cli/src/core/commands/ (dogfooding source, if exists)
|
|
1329
|
-
* Returns { sourceCount, coreCount, ideCount, stale, staleCoreInstall } or null on error.
|
|
1330
|
-
*/
|
|
1331
|
-
function checkCommandSync(rootDir) {
|
|
1332
|
-
try {
|
|
1333
|
-
const countMdFiles = dir => {
|
|
1334
|
-
if (!fs.existsSync(dir)) return 0;
|
|
1335
|
-
return fs.readdirSync(dir, { recursive: true }).filter(f => String(f).endsWith('.md')).length;
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
const agileflowDir = getAgileflowDir(rootDir);
|
|
1339
|
-
const coreCount = countMdFiles(path.join(agileflowDir, 'commands'));
|
|
1340
|
-
|
|
1341
|
-
// IDE install: .claude/commands/agileflow/ excluding agents/ subdir
|
|
1342
|
-
const ideDir = path.join(rootDir, '.claude', 'commands', 'agileflow');
|
|
1343
|
-
let ideCount = 0;
|
|
1344
|
-
if (fs.existsSync(ideDir)) {
|
|
1345
|
-
ideCount = fs.readdirSync(ideDir, { recursive: true }).filter(f => {
|
|
1346
|
-
const s = String(f);
|
|
1347
|
-
return s.endsWith('.md') && !s.startsWith('agents/') && !s.startsWith('agents\\');
|
|
1348
|
-
}).length;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
// Dogfooding source (only exists in the AgileFlow repo itself)
|
|
1352
|
-
const sourceDir = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'commands');
|
|
1353
|
-
const sourceCount = countMdFiles(sourceDir);
|
|
1354
|
-
|
|
1355
|
-
// Determine staleness
|
|
1356
|
-
const referenceCount = sourceCount > 0 ? sourceCount : coreCount;
|
|
1357
|
-
const stale = referenceCount > 0 && ideCount < referenceCount;
|
|
1358
|
-
const staleCoreInstall = sourceCount > 0 && coreCount < sourceCount;
|
|
1359
|
-
|
|
1360
|
-
return { sourceCount, coreCount, ideCount, stale, staleCoreInstall };
|
|
1361
|
-
} catch (e) {
|
|
1362
|
-
return null;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// Full validation function (kept for /agileflow:validate-expertise command)
|
|
1367
|
-
function validateExpertise(rootDir) {
|
|
1368
|
-
const result = { total: 0, passed: 0, warnings: 0, failed: 0, issues: [] };
|
|
1369
|
-
|
|
1370
|
-
// Find experts directory
|
|
1371
|
-
let expertsDir = path.join(getAgileflowDir(rootDir), 'experts');
|
|
1372
|
-
if (!fs.existsSync(expertsDir)) {
|
|
1373
|
-
expertsDir = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'experts');
|
|
1374
|
-
}
|
|
1375
|
-
if (!fs.existsSync(expertsDir)) {
|
|
1376
|
-
return result; // No experts directory found
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
const STALE_DAYS = 30;
|
|
1380
|
-
const MAX_LINES = 200;
|
|
1381
|
-
|
|
1382
|
-
try {
|
|
1383
|
-
const domains = fs
|
|
1384
|
-
.readdirSync(expertsDir, { withFileTypes: true })
|
|
1385
|
-
.filter(d => d.isDirectory() && d.name !== 'templates')
|
|
1386
|
-
.map(d => d.name);
|
|
1387
|
-
|
|
1388
|
-
for (const domain of domains) {
|
|
1389
|
-
const filePath = path.join(expertsDir, domain, 'expertise.yaml');
|
|
1390
|
-
if (!fs.existsSync(filePath)) {
|
|
1391
|
-
result.total++;
|
|
1392
|
-
result.failed++;
|
|
1393
|
-
result.issues.push(`${domain}: missing file`);
|
|
1394
|
-
continue;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
result.total++;
|
|
1398
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
1399
|
-
const lines = content.split('\n');
|
|
1400
|
-
let status = 'pass';
|
|
1401
|
-
let issue = '';
|
|
1402
|
-
|
|
1403
|
-
// Check required fields (use multiline flag)
|
|
1404
|
-
const hasVersion = /^version:/m.test(content);
|
|
1405
|
-
const hasDomain = /^domain:/m.test(content);
|
|
1406
|
-
const hasLastUpdated = /^last_updated:/m.test(content);
|
|
1407
|
-
|
|
1408
|
-
if (!hasVersion || !hasDomain || !hasLastUpdated) {
|
|
1409
|
-
status = 'fail';
|
|
1410
|
-
issue = 'missing required fields';
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
// Check staleness
|
|
1414
|
-
const lastUpdatedMatch = content.match(/^last_updated:\s*['"]?(\d{4}-\d{2}-\d{2})/m);
|
|
1415
|
-
if (lastUpdatedMatch && status !== 'fail') {
|
|
1416
|
-
const lastDate = new Date(lastUpdatedMatch[1]);
|
|
1417
|
-
const daysSince = Math.floor((Date.now() - lastDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
1418
|
-
if (daysSince > STALE_DAYS) {
|
|
1419
|
-
status = 'warn';
|
|
1420
|
-
issue = `stale (${daysSince}d)`;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
// Check file size
|
|
1425
|
-
if (lines.length > MAX_LINES && status === 'pass') {
|
|
1426
|
-
status = 'warn';
|
|
1427
|
-
issue = `large (${lines.length} lines)`;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
if (status === 'pass') {
|
|
1431
|
-
result.passed++;
|
|
1432
|
-
} else if (status === 'warn') {
|
|
1433
|
-
result.warnings++;
|
|
1434
|
-
result.issues.push(`${domain}: ${issue}`);
|
|
1435
|
-
} else {
|
|
1436
|
-
result.failed++;
|
|
1437
|
-
result.issues.push(`${domain}: ${issue}`);
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
} catch (e) {
|
|
1441
|
-
// Silently fail
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
return result;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
function getFeatureVersions(rootDir) {
|
|
1448
|
-
const result = {
|
|
1449
|
-
hooks: { version: null, outdated: false },
|
|
1450
|
-
archival: { version: null, outdated: false },
|
|
1451
|
-
statusline: { version: null, outdated: false },
|
|
1452
|
-
precompact: { version: null, outdated: false },
|
|
1453
|
-
};
|
|
1454
|
-
|
|
1455
|
-
// Minimum compatible versions for each feature
|
|
1456
|
-
const minVersions = {
|
|
1457
|
-
hooks: '2.35.0',
|
|
1458
|
-
archival: '2.35.0',
|
|
1459
|
-
statusline: '2.35.0',
|
|
1460
|
-
precompact: '2.40.0', // Multi-command support
|
|
1461
|
-
};
|
|
1462
|
-
|
|
1463
|
-
try {
|
|
1464
|
-
const metadataPath = getMetadataPath(rootDir);
|
|
1465
|
-
if (fs.existsSync(metadataPath)) {
|
|
1466
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
1467
|
-
|
|
1468
|
-
for (const feature of Object.keys(result)) {
|
|
1469
|
-
if (metadata.features?.[feature]?.configured_version) {
|
|
1470
|
-
result[feature].version = metadata.features[feature].configured_version;
|
|
1471
|
-
result[feature].outdated =
|
|
1472
|
-
compareVersions(result[feature].version, minVersions[feature]) < 0;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
} catch (e) {
|
|
1477
|
-
log.debug('getFeatureVersions:', e?.message || String(e));
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
return result;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
function pad(str, len, align = 'left') {
|
|
1484
|
-
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
1485
|
-
const diff = len - stripped.length;
|
|
1486
|
-
if (diff <= 0) return str;
|
|
1487
|
-
if (align === 'right') return ' '.repeat(diff) + str;
|
|
1488
|
-
if (align === 'center')
|
|
1489
|
-
return ' '.repeat(Math.floor(diff / 2)) + str + ' '.repeat(Math.ceil(diff / 2));
|
|
1490
|
-
return str + ' '.repeat(diff);
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
// Truncate string to max length, respecting ANSI codes
|
|
1494
|
-
function truncate(str, maxLen, suffix = '..') {
|
|
1495
|
-
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
1496
|
-
if (stripped.length <= maxLen) return str;
|
|
1497
|
-
|
|
1498
|
-
// Find position in original string that corresponds to maxLen - suffix.length visible chars
|
|
1499
|
-
const targetLen = maxLen - suffix.length;
|
|
1500
|
-
let visibleCount = 0;
|
|
1501
|
-
let cutIndex = 0;
|
|
1502
|
-
let inEscape = false;
|
|
1503
|
-
|
|
1504
|
-
for (let i = 0; i < str.length; i++) {
|
|
1505
|
-
if (str[i] === '\x1b') {
|
|
1506
|
-
inEscape = true;
|
|
1507
|
-
} else if (inEscape && str[i] === 'm') {
|
|
1508
|
-
inEscape = false;
|
|
1509
|
-
} else if (!inEscape) {
|
|
1510
|
-
visibleCount++;
|
|
1511
|
-
if (visibleCount >= targetLen) {
|
|
1512
|
-
cutIndex = i + 1;
|
|
1513
|
-
break;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
return str.substring(0, cutIndex) + suffix;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function formatTable(
|
|
1522
|
-
info,
|
|
1523
|
-
archival,
|
|
1524
|
-
session,
|
|
1525
|
-
precompact,
|
|
1526
|
-
parallelSessions,
|
|
1527
|
-
updateInfo = {},
|
|
1528
|
-
expertise = {},
|
|
1529
|
-
damageControl = {},
|
|
1530
|
-
agentTeamsInfo = {},
|
|
1531
|
-
scaleDetection = {}
|
|
1532
|
-
) {
|
|
1533
|
-
const W = 58; // inner width (total table = W + 2 = 60)
|
|
1534
|
-
const R = W - 25; // right column width (33 chars) to match total of 60
|
|
1535
|
-
const lines = [];
|
|
1536
|
-
|
|
1537
|
-
// Helper to create a row (auto-truncates right content to fit)
|
|
1538
|
-
const row = (left, right, leftColor = '', rightColor = '') => {
|
|
1539
|
-
const leftStr = `${leftColor}${left}${leftColor ? c.reset : ''}`;
|
|
1540
|
-
const rightTrunc = truncate(right, R);
|
|
1541
|
-
const rightStr = `${rightColor}${rightTrunc}${rightColor ? c.reset : ''}`;
|
|
1542
|
-
return `${c.dim}${box.v}${c.reset} ${pad(leftStr, 20)} ${c.dim}${box.v}${c.reset} ${pad(rightStr, R)} ${c.dim}${box.v}${c.reset}`;
|
|
1543
|
-
};
|
|
1544
|
-
|
|
1545
|
-
// Helper for full-width row (spans both columns)
|
|
1546
|
-
// Content width = W - 2 (for the two spaces after │ and before │)
|
|
1547
|
-
const fullRow = (content, color = '') => {
|
|
1548
|
-
const contentStr = `${color}${content}${color ? c.reset : ''}`;
|
|
1549
|
-
return `${c.dim}${box.v}${c.reset} ${pad(contentStr, W - 2)} ${c.dim}${box.v}${c.reset}`;
|
|
1550
|
-
};
|
|
1551
|
-
|
|
1552
|
-
// Two-column dividers: ├ + 22 dashes + ┼ + 35 dashes + ┤ = 60 total
|
|
1553
|
-
const divider = () =>
|
|
1554
|
-
`${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 23)}${box.rT}${c.reset}`;
|
|
1555
|
-
// Full-width divider: ├ + 58 dashes + ┤ = 60 total
|
|
1556
|
-
const fullDivider = () => `${c.dim}${box.lT}${box.h.repeat(W)}${box.rT}${c.reset}`;
|
|
1557
|
-
// Transition: full-width TO two-column
|
|
1558
|
-
const splitDivider = () =>
|
|
1559
|
-
`${c.dim}${box.lT}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 23)}${box.rT}${c.reset}`;
|
|
1560
|
-
// Transition: two-column TO full-width
|
|
1561
|
-
const mergeDivider = () =>
|
|
1562
|
-
`${c.dim}${box.lT}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 23)}${box.rT}${c.reset}`;
|
|
1563
|
-
// Borders
|
|
1564
|
-
const topBorder = `${c.dim}${box.tl}${box.h.repeat(W)}${box.tr}${c.reset}`;
|
|
1565
|
-
const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 23)}${box.br}${c.reset}`;
|
|
1566
|
-
|
|
1567
|
-
// Header with version and optional update indicator
|
|
1568
|
-
// Use vibrant colors for branch
|
|
1569
|
-
const branchColor =
|
|
1570
|
-
info.branch === 'main' ? c.mintGreen : info.branch.startsWith('fix') ? c.coral : c.skyBlue;
|
|
1571
|
-
|
|
1572
|
-
// Build version string with update status (vibrant colors)
|
|
1573
|
-
let versionStr = `v${info.version}`;
|
|
1574
|
-
if (updateInfo.justUpdated && updateInfo.previousVersion) {
|
|
1575
|
-
versionStr = `v${info.version} ${c.mintGreen}✓${c.reset}${c.slate} (was v${updateInfo.previousVersion})`;
|
|
1576
|
-
} else if (updateInfo.available && updateInfo.latest) {
|
|
1577
|
-
versionStr = `v${info.version} ${c.amber}↑${updateInfo.latest}${c.reset}`;
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
// Calculate remaining space for branch
|
|
1581
|
-
const versionVisibleLen = updateInfo.justUpdated
|
|
1582
|
-
? info.version.length + 20 + (updateInfo.previousVersion?.length || 0)
|
|
1583
|
-
: updateInfo.available
|
|
1584
|
-
? info.version.length + 3 + (updateInfo.latest?.length || 0)
|
|
1585
|
-
: info.version.length;
|
|
1586
|
-
const maxBranchLen = W - 1 - 15 - versionVisibleLen;
|
|
1587
|
-
const branchDisplay =
|
|
1588
|
-
info.branch.length > maxBranchLen
|
|
1589
|
-
? info.branch.substring(0, Math.max(5, maxBranchLen - 2)) + '..'
|
|
1590
|
-
: info.branch;
|
|
1591
|
-
|
|
1592
|
-
const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}${versionStr}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
|
|
1593
|
-
const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 2)} ${c.dim}${box.v}${c.reset}`;
|
|
1594
|
-
|
|
1595
|
-
lines.push(topBorder);
|
|
1596
|
-
lines.push(headerLine);
|
|
1597
|
-
|
|
1598
|
-
// Show update available notification (using vibrant colors)
|
|
1599
|
-
if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
|
|
1600
|
-
lines.push(fullDivider());
|
|
1601
|
-
lines.push(
|
|
1602
|
-
fullRow(
|
|
1603
|
-
`${c.amber}↑${c.reset} Update available: ${c.softGold}v${updateInfo.latest}${c.reset}`,
|
|
1604
|
-
''
|
|
1605
|
-
)
|
|
1606
|
-
);
|
|
1607
|
-
lines.push(fullRow(` Run: ${c.skyBlue}npx agileflow update${c.reset}`, ''));
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// Always show "What's new" section with current version changelog
|
|
1611
|
-
// Get changelog entries for current version (even if not just updated)
|
|
1612
|
-
const changelogEntries =
|
|
1613
|
-
updateInfo.changelog && updateInfo.changelog.length > 0
|
|
1614
|
-
? updateInfo.changelog
|
|
1615
|
-
: getChangelogEntries(info.version);
|
|
1616
|
-
|
|
1617
|
-
if (changelogEntries && changelogEntries.length > 0) {
|
|
1618
|
-
lines.push(fullDivider());
|
|
1619
|
-
const headerText = updateInfo.justUpdated
|
|
1620
|
-
? `${c.mintGreen}✨${c.reset} Just updated to ${c.softGold}v${info.version}${c.reset}:`
|
|
1621
|
-
: `${c.teal}📋${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`;
|
|
1622
|
-
lines.push(fullRow(headerText, ''));
|
|
1623
|
-
for (const entry of changelogEntries.slice(0, 2)) {
|
|
1624
|
-
lines.push(fullRow(` ${c.teal}•${c.reset} ${truncate(entry, W - 6)}`, ''));
|
|
1625
|
-
}
|
|
1626
|
-
lines.push(fullRow(` Run ${c.skyBlue}/agileflow:whats-new${c.reset} for full changelog`, ''));
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
// Transition from full-width sections to two-column stories section
|
|
1630
|
-
lines.push(splitDivider());
|
|
1631
|
-
|
|
1632
|
-
// Stories section (always colorful labels like obtain-context)
|
|
1633
|
-
lines.push(
|
|
1634
|
-
row(
|
|
1635
|
-
'In Progress',
|
|
1636
|
-
info.wipCount > 0 ? `${info.wipCount}` : '0',
|
|
1637
|
-
c.peach,
|
|
1638
|
-
info.wipCount > 0 ? c.peach : c.dim
|
|
1639
|
-
)
|
|
1640
|
-
);
|
|
1641
|
-
lines.push(
|
|
1642
|
-
row(
|
|
1643
|
-
'Blocked',
|
|
1644
|
-
info.blockedCount > 0 ? `${info.blockedCount}` : '0',
|
|
1645
|
-
c.coral,
|
|
1646
|
-
info.blockedCount > 0 ? c.coral : c.dim
|
|
1647
|
-
)
|
|
1648
|
-
);
|
|
1649
|
-
lines.push(
|
|
1650
|
-
row(
|
|
1651
|
-
'Ready',
|
|
1652
|
-
info.readyCount > 0 ? `${info.readyCount}` : '0',
|
|
1653
|
-
c.skyBlue,
|
|
1654
|
-
info.readyCount > 0 ? c.skyBlue : c.dim
|
|
1655
|
-
)
|
|
1656
|
-
);
|
|
1657
|
-
const completedColor = `${c.bold}${c.mintGreen}`;
|
|
1658
|
-
lines.push(
|
|
1659
|
-
row(
|
|
1660
|
-
'Completed',
|
|
1661
|
-
info.completedCount > 0 ? `${info.completedCount}` : '0',
|
|
1662
|
-
completedColor,
|
|
1663
|
-
info.completedCount > 0 ? completedColor : c.dim
|
|
1664
|
-
)
|
|
1665
|
-
);
|
|
1666
|
-
|
|
1667
|
-
lines.push(divider());
|
|
1668
|
-
|
|
1669
|
-
// System section (colorful labels like obtain-context)
|
|
1670
|
-
if (archival.disabled) {
|
|
1671
|
-
lines.push(row('Auto-archival', 'disabled', c.lavender, c.slate));
|
|
1672
|
-
} else if (archival.skippedByScale) {
|
|
1673
|
-
lines.push(row('Auto-archival', 'skipped (small project)', c.lavender, c.dim));
|
|
1674
|
-
} else {
|
|
1675
|
-
const archivalStatus =
|
|
1676
|
-
archival.archived > 0 ? `archived ${archival.archived} stories` : `nothing to archive`;
|
|
1677
|
-
lines.push(
|
|
1678
|
-
row('Auto-archival', archivalStatus, c.lavender, archival.archived > 0 ? c.mintGreen : c.dim)
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
// Session cleanup
|
|
1683
|
-
let sessionStatus, sessionColor;
|
|
1684
|
-
if (session.preserved) {
|
|
1685
|
-
sessionStatus = `preserved ${session.commandNames.length} command(s)`;
|
|
1686
|
-
sessionColor = c.mintGreen;
|
|
1687
|
-
} else if (session.cleared > 0) {
|
|
1688
|
-
sessionStatus = `cleared ${session.cleared} command(s)`;
|
|
1689
|
-
sessionColor = c.mintGreen;
|
|
1690
|
-
} else {
|
|
1691
|
-
sessionStatus = `clean`;
|
|
1692
|
-
sessionColor = c.dim;
|
|
1693
|
-
}
|
|
1694
|
-
lines.push(row('Session state', sessionStatus, c.lavender, sessionColor));
|
|
1695
|
-
|
|
1696
|
-
// PreCompact status with version check
|
|
1697
|
-
if (precompact.configured && precompact.scriptExists) {
|
|
1698
|
-
if (precompact.outdated) {
|
|
1699
|
-
const verStr = precompact.version ? ` (v${precompact.version})` : '';
|
|
1700
|
-
lines.push(row('Context preserve', `outdated${verStr}`, c.peach, c.peach));
|
|
1701
|
-
} else if (session.commandNames && session.commandNames.length > 0) {
|
|
1702
|
-
// Show the preserved command names
|
|
1703
|
-
const cmdDisplay = session.commandNames.map(n => `/agileflow:${n}`).join(', ');
|
|
1704
|
-
lines.push(row('Context preserve', cmdDisplay, c.lavender, c.mintGreen));
|
|
1705
|
-
} else {
|
|
1706
|
-
lines.push(row('Context preserve', 'ready', c.lavender, c.dim));
|
|
1707
|
-
}
|
|
1708
|
-
} else if (precompact.configured) {
|
|
1709
|
-
lines.push(row('Context preserve', 'script missing', c.peach, c.peach));
|
|
1710
|
-
} else {
|
|
1711
|
-
lines.push(row('Context preserve', 'not configured', c.slate, c.slate));
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
// Parallel sessions status
|
|
1715
|
-
if (parallelSessions && parallelSessions.available) {
|
|
1716
|
-
if (parallelSessions.otherActive > 0) {
|
|
1717
|
-
const sessionStr = `⚠️ ${parallelSessions.otherActive} other active`;
|
|
1718
|
-
lines.push(row('Sessions', sessionStr, c.peach, c.peach));
|
|
1719
|
-
} else {
|
|
1720
|
-
const sessionStr = parallelSessions.currentId
|
|
1721
|
-
? `✓ Session ${parallelSessions.currentId} (only)`
|
|
1722
|
-
: '✓ Only session';
|
|
1723
|
-
lines.push(row('Sessions', sessionStr, c.lavender, c.mintGreen));
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
// Agent expertise validation (always show with color)
|
|
1728
|
-
if (expertise && expertise.total > 0) {
|
|
1729
|
-
if (expertise.failed > 0) {
|
|
1730
|
-
const expertStr = `❌ ${expertise.failed} failed, ${expertise.warnings} warnings`;
|
|
1731
|
-
lines.push(row('Expertise', expertStr, c.coral, c.coral));
|
|
1732
|
-
} else if (expertise.warnings > 0) {
|
|
1733
|
-
const expertStr = `⚠️ ${expertise.warnings} warnings (${expertise.passed} ok)`;
|
|
1734
|
-
lines.push(row('Expertise', expertStr, c.peach, c.peach));
|
|
1735
|
-
} else {
|
|
1736
|
-
lines.push(row('Expertise', `✓ ${expertise.total} valid`, c.lavender, c.mintGreen));
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
// Damage control status (PreToolUse hooks for dangerous command protection)
|
|
1741
|
-
if (damageControl && damageControl.configured) {
|
|
1742
|
-
if (!damageControl.scriptsOk) {
|
|
1743
|
-
lines.push(row('Damage control', '⚠️ scripts missing', c.coral, c.coral));
|
|
1744
|
-
} else {
|
|
1745
|
-
const levelStr = damageControl.level || 'standard';
|
|
1746
|
-
const patternStr =
|
|
1747
|
-
damageControl.patternCount > 0 ? `${damageControl.patternCount} patterns` : '';
|
|
1748
|
-
const dcStatus = `🛡️ ${levelStr}${patternStr ? ` (${patternStr})` : ''}`;
|
|
1749
|
-
lines.push(row('Damage control', dcStatus, c.lavender, c.mintGreen));
|
|
1750
|
-
}
|
|
1751
|
-
} else {
|
|
1752
|
-
lines.push(row('Damage control', 'not configured', c.slate, c.slate));
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
// Agent Teams status
|
|
1756
|
-
if (agentTeamsInfo && agentTeamsInfo.status === 'enabled') {
|
|
1757
|
-
lines.push(row('Agent Teams', `${agentTeamsInfo.value}`, c.lavender, c.mintGreen));
|
|
1758
|
-
} else if (agentTeamsInfo && agentTeamsInfo.status === 'fallback') {
|
|
1759
|
-
lines.push(row('Agent Teams', `${agentTeamsInfo.value}`, c.lavender, c.dim));
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
// Scale detection (EP-0033)
|
|
1763
|
-
if (scaleDetection && scaleDetection.scale) {
|
|
1764
|
-
const scaleColors = {
|
|
1765
|
-
micro: c.cyan,
|
|
1766
|
-
small: c.teal,
|
|
1767
|
-
medium: c.mintGreen,
|
|
1768
|
-
large: c.peach,
|
|
1769
|
-
enterprise: c.coral,
|
|
1770
|
-
};
|
|
1771
|
-
const scaleIcons = {
|
|
1772
|
-
micro: '◦',
|
|
1773
|
-
small: '○',
|
|
1774
|
-
medium: '◎',
|
|
1775
|
-
large: '●',
|
|
1776
|
-
enterprise: '◉',
|
|
1777
|
-
};
|
|
1778
|
-
const scale = scaleDetection.scale;
|
|
1779
|
-
const icon = scaleIcons[scale] || '◎';
|
|
1780
|
-
const label = scale.charAt(0).toUpperCase() + scale.slice(1);
|
|
1781
|
-
const cacheNote = scaleDetection.fromCache ? '' : ` (${scaleDetection.detection_ms}ms)`;
|
|
1782
|
-
lines.push(
|
|
1783
|
-
row('Scale', `${icon} ${label}${cacheNote}`, c.lavender, scaleColors[scale] || c.dim)
|
|
1784
|
-
);
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
lines.push(divider());
|
|
1788
|
-
|
|
1789
|
-
// Current story (colorful like obtain-context)
|
|
1790
|
-
if (info.currentStory) {
|
|
1791
|
-
lines.push(
|
|
1792
|
-
row(
|
|
1793
|
-
'Current',
|
|
1794
|
-
`${c.lightYellow}${info.currentStory.id}${c.reset}: ${info.currentStory.title}`,
|
|
1795
|
-
c.skyBlue,
|
|
1796
|
-
''
|
|
1797
|
-
)
|
|
1798
|
-
);
|
|
1799
|
-
} else {
|
|
1800
|
-
lines.push(row('Current', 'No active story', c.skyBlue, c.dim));
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
// Last commit (colorful like obtain-context)
|
|
1804
|
-
lines.push(
|
|
1805
|
-
row('Last commit', `${c.peach}${info.commit}${c.reset} ${info.lastCommit}`, c.lavender, '')
|
|
1806
|
-
);
|
|
1807
|
-
|
|
1808
|
-
lines.push(bottomBorder);
|
|
1809
|
-
|
|
1810
|
-
return lines.join('\n');
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
// Format session banner for non-main sessions
|
|
1814
|
-
function formatSessionBanner(parallelSessions) {
|
|
1815
|
-
if (!parallelSessions.available || parallelSessions.isMain) {
|
|
1816
|
-
return null;
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
const W = 62; // banner width
|
|
1820
|
-
const lines = [];
|
|
1821
|
-
|
|
1822
|
-
// Get display name
|
|
1823
|
-
const sessionName = parallelSessions.nickname
|
|
1824
|
-
? `SESSION ${parallelSessions.currentId} "${parallelSessions.nickname}"`
|
|
1825
|
-
: `SESSION ${parallelSessions.currentId}`;
|
|
1826
|
-
|
|
1827
|
-
lines.push(`${c.dim}${box.tl}${box.h.repeat(W)}${box.tr}${c.reset}`);
|
|
1828
|
-
lines.push(
|
|
1829
|
-
`${c.dim}${box.v}${c.reset} ${c.teal}${c.bold}${pad(sessionName, W - 2)}${c.reset} ${c.dim}${box.v}${c.reset}`
|
|
1830
|
-
);
|
|
1831
|
-
lines.push(
|
|
1832
|
-
`${c.dim}${box.v}${c.reset} ${c.slate}Branch:${c.reset} ${pad(parallelSessions.branch || 'unknown', W - 13)} ${c.dim}${box.v}${c.reset}`
|
|
1833
|
-
);
|
|
1834
|
-
|
|
1835
|
-
// Show relative path to main
|
|
1836
|
-
if (parallelSessions.sessionPath) {
|
|
1837
|
-
const relPath = path.relative(parallelSessions.sessionPath, parallelSessions.mainPath) || '.';
|
|
1838
|
-
lines.push(
|
|
1839
|
-
`${c.dim}${box.v}${c.reset} ${c.slate}Main at:${c.reset} ${pad(relPath, W - 14)} ${c.dim}${box.v}${c.reset}`
|
|
1840
|
-
);
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
lines.push(`${c.dim}${box.bl}${box.h.repeat(W)}${box.br}${c.reset}`);
|
|
1844
|
-
|
|
1845
|
-
return lines.join('\n');
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
// Main (synchronous - async work deferred to welcome-deferred.js)
|
|
1849
|
-
function main() {
|
|
1850
|
-
_mark('start');
|
|
1851
|
-
|
|
1852
|
-
// Start hook timer for metrics
|
|
1853
|
-
const timer = hookMetrics ? hookMetrics.startHookTimer('SessionStart', 'welcome') : null;
|
|
1854
|
-
|
|
1855
|
-
const rootDir = getProjectRoot();
|
|
1856
|
-
|
|
1857
|
-
// PERFORMANCE: Load all project files once into cache
|
|
1858
|
-
// This eliminates 6-8 duplicate file reads across functions
|
|
1859
|
-
const cache = loadProjectFiles(rootDir);
|
|
1860
|
-
_mark('loadFiles');
|
|
1861
|
-
|
|
1862
|
-
// ============================================
|
|
1863
|
-
// PHASE 1: INSTANT WELCOME (< 300ms)
|
|
1864
|
-
// All fast operations - no network, no auto-update
|
|
1865
|
-
// ============================================
|
|
1866
|
-
const info = getProjectInfo(rootDir, cache);
|
|
1867
|
-
_mark('getInfo');
|
|
1868
|
-
|
|
1869
|
-
// PERFORMANCE OPTIMIZATION (US-0356): Read scale from session-state cache
|
|
1870
|
-
// Avoids requiring scale-detector module + git subprocess when cache is fresh (< 5 min)
|
|
1871
|
-
let earlyScale = null;
|
|
1872
|
-
const cachedScale = cache?.sessionState?.scale_detection;
|
|
1873
|
-
const scaleFresh =
|
|
1874
|
-
cachedScale &&
|
|
1875
|
-
cachedScale.detected_at &&
|
|
1876
|
-
Date.now() - new Date(cachedScale.detected_at).getTime() < 300000; // 5 min
|
|
1877
|
-
if (scaleFresh) {
|
|
1878
|
-
earlyScale = { ...cachedScale, fromCache: true };
|
|
1879
|
-
} else {
|
|
1880
|
-
try {
|
|
1881
|
-
const scaleDetector = require('./lib/scale-detector');
|
|
1882
|
-
earlyScale = scaleDetector.detectScale({
|
|
1883
|
-
rootDir,
|
|
1884
|
-
statusJson: cache?.status,
|
|
1885
|
-
sessionState: cache?.sessionState,
|
|
1886
|
-
});
|
|
1887
|
-
} catch (e) {
|
|
1888
|
-
// Scale detection not available
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
const scaleRecommendations = earlyScale
|
|
1893
|
-
? (() => {
|
|
1894
|
-
try {
|
|
1895
|
-
return require('./lib/scale-detector').getScaleRecommendations(earlyScale.scale);
|
|
1896
|
-
} catch {
|
|
1897
|
-
return null;
|
|
1898
|
-
}
|
|
1899
|
-
})()
|
|
1900
|
-
: null;
|
|
1901
|
-
_mark('scale');
|
|
1902
|
-
|
|
1903
|
-
const archival =
|
|
1904
|
-
scaleRecommendations && scaleRecommendations.skipArchival
|
|
1905
|
-
? { ran: false, threshold: 0, archived: 0, remaining: 0, skippedByScale: true }
|
|
1906
|
-
: runArchival(rootDir, cache);
|
|
1907
|
-
_mark('archival');
|
|
1908
|
-
|
|
1909
|
-
const session = clearActiveCommands(rootDir, cache);
|
|
1910
|
-
_mark('clearCmds');
|
|
1911
|
-
|
|
1912
|
-
const precompact = checkPreCompact(rootDir, cache);
|
|
1913
|
-
_mark('precompact');
|
|
1914
|
-
|
|
1915
|
-
// PERFORMANCE OPTIMIZATION (US-0356): Use lightweight session check
|
|
1916
|
-
// Reads registry.json + lock files directly (~30 lines) instead of
|
|
1917
|
-
// requiring the full session-manager module chain (~3,600 lines across 8 modules).
|
|
1918
|
-
// Full session registration deferred to Phase 3 (welcome-deferred.js).
|
|
1919
|
-
const parallelSessions = checkParallelSessionsFast(rootDir);
|
|
1920
|
-
_mark('sessions');
|
|
1921
|
-
|
|
1922
|
-
// PERFORMANCE OPTIMIZATION (US-0356): Defer expertise count to Phase 3
|
|
1923
|
-
// Directory scan with file reads is non-critical for the table.
|
|
1924
|
-
// Use cached result from session-state if available, otherwise show placeholder.
|
|
1925
|
-
let expertise;
|
|
1926
|
-
const cachedExpertise = cache?.sessionState?.expertise_count;
|
|
1927
|
-
const validExpertiseCache =
|
|
1928
|
-
cachedExpertise &&
|
|
1929
|
-
cachedExpertise.total > 0 &&
|
|
1930
|
-
typeof cachedExpertise.passed === 'number' &&
|
|
1931
|
-
typeof cachedExpertise.warnings === 'number' &&
|
|
1932
|
-
typeof cachedExpertise.failed === 'number';
|
|
1933
|
-
if (validExpertiseCache) {
|
|
1934
|
-
expertise = cachedExpertise;
|
|
1935
|
-
} else {
|
|
1936
|
-
expertise = getExpertiseCountFast(rootDir);
|
|
1937
|
-
}
|
|
1938
|
-
_mark('expertise');
|
|
1939
|
-
|
|
1940
|
-
const commandSync = checkCommandSync(rootDir);
|
|
1941
|
-
_mark('cmdSync');
|
|
1942
|
-
|
|
1943
|
-
const damageControl = checkDamageControl(rootDir, cache);
|
|
1944
|
-
_mark('damageCtl');
|
|
1945
|
-
|
|
1946
|
-
// Use early scale detection result (already computed for hook scheduling)
|
|
1947
|
-
const scaleDetection = earlyScale || { scale: 'medium' };
|
|
1948
|
-
|
|
1949
|
-
// PERFORMANCE OPTIMIZATION (US-0356): Read Agent Teams mode from metadata cache
|
|
1950
|
-
// Avoids requiring feature-flags module when metadata is already loaded.
|
|
1951
|
-
// Uses same { label, value, status } format as getAgentTeamsDisplayInfo().
|
|
1952
|
-
let agentTeamsInfo = {};
|
|
1953
|
-
const envTeams = process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
1954
|
-
const envTeamsEnabled = envTeams === '1' || envTeams === 'true' || envTeams === 'yes';
|
|
1955
|
-
const metaTeamsEnabled = cache?.metadata?.features?.agentTeams?.enabled === true;
|
|
1956
|
-
if (envTeamsEnabled || metaTeamsEnabled) {
|
|
1957
|
-
agentTeamsInfo = { label: 'Agent Teams', value: 'ENABLED (native)', status: 'enabled' };
|
|
1958
|
-
} else {
|
|
1959
|
-
agentTeamsInfo = { label: 'Agent Teams', value: 'subagent mode', status: 'fallback' };
|
|
1960
|
-
}
|
|
1961
|
-
_mark('agentTeams');
|
|
1962
|
-
|
|
1963
|
-
// Check if a previous background update completed successfully
|
|
1964
|
-
// This allows us to show "just updated" even for background updates
|
|
1965
|
-
let updateInfo = {};
|
|
1966
|
-
try {
|
|
1967
|
-
// Use already-loaded session state from cache instead of re-reading
|
|
1968
|
-
const state = cache?.sessionState;
|
|
1969
|
-
if (state?.pending_update) {
|
|
1970
|
-
const pendingUpdate = state.pending_update;
|
|
1971
|
-
const startedAt = new Date(pendingUpdate.started_at);
|
|
1972
|
-
const minutesAgo = (Date.now() - startedAt.getTime()) / (1000 * 60);
|
|
1973
|
-
|
|
1974
|
-
if (info.version === pendingUpdate.to) {
|
|
1975
|
-
updateInfo.justUpdated = true;
|
|
1976
|
-
updateInfo.previousVersion = pendingUpdate.from;
|
|
1977
|
-
updateInfo.changelog = getChangelogEntries(info.version);
|
|
1978
|
-
// Clear pending update
|
|
1979
|
-
const sessionStatePath = getSessionStatePath(rootDir);
|
|
1980
|
-
delete state.pending_update;
|
|
1981
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
1982
|
-
} else if (minutesAgo > 5) {
|
|
1983
|
-
const sessionStatePath = getSessionStatePath(rootDir);
|
|
1984
|
-
delete state.pending_update;
|
|
1985
|
-
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
} catch (e) {
|
|
1989
|
-
// Silently continue - pending update check is non-critical
|
|
1990
|
-
}
|
|
1991
|
-
_mark('updateInfo');
|
|
1992
|
-
|
|
1993
|
-
// PERFORMANCE OPTIMIZATION (US-0356): Defer config staleness to Phase 3
|
|
1994
|
-
// Only produces a notification; not needed for the main table.
|
|
1995
|
-
// Read cached result from session-state if available.
|
|
1996
|
-
let configStaleness = { outdated: false, autoApply: false };
|
|
1997
|
-
let configAutoApplied = 0;
|
|
1998
|
-
const cachedConfigStaleness = cache?.sessionState?.config_staleness;
|
|
1999
|
-
const configCacheAge = cachedConfigStaleness?.cached_at
|
|
2000
|
-
? Date.now() - new Date(cachedConfigStaleness.cached_at).getTime()
|
|
2001
|
-
: Infinity;
|
|
2002
|
-
const configCacheFresh = configCacheAge < 300000; // 5 min TTL
|
|
2003
|
-
if (cachedConfigStaleness && configCacheFresh) {
|
|
2004
|
-
configStaleness = cachedConfigStaleness;
|
|
2005
|
-
} else {
|
|
2006
|
-
try {
|
|
2007
|
-
configStaleness = checkConfigStaleness(rootDir, info.version, cache);
|
|
2008
|
-
|
|
2009
|
-
// Auto-apply new options if profile is "full" (only auto-applyable ones)
|
|
2010
|
-
if (configStaleness.autoApply && configStaleness.autoApplyOptions?.length > 0) {
|
|
2011
|
-
configAutoApplied = autoApplyConfigOptions(rootDir, configStaleness.autoApplyOptions);
|
|
2012
|
-
if (configAutoApplied > 0) {
|
|
2013
|
-
configStaleness.newOptions = configStaleness.newOptions.filter(o => !o.autoApplyable);
|
|
2014
|
-
configStaleness.newOptionsCount = configStaleness.newOptions.length;
|
|
2015
|
-
if (configStaleness.newOptionsCount === 0) {
|
|
2016
|
-
configStaleness.outdated = false;
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
} catch (e) {
|
|
2021
|
-
// Config check failed - continue without it
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
_mark('configStale');
|
|
2025
|
-
|
|
2026
|
-
// Check tmux availability (only if tmuxAutoSpawn is enabled)
|
|
2027
|
-
let tmuxCheck = { available: true };
|
|
2028
|
-
const tmuxAutoSpawnEnabled = cache?.metadata?.features?.tmuxAutoSpawn?.enabled !== false;
|
|
2029
|
-
if (tmuxAutoSpawnEnabled) {
|
|
2030
|
-
tmuxCheck = checkTmuxAvailability(cache);
|
|
2031
|
-
}
|
|
2032
|
-
_mark('tmux');
|
|
2033
|
-
|
|
2034
|
-
// Show session banner FIRST if in a non-main session
|
|
2035
|
-
const sessionBanner = formatSessionBanner(parallelSessions);
|
|
2036
|
-
if (sessionBanner) {
|
|
2037
|
-
console.log(sessionBanner);
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
console.log(
|
|
2041
|
-
formatTable(
|
|
2042
|
-
info,
|
|
2043
|
-
archival,
|
|
2044
|
-
session,
|
|
2045
|
-
precompact,
|
|
2046
|
-
parallelSessions,
|
|
2047
|
-
updateInfo,
|
|
2048
|
-
expertise,
|
|
2049
|
-
damageControl,
|
|
2050
|
-
agentTeamsInfo,
|
|
2051
|
-
scaleDetection
|
|
2052
|
-
)
|
|
2053
|
-
);
|
|
2054
|
-
|
|
2055
|
-
// ============================================
|
|
2056
|
-
// PHASE 2: FAST POST-TABLE NOTIFICATIONS + DEFERRED BACKGROUND WORK
|
|
2057
|
-
// Only instant operations here. Expensive work deferred to welcome-deferred.js
|
|
2058
|
-
// ============================================
|
|
2059
|
-
|
|
2060
|
-
// Display deferred warnings from previous session's background work
|
|
2061
|
-
displayDeferredWarnings(rootDir);
|
|
2062
|
-
|
|
2063
|
-
// Check update cache (1-hour TTL) - instant if cached, skipped if stale
|
|
2064
|
-
let skipUpdateInDeferred = false;
|
|
2065
|
-
try {
|
|
2066
|
-
const cachedUpdate = getUpdateFromCache(cache);
|
|
2067
|
-
if (cachedUpdate) {
|
|
2068
|
-
skipUpdateInDeferred = true;
|
|
2069
|
-
// Use cached result for notification
|
|
2070
|
-
if (cachedUpdate.available && cachedUpdate.latest && !updateInfo.justUpdated) {
|
|
2071
|
-
console.log('');
|
|
2072
|
-
console.log(
|
|
2073
|
-
`${c.amber}↑ Update available:${c.reset} v${info.version} → ${c.softGold}v${cachedUpdate.latest}${c.reset}`
|
|
2074
|
-
);
|
|
2075
|
-
console.log(` Run: ${c.skyBlue}npx agileflow update${c.reset}`);
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
// If no cache or stale, the deferred script will check and cache the result
|
|
2079
|
-
} catch (e) {
|
|
2080
|
-
// Cache check failed, deferred script will handle it
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
// Show config auto-apply confirmation (for "full" profile)
|
|
2084
|
-
if (configAutoApplied > 0) {
|
|
2085
|
-
console.log('');
|
|
2086
|
-
console.log(
|
|
2087
|
-
`${c.mintGreen}✨ Auto-applied ${configAutoApplied} new config option(s)${c.reset}`
|
|
2088
|
-
);
|
|
2089
|
-
console.log(` ${c.slate}Profile "full" enables all new features automatically.${c.reset}`);
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
// Show config staleness notification (for custom profiles)
|
|
2093
|
-
if (configStaleness.outdated && configStaleness.newOptionsCount > 0) {
|
|
2094
|
-
console.log('');
|
|
2095
|
-
console.log(
|
|
2096
|
-
`${c.amber}⚙️ ${configStaleness.newOptionsCount} new configuration option(s) available${c.reset}`
|
|
2097
|
-
);
|
|
2098
|
-
for (const opt of configStaleness.newOptions.slice(0, 3)) {
|
|
2099
|
-
console.log(` ${c.dim}• ${opt.description}${c.reset}`);
|
|
2100
|
-
}
|
|
2101
|
-
console.log(
|
|
2102
|
-
` ${c.slate}Run ${c.skyBlue}/agileflow:configure${c.reset}${c.slate} to enable them.${c.reset}`
|
|
2103
|
-
);
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
// Show command sync staleness notification
|
|
2107
|
-
if (commandSync && commandSync.stale) {
|
|
2108
|
-
const ref = commandSync.sourceCount > 0 ? commandSync.sourceCount : commandSync.coreCount;
|
|
2109
|
-
console.log('');
|
|
2110
|
-
console.log(
|
|
2111
|
-
`${c.amber}⚠️ ${ref - commandSync.ideCount} command(s) out of sync${c.reset} ${c.dim}(IDE has ${commandSync.ideCount}/${ref})${c.reset}`
|
|
2112
|
-
);
|
|
2113
|
-
if (commandSync.staleCoreInstall) {
|
|
2114
|
-
console.log(
|
|
2115
|
-
` ${c.dim}Core install also stale: ${commandSync.coreCount}/${commandSync.sourceCount}${c.reset}`
|
|
2116
|
-
);
|
|
2117
|
-
}
|
|
2118
|
-
console.log(` ${c.slate}Auto-syncing in background...${c.reset}`);
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// Show tmux installation notice if tmux auto-spawn is enabled but tmux not installed
|
|
2122
|
-
if (tmuxAutoSpawnEnabled && !tmuxCheck.available) {
|
|
2123
|
-
console.log('');
|
|
2124
|
-
console.log(
|
|
2125
|
-
`${c.amber}📦 tmux not installed${c.reset} ${c.dim}(enables parallel sessions in one terminal)${c.reset}`
|
|
2126
|
-
);
|
|
2127
|
-
|
|
2128
|
-
// Show platform-specific install command
|
|
2129
|
-
if (tmuxCheck.platform?.installCmd) {
|
|
2130
|
-
console.log(` ${c.slate}Install for ${tmuxCheck.platform.os}:${c.reset}`);
|
|
2131
|
-
console.log(` ${c.cyan}${tmuxCheck.platform.installCmd}${c.reset}`);
|
|
2132
|
-
console.log('');
|
|
2133
|
-
console.log(` ${c.dim}No sudo? Use: ${c.cyan}${tmuxCheck.noSudoCmd}${c.reset}`);
|
|
2134
|
-
} else {
|
|
2135
|
-
// Unknown platform - show all options
|
|
2136
|
-
console.log(` ${c.slate}Install with one of:${c.reset}`);
|
|
2137
|
-
console.log(` ${c.dim}• macOS:${c.reset} ${c.cyan}brew install tmux${c.reset}`);
|
|
2138
|
-
console.log(` ${c.dim}• Ubuntu:${c.reset} ${c.cyan}sudo apt install tmux${c.reset}`);
|
|
2139
|
-
console.log(` ${c.dim}• No sudo:${c.reset} ${c.cyan}${tmuxCheck.noSudoCmd}${c.reset}`);
|
|
2140
|
-
}
|
|
2141
|
-
console.log(
|
|
2142
|
-
` ${c.dim}Or disable this notice: ${c.skyBlue}/agileflow:configure --disable=tmuxautospawn${c.reset}`
|
|
2143
|
-
);
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
// Show warning and tip if other sessions are active (vibrant colors)
|
|
2147
|
-
if (parallelSessions.otherActive > 0) {
|
|
2148
|
-
console.log('');
|
|
2149
|
-
console.log(`${c.amber}⚠️ Other Claude session(s) active in this repo.${c.reset}`);
|
|
2150
|
-
console.log(
|
|
2151
|
-
`${c.slate} Run ${c.skyBlue}/agileflow:session:status${c.reset}${c.slate} to see all sessions.${c.reset}`
|
|
2152
|
-
);
|
|
2153
|
-
console.log(
|
|
2154
|
-
`${c.slate} Run ${c.skyBlue}/agileflow:session:new${c.reset}${c.slate} to create isolated workspace.${c.reset}`
|
|
2155
|
-
);
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
// Show detailed message if sessions were cleaned (VISIBLE - not hidden!)
|
|
2159
|
-
if (parallelSessions.cleaned > 0 && parallelSessions.cleanedSessions) {
|
|
2160
|
-
console.log('');
|
|
2161
|
-
console.log(`${c.amber}📋 Cleaned ${parallelSessions.cleaned} inactive session(s):${c.reset}`);
|
|
2162
|
-
parallelSessions.cleanedSessions.forEach(sess => {
|
|
2163
|
-
const name = sess.nickname ? `${sess.id} "${sess.nickname}"` : `Session ${sess.id}`;
|
|
2164
|
-
const reason = sess.reason === 'pid_dead' ? 'process ended' : sess.reason;
|
|
2165
|
-
console.log(` ${c.dim}└─ ${name} (${reason}, PID ${sess.pid})${c.reset}`);
|
|
2166
|
-
});
|
|
2167
|
-
console.log(
|
|
2168
|
-
` ${c.slate}Sessions are cleaned when their Claude Code process is no longer running.${c.reset}`
|
|
2169
|
-
);
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
_mark('postTable');
|
|
2173
|
-
|
|
2174
|
-
// ============================================
|
|
2175
|
-
// PHASE 3: SPAWN DEFERRED BACKGROUND WORK
|
|
2176
|
-
// Session health, process cleanup, story claiming, file tracking,
|
|
2177
|
-
// epic completion, ideation sync, automations, update check (if stale)
|
|
2178
|
-
// Also: full session registration, expertise re-scan, config staleness check
|
|
2179
|
-
// Results saved to session-state.json for next session display.
|
|
2180
|
-
// ============================================
|
|
2181
|
-
try {
|
|
2182
|
-
const deferredScript = path.join(__dirname, 'welcome-deferred.js');
|
|
2183
|
-
if (fs.existsSync(deferredScript)) {
|
|
2184
|
-
const deferredArgs = [deferredScript, rootDir, `--version=${info.version}`];
|
|
2185
|
-
if (skipUpdateInDeferred) {
|
|
2186
|
-
deferredArgs.push('--skip-update');
|
|
2187
|
-
}
|
|
2188
|
-
if (updateInfo.justUpdated) {
|
|
2189
|
-
deferredArgs.push('--just-updated');
|
|
2190
|
-
}
|
|
2191
|
-
// US-0356: Signal deferred to run full session registration and expertise scan
|
|
2192
|
-
deferredArgs.push('--run-session-register');
|
|
2193
|
-
deferredArgs.push('--run-expertise-scan');
|
|
2194
|
-
if (!cachedConfigStaleness || !configCacheFresh) {
|
|
2195
|
-
deferredArgs.push('--run-config-staleness');
|
|
2196
|
-
}
|
|
2197
|
-
spawnBackground('node', deferredArgs, { cwd: rootDir });
|
|
2198
|
-
}
|
|
2199
|
-
} catch (e) {
|
|
2200
|
-
// Deferred script spawn failed, non-critical
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
|
-
_mark('deferred');
|
|
2204
|
-
|
|
2205
|
-
// Spawn background command sync if stale
|
|
2206
|
-
if (commandSync && commandSync.stale) {
|
|
2207
|
-
try {
|
|
2208
|
-
const isDogfooding = commandSync.sourceCount > 0;
|
|
2209
|
-
if (isDogfooding) {
|
|
2210
|
-
const cliPath = path.join(rootDir, 'packages', 'cli', 'tools', 'cli', 'agileflow-cli.js');
|
|
2211
|
-
if (fs.existsSync(cliPath)) {
|
|
2212
|
-
spawnBackground('node', [cliPath, 'update', '--force', '-d', rootDir], { cwd: rootDir });
|
|
2213
|
-
}
|
|
2214
|
-
} else {
|
|
2215
|
-
spawnBackground('npx', ['agileflow', 'update', '--force'], { cwd: rootDir });
|
|
2216
|
-
}
|
|
2217
|
-
} catch (e) {
|
|
2218
|
-
// Command sync spawn failed, non-critical
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
_mark('cmdSyncSpawn');
|
|
2222
|
-
|
|
2223
|
-
// Record hook metrics
|
|
2224
|
-
if (timer && hookMetrics) {
|
|
2225
|
-
hookMetrics.recordHookMetrics(timer, 'success', null, { rootDir });
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
// Output profiling data if enabled
|
|
2229
|
-
_logTimings();
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
try {
|
|
2233
|
-
main();
|
|
2234
|
-
} catch (err) {
|
|
2235
|
-
log.error(err.message || String(err));
|
|
2236
|
-
// Record error in metrics if possible
|
|
2237
|
-
if (hookMetrics) {
|
|
2238
|
-
try {
|
|
2239
|
-
const rootDir = getProjectRoot();
|
|
2240
|
-
const timer = hookMetrics.startHookTimer('SessionStart', 'welcome');
|
|
2241
|
-
hookMetrics.recordHookMetrics(timer, 'error', err.message, { rootDir });
|
|
2242
|
-
} catch (e) {
|
|
2243
|
-
// Silently ignore metrics errors
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
}
|