@cleocode/core 2026.4.7 → 2026.4.11
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/dist/adapters/adapter-registry.js +64 -0
- package/dist/adapters/adapter-registry.js.map +1 -0
- package/dist/adapters/discovery.js +83 -0
- package/dist/adapters/discovery.js.map +1 -0
- package/dist/adapters/index.js +9 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/manager.js +260 -0
- package/dist/adapters/manager.js.map +1 -0
- package/dist/admin/export-tasks.js +171 -0
- package/dist/admin/export-tasks.js.map +1 -0
- package/dist/admin/export.js +103 -0
- package/dist/admin/export.js.map +1 -0
- package/dist/admin/help.js +106 -0
- package/dist/admin/help.js.map +1 -0
- package/dist/admin/import-tasks.js +182 -0
- package/dist/admin/import-tasks.js.map +1 -0
- package/dist/admin/import.js +129 -0
- package/dist/admin/import.js.map +1 -0
- package/dist/admin/index.js +13 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/adrs/find.js +134 -0
- package/dist/adrs/find.js.map +1 -0
- package/dist/adrs/index.js +15 -0
- package/dist/adrs/index.js.map +1 -0
- package/dist/adrs/link-pipeline.js +160 -0
- package/dist/adrs/link-pipeline.js.map +1 -0
- package/dist/adrs/list.js +43 -0
- package/dist/adrs/list.js.map +1 -0
- package/dist/adrs/parse.js +51 -0
- package/dist/adrs/parse.js.map +1 -0
- package/dist/adrs/show.js +22 -0
- package/dist/adrs/show.js.map +1 -0
- package/dist/adrs/sync.js +188 -0
- package/dist/adrs/sync.js.map +1 -0
- package/dist/adrs/types.js +9 -0
- package/dist/adrs/types.js.map +1 -0
- package/dist/adrs/validate.js +57 -0
- package/dist/adrs/validate.js.map +1 -0
- package/dist/agents/agent-registry.js +288 -0
- package/dist/agents/agent-registry.js.map +1 -0
- package/dist/agents/agent-schema.d.ts +2 -2
- package/dist/agents/agent-schema.js +82 -0
- package/dist/agents/agent-schema.js.map +1 -0
- package/dist/agents/capacity.js +116 -0
- package/dist/agents/capacity.js.map +1 -0
- package/dist/agents/execution-learning.js +474 -0
- package/dist/agents/execution-learning.js.map +1 -0
- package/dist/agents/health-monitor.js +217 -0
- package/dist/agents/health-monitor.js.map +1 -0
- package/dist/agents/index.js +29 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/registry.js +314 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/retry.js +215 -0
- package/dist/agents/retry.js.map +1 -0
- package/dist/audit-prune.js +94 -0
- package/dist/audit-prune.js.map +1 -0
- package/dist/audit.js +68 -0
- package/dist/audit.js.map +1 -0
- package/dist/backfill/index.js +229 -0
- package/dist/backfill/index.js.map +1 -0
- package/dist/bootstrap.js +344 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/caamp/adapter.js +259 -0
- package/dist/caamp/adapter.js.map +1 -0
- package/dist/caamp/capability-check.js +38 -0
- package/dist/caamp/capability-check.js.map +1 -0
- package/dist/caamp/index.js +21 -0
- package/dist/caamp/index.js.map +1 -0
- package/dist/caamp-init.js +16 -0
- package/dist/caamp-init.js.map +1 -0
- package/dist/cleo.js +322 -0
- package/dist/cleo.js.map +1 -0
- package/dist/code/index.js +10 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/outline.js +165 -0
- package/dist/code/outline.js.map +1 -0
- package/dist/code/parser.js +295 -0
- package/dist/code/parser.js.map +1 -0
- package/dist/code/search.js +135 -0
- package/dist/code/search.js.map +1 -0
- package/dist/code/unfold.js +155 -0
- package/dist/code/unfold.js.map +1 -0
- package/dist/codebase-map/analyzers/architecture.js +130 -0
- package/dist/codebase-map/analyzers/architecture.js.map +1 -0
- package/dist/codebase-map/analyzers/concerns.js +122 -0
- package/dist/codebase-map/analyzers/concerns.js.map +1 -0
- package/dist/codebase-map/analyzers/conventions.js +149 -0
- package/dist/codebase-map/analyzers/conventions.js.map +1 -0
- package/dist/codebase-map/analyzers/integrations.js +108 -0
- package/dist/codebase-map/analyzers/integrations.js.map +1 -0
- package/dist/codebase-map/analyzers/stack.js +117 -0
- package/dist/codebase-map/analyzers/stack.js.map +1 -0
- package/dist/codebase-map/analyzers/structure.js +137 -0
- package/dist/codebase-map/analyzers/structure.js.map +1 -0
- package/dist/codebase-map/analyzers/testing.js +118 -0
- package/dist/codebase-map/analyzers/testing.js.map +1 -0
- package/dist/codebase-map/index.js +57 -0
- package/dist/codebase-map/index.js.map +1 -0
- package/dist/codebase-map/store.js +122 -0
- package/dist/codebase-map/store.js.map +1 -0
- package/dist/codebase-map/summary.js +152 -0
- package/dist/codebase-map/summary.js.map +1 -0
- package/dist/compliance/index.js +288 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/protocol-enforcement.js +332 -0
- package/dist/compliance/protocol-enforcement.js.map +1 -0
- package/dist/compliance/protocol-rules.js +786 -0
- package/dist/compliance/protocol-rules.js.map +1 -0
- package/dist/compliance/protocol-types.js +79 -0
- package/dist/compliance/protocol-types.js.map +1 -0
- package/dist/compliance/store.js +53 -0
- package/dist/compliance/store.js.map +1 -0
- package/dist/conduit/conduit-client.js +107 -0
- package/dist/conduit/conduit-client.js.map +1 -0
- package/dist/conduit/factory.js +52 -0
- package/dist/conduit/factory.js.map +1 -0
- package/dist/conduit/http-transport.js +155 -0
- package/dist/conduit/http-transport.js.map +1 -0
- package/dist/conduit/index.js +15 -0
- package/dist/conduit/index.js.map +1 -0
- package/dist/conduit/local-transport.js +245 -0
- package/dist/conduit/local-transport.js.map +1 -0
- package/dist/conduit/sse-transport.js +299 -0
- package/dist/conduit/sse-transport.js.map +1 -0
- package/dist/config/build-config.js +29 -0
- package/dist/config/build-config.js.map +1 -0
- package/dist/config.js +401 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.js +18 -0
- package/dist/constants.js.map +1 -0
- package/dist/context/index.js +137 -0
- package/dist/context/index.js.map +1 -0
- package/dist/crypto/credentials.js +191 -0
- package/dist/crypto/credentials.js.map +1 -0
- package/dist/discovery.js +182 -0
- package/dist/discovery.js.map +1 -0
- package/dist/engine-result.js +12 -0
- package/dist/engine-result.js.map +1 -0
- package/dist/error-catalog.js +404 -0
- package/dist/error-catalog.js.map +1 -0
- package/dist/error-registry.js +393 -0
- package/dist/error-registry.js.map +1 -0
- package/dist/errors.js +167 -0
- package/dist/errors.js.map +1 -0
- package/dist/hooks/handlers/agent-hooks.js +106 -0
- package/dist/hooks/handlers/agent-hooks.js.map +1 -0
- package/dist/hooks/handlers/context-hooks.js +111 -0
- package/dist/hooks/handlers/context-hooks.js.map +1 -0
- package/dist/hooks/handlers/error-hooks.js +52 -0
- package/dist/hooks/handlers/error-hooks.js.map +1 -0
- package/dist/hooks/handlers/file-hooks.js +104 -0
- package/dist/hooks/handlers/file-hooks.js.map +1 -0
- package/dist/hooks/handlers/handler-helpers.js +61 -0
- package/dist/hooks/handlers/handler-helpers.js.map +1 -0
- package/dist/hooks/handlers/index.js +28 -0
- package/dist/hooks/handlers/index.js.map +1 -0
- package/dist/hooks/handlers/memory-bridge-refresh.js +42 -0
- package/dist/hooks/handlers/memory-bridge-refresh.js.map +1 -0
- package/dist/hooks/handlers/notification-hooks.js +62 -0
- package/dist/hooks/handlers/notification-hooks.js.map +1 -0
- package/dist/hooks/handlers/session-hooks.d.ts +21 -0
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.js +142 -0
- package/dist/hooks/handlers/session-hooks.js.map +1 -0
- package/dist/hooks/handlers/task-hooks.js +65 -0
- package/dist/hooks/handlers/task-hooks.js.map +1 -0
- package/dist/hooks/handlers/work-capture-hooks.js +165 -0
- package/dist/hooks/handlers/work-capture-hooks.js.map +1 -0
- package/dist/hooks/index.js +13 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/payload-schemas.d.ts +2 -2
- package/dist/hooks/payload-schemas.js +220 -0
- package/dist/hooks/payload-schemas.js.map +1 -0
- package/dist/hooks/provider-hooks.js +66 -0
- package/dist/hooks/provider-hooks.js.map +1 -0
- package/dist/hooks/registry.js +229 -0
- package/dist/hooks/registry.js.map +1 -0
- package/dist/hooks/types.js +66 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/hooks.js +136 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.js +3361 -3095
- package/dist/index.js.map +4 -4
- package/dist/init.js +852 -0
- package/dist/init.js.map +1 -0
- package/dist/inject/index.js +82 -0
- package/dist/inject/index.js.map +1 -0
- package/dist/injection.js +343 -0
- package/dist/injection.js.map +1 -0
- package/dist/intelligence/adaptive-validation.js +497 -0
- package/dist/intelligence/adaptive-validation.js.map +1 -0
- package/dist/intelligence/impact.js +675 -0
- package/dist/intelligence/impact.js.map +1 -0
- package/dist/intelligence/index.js +22 -0
- package/dist/intelligence/index.js.map +1 -0
- package/dist/intelligence/patterns.js +492 -0
- package/dist/intelligence/patterns.js.map +1 -0
- package/dist/intelligence/prediction.js +499 -0
- package/dist/intelligence/prediction.js.map +1 -0
- package/dist/intelligence/types.js +13 -0
- package/dist/intelligence/types.js.map +1 -0
- package/dist/internal.d.ts +7 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +299 -0
- package/dist/internal.js.map +1 -0
- package/dist/issue/create.js +121 -0
- package/dist/issue/create.js.map +1 -0
- package/dist/issue/diagnostics.js +59 -0
- package/dist/issue/diagnostics.js.map +1 -0
- package/dist/issue/index.js +10 -0
- package/dist/issue/index.js.map +1 -0
- package/dist/issue/template-parser.js +267 -0
- package/dist/issue/template-parser.js.map +1 -0
- package/dist/json-schema-validator.js +76 -0
- package/dist/json-schema-validator.js.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/retry.js +152 -0
- package/dist/lib/retry.js.map +1 -0
- package/dist/lib/tree-sitter-languages.js +71 -0
- package/dist/lib/tree-sitter-languages.js.map +1 -0
- package/dist/lifecycle/chain-composition.js +152 -0
- package/dist/lifecycle/chain-composition.js.map +1 -0
- package/dist/lifecycle/chain-store.js +246 -0
- package/dist/lifecycle/chain-store.js.map +1 -0
- package/dist/lifecycle/consolidate-rcasd.js +352 -0
- package/dist/lifecycle/consolidate-rcasd.js.map +1 -0
- package/dist/lifecycle/default-chain.js +176 -0
- package/dist/lifecycle/default-chain.js.map +1 -0
- package/dist/lifecycle/evidence.js +180 -0
- package/dist/lifecycle/evidence.js.map +1 -0
- package/dist/lifecycle/frontmatter.js +363 -0
- package/dist/lifecycle/frontmatter.js.map +1 -0
- package/dist/lifecycle/index.js +756 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle/pipeline.js +656 -0
- package/dist/lifecycle/pipeline.js.map +1 -0
- package/dist/lifecycle/rcasd-index.js +326 -0
- package/dist/lifecycle/rcasd-index.js.map +1 -0
- package/dist/lifecycle/rcasd-paths.js +220 -0
- package/dist/lifecycle/rcasd-paths.js.map +1 -0
- package/dist/lifecycle/resume.js +864 -0
- package/dist/lifecycle/resume.js.map +1 -0
- package/dist/lifecycle/stage-artifacts.js +94 -0
- package/dist/lifecycle/stage-artifacts.js.map +1 -0
- package/dist/lifecycle/stage-guidance.js +234 -0
- package/dist/lifecycle/stage-guidance.js.map +1 -0
- package/dist/lifecycle/stages.js +534 -0
- package/dist/lifecycle/stages.js.map +1 -0
- package/dist/lifecycle/state-machine.js +516 -0
- package/dist/lifecycle/state-machine.js.map +1 -0
- package/dist/lifecycle/tessera-engine.js +249 -0
- package/dist/lifecycle/tessera-engine.js.map +1 -0
- package/dist/logger.js +140 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/auto-extract.js +177 -0
- package/dist/memory/auto-extract.js.map +1 -0
- package/dist/memory/brain-embedding.js +66 -0
- package/dist/memory/brain-embedding.js.map +1 -0
- package/dist/memory/brain-lifecycle.js +298 -0
- package/dist/memory/brain-lifecycle.js.map +1 -0
- package/dist/memory/brain-links.js +161 -0
- package/dist/memory/brain-links.js.map +1 -0
- package/dist/memory/brain-maintenance.js +114 -0
- package/dist/memory/brain-maintenance.js.map +1 -0
- package/dist/memory/brain-migration.js +149 -0
- package/dist/memory/brain-migration.js.map +1 -0
- package/dist/memory/brain-reasoning.js +215 -0
- package/dist/memory/brain-reasoning.js.map +1 -0
- package/dist/memory/brain-retrieval.js +542 -0
- package/dist/memory/brain-retrieval.js.map +1 -0
- package/dist/memory/brain-row-types.js +10 -0
- package/dist/memory/brain-row-types.js.map +1 -0
- package/dist/memory/brain-search.js +519 -0
- package/dist/memory/brain-search.js.map +1 -0
- package/dist/memory/brain-similarity.js +145 -0
- package/dist/memory/brain-similarity.js.map +1 -0
- package/dist/memory/claude-mem-migration.js +277 -0
- package/dist/memory/claude-mem-migration.js.map +1 -0
- package/dist/memory/decisions.js +162 -0
- package/dist/memory/decisions.js.map +1 -0
- package/dist/memory/embedding-local.js +97 -0
- package/dist/memory/embedding-local.js.map +1 -0
- package/dist/memory/embedding-queue.js +271 -0
- package/dist/memory/embedding-queue.js.map +1 -0
- package/dist/memory/embedding-worker.js +58 -0
- package/dist/memory/embedding-worker.js.map +1 -0
- package/dist/memory/engine-compat.js +1397 -0
- package/dist/memory/engine-compat.js.map +1 -0
- package/dist/memory/index.js +1140 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/learnings.d.ts +4 -4
- package/dist/memory/learnings.js +121 -0
- package/dist/memory/learnings.js.map +1 -0
- package/dist/memory/memory-bridge.js +370 -0
- package/dist/memory/memory-bridge.js.map +1 -0
- package/dist/memory/patterns.d.ts +6 -6
- package/dist/memory/patterns.js +122 -0
- package/dist/memory/patterns.js.map +1 -0
- package/dist/memory/pipeline-manifest-sqlite.js +975 -0
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -0
- package/dist/memory/session-memory.js +331 -0
- package/dist/memory/session-memory.js.map +1 -0
- package/dist/metrics/ab-test.js +260 -0
- package/dist/metrics/ab-test.js.map +1 -0
- package/dist/metrics/aggregation.js +363 -0
- package/dist/metrics/aggregation.js.map +1 -0
- package/dist/metrics/common.js +64 -0
- package/dist/metrics/common.js.map +1 -0
- package/dist/metrics/enums.js +78 -0
- package/dist/metrics/enums.js.map +1 -0
- package/dist/metrics/index.js +19 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/model-provider-registry.js +88 -0
- package/dist/metrics/model-provider-registry.js.map +1 -0
- package/dist/metrics/otel-integration.js +263 -0
- package/dist/metrics/otel-integration.js.map +1 -0
- package/dist/metrics/provider-detection.js +103 -0
- package/dist/metrics/provider-detection.js.map +1 -0
- package/dist/metrics/token-estimation.js +253 -0
- package/dist/metrics/token-estimation.js.map +1 -0
- package/dist/metrics/token-service.js +450 -0
- package/dist/metrics/token-service.js.map +1 -0
- package/dist/migration/agent-outputs.js +316 -0
- package/dist/migration/agent-outputs.js.map +1 -0
- package/dist/migration/checksum.js +92 -0
- package/dist/migration/checksum.js.map +1 -0
- package/dist/migration/index.js +282 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/migration/logger.js +360 -0
- package/dist/migration/logger.js.map +1 -0
- package/dist/migration/preflight.js +9 -0
- package/dist/migration/preflight.js.map +1 -0
- package/dist/migration/state.js +421 -0
- package/dist/migration/state.js.map +1 -0
- package/dist/migration/validate.js +241 -0
- package/dist/migration/validate.js.map +1 -0
- package/dist/mvi-helpers.js +74 -0
- package/dist/mvi-helpers.js.map +1 -0
- package/dist/nexus/deps.js +375 -0
- package/dist/nexus/deps.js.map +1 -0
- package/dist/nexus/discover.js +288 -0
- package/dist/nexus/discover.js.map +1 -0
- package/dist/nexus/hash.js +10 -0
- package/dist/nexus/hash.js.map +1 -0
- package/dist/nexus/index.js +40 -0
- package/dist/nexus/index.js.map +1 -0
- package/dist/nexus/migrate-json-to-sqlite.js +115 -0
- package/dist/nexus/migrate-json-to-sqlite.js.map +1 -0
- package/dist/nexus/permissions.js +105 -0
- package/dist/nexus/permissions.js.map +1 -0
- package/dist/nexus/query.js +175 -0
- package/dist/nexus/query.js.map +1 -0
- package/dist/nexus/registry.js +584 -0
- package/dist/nexus/registry.js.map +1 -0
- package/dist/nexus/sharing/index.js +288 -0
- package/dist/nexus/sharing/index.js.map +1 -0
- package/dist/nexus/transfer-types.js +8 -0
- package/dist/nexus/transfer-types.js.map +1 -0
- package/dist/nexus/transfer.js +263 -0
- package/dist/nexus/transfer.js.map +1 -0
- package/dist/nexus/workspace.js +355 -0
- package/dist/nexus/workspace.js.map +1 -0
- package/dist/observability/index.js +103 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/log-filter.js +63 -0
- package/dist/observability/log-filter.js.map +1 -0
- package/dist/observability/log-parser.js +99 -0
- package/dist/observability/log-parser.js.map +1 -0
- package/dist/observability/log-reader.js +139 -0
- package/dist/observability/log-reader.js.map +1 -0
- package/dist/observability/types.js +19 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/orchestration/analyze.js +107 -0
- package/dist/orchestration/analyze.js.map +1 -0
- package/dist/orchestration/bootstrap.js +132 -0
- package/dist/orchestration/bootstrap.js.map +1 -0
- package/dist/orchestration/context.js +56 -0
- package/dist/orchestration/context.js.map +1 -0
- package/dist/orchestration/critical-path.js +100 -0
- package/dist/orchestration/critical-path.js.map +1 -0
- package/dist/orchestration/hierarchy.js +183 -0
- package/dist/orchestration/hierarchy.js.map +1 -0
- package/dist/orchestration/index.js +287 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/parallel.js +89 -0
- package/dist/orchestration/parallel.js.map +1 -0
- package/dist/orchestration/protocol-validators.js +815 -0
- package/dist/orchestration/protocol-validators.js.map +1 -0
- package/dist/orchestration/skill-ops.js +98 -0
- package/dist/orchestration/skill-ops.js.map +1 -0
- package/dist/orchestration/status.js +107 -0
- package/dist/orchestration/status.js.map +1 -0
- package/dist/orchestration/unblock.js +103 -0
- package/dist/orchestration/unblock.js.map +1 -0
- package/dist/orchestration/validate-spawn.js +67 -0
- package/dist/orchestration/validate-spawn.js.map +1 -0
- package/dist/orchestration/waves.js +86 -0
- package/dist/orchestration/waves.js.map +1 -0
- package/dist/otel/index.js +163 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/output.js +164 -0
- package/dist/output.js.map +1 -0
- package/dist/pagination.js +64 -0
- package/dist/pagination.js.map +1 -0
- package/dist/paths.d.ts +39 -9
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +776 -0
- package/dist/paths.js.map +1 -0
- package/dist/phases/deps.js +372 -0
- package/dist/phases/deps.js.map +1 -0
- package/dist/phases/index.js +349 -0
- package/dist/phases/index.js.map +1 -0
- package/dist/pipeline/index.js +10 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/phase.js +45 -0
- package/dist/pipeline/phase.js.map +1 -0
- package/dist/platform.js +211 -0
- package/dist/platform.js.map +1 -0
- package/dist/project-info.js +84 -0
- package/dist/project-info.js.map +1 -0
- package/dist/reconciliation/index.js +10 -0
- package/dist/reconciliation/index.js.map +1 -0
- package/dist/reconciliation/link-store.js +129 -0
- package/dist/reconciliation/link-store.js.map +1 -0
- package/dist/reconciliation/reconciliation-engine.js +298 -0
- package/dist/reconciliation/reconciliation-engine.js.map +1 -0
- package/dist/release/artifacts.js +427 -0
- package/dist/release/artifacts.js.map +1 -0
- package/dist/release/changelog-writer.js +151 -0
- package/dist/release/changelog-writer.js.map +1 -0
- package/dist/release/channel.js +144 -0
- package/dist/release/channel.js.map +1 -0
- package/dist/release/ci.js +166 -0
- package/dist/release/ci.js.map +1 -0
- package/dist/release/github-pr.js +225 -0
- package/dist/release/github-pr.js.map +1 -0
- package/dist/release/guards.js +116 -0
- package/dist/release/guards.js.map +1 -0
- package/dist/release/index.js +22 -0
- package/dist/release/index.js.map +1 -0
- package/dist/release/release-config.js +158 -0
- package/dist/release/release-config.js.map +1 -0
- package/dist/release/release-manifest.js +1019 -0
- package/dist/release/release-manifest.js.map +1 -0
- package/dist/release/version-bump.js +255 -0
- package/dist/release/version-bump.js.map +1 -0
- package/dist/remote/index.js +257 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/repair.js +130 -0
- package/dist/repair.js.map +1 -0
- package/dist/research/index.js +2 -0
- package/dist/research/index.js.map +1 -0
- package/dist/roadmap/index.js +59 -0
- package/dist/roadmap/index.js.map +1 -0
- package/dist/routing/capability-matrix.js +1556 -0
- package/dist/routing/capability-matrix.js.map +1 -0
- package/dist/routing/index.js +9 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/scaffold.d.ts +15 -2
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +1759 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/schema-management.js +295 -0
- package/dist/schema-management.js.map +1 -0
- package/dist/security/index.js +9 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/input-sanitization.js +321 -0
- package/dist/security/input-sanitization.js.map +1 -0
- package/dist/sequence/index.js +295 -0
- package/dist/sequence/index.js.map +1 -0
- package/dist/sessions/assumptions.js +54 -0
- package/dist/sessions/assumptions.js.map +1 -0
- package/dist/sessions/briefing.js +377 -0
- package/dist/sessions/briefing.js.map +1 -0
- package/dist/sessions/context-alert.js +222 -0
- package/dist/sessions/context-alert.js.map +1 -0
- package/dist/sessions/context-inject.js +61 -0
- package/dist/sessions/context-inject.js.map +1 -0
- package/dist/sessions/context-monitor.js +98 -0
- package/dist/sessions/context-monitor.js.map +1 -0
- package/dist/sessions/decisions.js +65 -0
- package/dist/sessions/decisions.js.map +1 -0
- package/dist/sessions/find.js +65 -0
- package/dist/sessions/find.js.map +1 -0
- package/dist/sessions/handoff.js +328 -0
- package/dist/sessions/handoff.js.map +1 -0
- package/dist/sessions/hitl-warnings.js +254 -0
- package/dist/sessions/hitl-warnings.js.map +1 -0
- package/dist/sessions/index.js +327 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/session-archive.js +40 -0
- package/dist/sessions/session-archive.js.map +1 -0
- package/dist/sessions/session-cleanup.js +59 -0
- package/dist/sessions/session-cleanup.js.map +1 -0
- package/dist/sessions/session-drift.js +134 -0
- package/dist/sessions/session-drift.js.map +1 -0
- package/dist/sessions/session-enforcement.js +144 -0
- package/dist/sessions/session-enforcement.js.map +1 -0
- package/dist/sessions/session-grade.js +253 -0
- package/dist/sessions/session-grade.js.map +1 -0
- package/dist/sessions/session-history.js +42 -0
- package/dist/sessions/session-history.js.map +1 -0
- package/dist/sessions/session-id.js +81 -0
- package/dist/sessions/session-id.js.map +1 -0
- package/dist/sessions/session-memory-bridge.js +52 -0
- package/dist/sessions/session-memory-bridge.js.map +1 -0
- package/dist/sessions/session-show.js +24 -0
- package/dist/sessions/session-show.js.map +1 -0
- package/dist/sessions/session-stats.js +69 -0
- package/dist/sessions/session-stats.js.map +1 -0
- package/dist/sessions/session-suspend.js +39 -0
- package/dist/sessions/session-suspend.js.map +1 -0
- package/dist/sessions/session-switch.js +51 -0
- package/dist/sessions/session-switch.js.map +1 -0
- package/dist/sessions/session-view.js +76 -0
- package/dist/sessions/session-view.js.map +1 -0
- package/dist/sessions/snapshot.js +213 -0
- package/dist/sessions/snapshot.js.map +1 -0
- package/dist/sessions/statusline-setup.js +85 -0
- package/dist/sessions/statusline-setup.js.map +1 -0
- package/dist/sessions/types.js +8 -0
- package/dist/sessions/types.js.map +1 -0
- package/dist/skills/agents/config.js +94 -0
- package/dist/skills/agents/config.js.map +1 -0
- package/dist/skills/agents/install.js +116 -0
- package/dist/skills/agents/install.js.map +1 -0
- package/dist/skills/agents/registry.js +161 -0
- package/dist/skills/agents/registry.js.map +1 -0
- package/dist/skills/discovery.js +333 -0
- package/dist/skills/discovery.js.map +1 -0
- package/dist/skills/dispatch.js +347 -0
- package/dist/skills/dispatch.js.map +1 -0
- package/dist/skills/dynamic-skill-generator.js +87 -0
- package/dist/skills/dynamic-skill-generator.js.map +1 -0
- package/dist/skills/index.js +44 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/injection/subagent.js +195 -0
- package/dist/skills/injection/subagent.js.map +1 -0
- package/dist/skills/injection/token.js +260 -0
- package/dist/skills/injection/token.js.map +1 -0
- package/dist/skills/install.js +40 -0
- package/dist/skills/install.js.map +1 -0
- package/dist/skills/manifests/contribution.js +175 -0
- package/dist/skills/manifests/contribution.js.map +1 -0
- package/dist/skills/manifests/research.js +281 -0
- package/dist/skills/manifests/research.js.map +1 -0
- package/dist/skills/manifests/resolver.js +146 -0
- package/dist/skills/manifests/resolver.js.map +1 -0
- package/dist/skills/marketplace.js +90 -0
- package/dist/skills/marketplace.js.map +1 -0
- package/dist/skills/orchestrator/spawn.js +178 -0
- package/dist/skills/orchestrator/spawn.js.map +1 -0
- package/dist/skills/orchestrator/startup.js +451 -0
- package/dist/skills/orchestrator/startup.js.map +1 -0
- package/dist/skills/orchestrator/validator.js +301 -0
- package/dist/skills/orchestrator/validator.js.map +1 -0
- package/dist/skills/precedence-integration.js +73 -0
- package/dist/skills/precedence-integration.js.map +1 -0
- package/dist/skills/precedence-types.js +16 -0
- package/dist/skills/precedence-types.js.map +1 -0
- package/dist/skills/routing-table.js +63 -0
- package/dist/skills/routing-table.js.map +1 -0
- package/dist/skills/skill-paths.js +217 -0
- package/dist/skills/skill-paths.js.map +1 -0
- package/dist/skills/test-utility.js +55 -0
- package/dist/skills/test-utility.js.map +1 -0
- package/dist/skills/types.js +118 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/skills/validation.js +183 -0
- package/dist/skills/validation.js.map +1 -0
- package/dist/skills/version.js +57 -0
- package/dist/skills/version.js.map +1 -0
- package/dist/snapshot/index.js +188 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/spawn/adapter-registry.js +246 -0
- package/dist/spawn/adapter-registry.js.map +1 -0
- package/dist/spawn/index.js +10 -0
- package/dist/spawn/index.js.map +1 -0
- package/dist/stats/index.js +343 -0
- package/dist/stats/index.js.map +1 -0
- package/dist/stats/workflow-telemetry.js +400 -0
- package/dist/stats/workflow-telemetry.js.map +1 -0
- package/dist/sticky/archive.js +47 -0
- package/dist/sticky/archive.js.map +1 -0
- package/dist/sticky/convert.js +235 -0
- package/dist/sticky/convert.js.map +1 -0
- package/dist/sticky/create.js +48 -0
- package/dist/sticky/create.js.map +1 -0
- package/dist/sticky/id.js +35 -0
- package/dist/sticky/id.js.map +1 -0
- package/dist/sticky/index.js +16 -0
- package/dist/sticky/index.js.map +1 -0
- package/dist/sticky/list.js +44 -0
- package/dist/sticky/list.js.map +1 -0
- package/dist/sticky/purge.js +45 -0
- package/dist/sticky/purge.js.map +1 -0
- package/dist/sticky/show.js +42 -0
- package/dist/sticky/show.js.map +1 -0
- package/dist/sticky/types.js +10 -0
- package/dist/sticky/types.js.map +1 -0
- package/dist/store/agent-registry-accessor.js +265 -0
- package/dist/store/agent-registry-accessor.js.map +1 -0
- package/dist/store/atomic.js +167 -0
- package/dist/store/atomic.js.map +1 -0
- package/dist/store/backup.js +94 -0
- package/dist/store/backup.js.map +1 -0
- package/dist/store/brain-accessor.js +397 -0
- package/dist/store/brain-accessor.js.map +1 -0
- package/dist/store/brain-schema.d.ts +8 -8
- package/dist/store/brain-schema.js +215 -0
- package/dist/store/brain-schema.js.map +1 -0
- package/dist/store/brain-sqlite.js +222 -0
- package/dist/store/brain-sqlite.js.map +1 -0
- package/dist/store/cache.js +168 -0
- package/dist/store/cache.js.map +1 -0
- package/dist/store/chain-schema.js +51 -0
- package/dist/store/chain-schema.js.map +1 -0
- package/dist/store/cleanup-legacy.d.ts +128 -0
- package/dist/store/cleanup-legacy.d.ts.map +1 -0
- package/dist/store/converters.js +124 -0
- package/dist/store/converters.js.map +1 -0
- package/dist/store/cross-db-cleanup.js +319 -0
- package/dist/store/cross-db-cleanup.js.map +1 -0
- package/dist/store/data-accessor.js +26 -0
- package/dist/store/data-accessor.js.map +1 -0
- package/dist/store/data-safety-central.js +269 -0
- package/dist/store/data-safety-central.js.map +1 -0
- package/dist/store/data-safety.js +274 -0
- package/dist/store/data-safety.js.map +1 -0
- package/dist/store/db-helpers.js +224 -0
- package/dist/store/db-helpers.js.map +1 -0
- package/dist/store/export.js +155 -0
- package/dist/store/export.js.map +1 -0
- package/dist/store/file-utils.js +270 -0
- package/dist/store/file-utils.js.map +1 -0
- package/dist/store/git-checkpoint.js +365 -0
- package/dist/store/git-checkpoint.js.map +1 -0
- package/dist/store/import-logging.js +139 -0
- package/dist/store/import-logging.js.map +1 -0
- package/dist/store/import-remap.js +145 -0
- package/dist/store/import-remap.js.map +1 -0
- package/dist/store/import-sort.js +121 -0
- package/dist/store/import-sort.js.map +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +29 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.js +208 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/lifecycle-store.js +249 -0
- package/dist/store/lifecycle-store.js.map +1 -0
- package/dist/store/lock.js +70 -0
- package/dist/store/lock.js.map +1 -0
- package/dist/store/migration-manager.js +151 -0
- package/dist/store/migration-manager.js.map +1 -0
- package/dist/store/migration-sqlite.js +676 -0
- package/dist/store/migration-sqlite.js.map +1 -0
- package/dist/store/nexus-schema.js +62 -0
- package/dist/store/nexus-schema.js.map +1 -0
- package/dist/store/nexus-sqlite.d.ts +14 -2
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.js +217 -0
- package/dist/store/nexus-sqlite.js.map +1 -0
- package/dist/store/nexus-validation-schemas.js +40 -0
- package/dist/store/nexus-validation-schemas.js.map +1 -0
- package/dist/store/parsers.js +37 -0
- package/dist/store/parsers.js.map +1 -0
- package/dist/store/project-detect.js +457 -0
- package/dist/store/project-detect.js.map +1 -0
- package/dist/store/provider.js +101 -0
- package/dist/store/provider.js.map +1 -0
- package/dist/store/safety-data-accessor.js +257 -0
- package/dist/store/safety-data-accessor.js.map +1 -0
- package/dist/store/schema.js +7 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/session-store.js +219 -0
- package/dist/store/session-store.js.map +1 -0
- package/dist/store/signaldock-sqlite.js +400 -0
- package/dist/store/signaldock-sqlite.js.map +1 -0
- package/dist/store/sqlite-backup.d.ts +121 -10
- package/dist/store/sqlite-backup.d.ts.map +1 -1
- package/dist/store/sqlite-backup.js +241 -0
- package/dist/store/sqlite-backup.js.map +1 -0
- package/dist/store/sqlite-data-accessor.js +787 -0
- package/dist/store/sqlite-data-accessor.js.map +1 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +481 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/store/status-registry.js +8 -0
- package/dist/store/status-registry.js.map +1 -0
- package/dist/store/task-store.js +358 -0
- package/dist/store/task-store.js.map +1 -0
- package/dist/store/tasks-schema.d.ts +8 -8
- package/dist/store/tasks-schema.js +610 -0
- package/dist/store/tasks-schema.js.map +1 -0
- package/dist/store/typed-query.js +15 -0
- package/dist/store/typed-query.js.map +1 -0
- package/dist/store/validation-schemas.d.ts +37 -37
- package/dist/store/validation-schemas.js +278 -0
- package/dist/store/validation-schemas.js.map +1 -0
- package/dist/system/archive-analytics.js +277 -0
- package/dist/system/archive-analytics.js.map +1 -0
- package/dist/system/archive-stats.js +64 -0
- package/dist/system/archive-stats.js.map +1 -0
- package/dist/system/audit.js +145 -0
- package/dist/system/audit.js.map +1 -0
- package/dist/system/backup.d.ts +91 -3
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +280 -0
- package/dist/system/backup.js.map +1 -0
- package/dist/system/cleanup.js +134 -0
- package/dist/system/cleanup.js.map +1 -0
- package/dist/system/health.js +1100 -0
- package/dist/system/health.js.map +1 -0
- package/dist/system/index.js +18 -0
- package/dist/system/index.js.map +1 -0
- package/dist/system/inject-generate.js +122 -0
- package/dist/system/inject-generate.js.map +1 -0
- package/dist/system/labels.js +38 -0
- package/dist/system/labels.js.map +1 -0
- package/dist/system/metrics.js +61 -0
- package/dist/system/metrics.js.map +1 -0
- package/dist/system/migrate.js +43 -0
- package/dist/system/migrate.js.map +1 -0
- package/dist/system/platform-paths.js +80 -0
- package/dist/system/platform-paths.js.map +1 -0
- package/dist/system/runtime.js +161 -0
- package/dist/system/runtime.js.map +1 -0
- package/dist/system/safestop.js +99 -0
- package/dist/system/safestop.js.map +1 -0
- package/dist/system/storage-preflight.js +123 -0
- package/dist/system/storage-preflight.js.map +1 -0
- package/dist/task-work/index.js +159 -0
- package/dist/task-work/index.js.map +1 -0
- package/dist/tasks/add.js +661 -0
- package/dist/tasks/add.js.map +1 -0
- package/dist/tasks/analyze.js +85 -0
- package/dist/tasks/analyze.js.map +1 -0
- package/dist/tasks/archive.js +90 -0
- package/dist/tasks/archive.js.map +1 -0
- package/dist/tasks/atomicity.js +83 -0
- package/dist/tasks/atomicity.js.map +1 -0
- package/dist/tasks/cancel-ops.js +83 -0
- package/dist/tasks/cancel-ops.js.map +1 -0
- package/dist/tasks/complete.js +211 -0
- package/dist/tasks/complete.js.map +1 -0
- package/dist/tasks/crossref-extract.js +73 -0
- package/dist/tasks/crossref-extract.js.map +1 -0
- package/dist/tasks/delete-preview.js +192 -0
- package/dist/tasks/delete-preview.js.map +1 -0
- package/dist/tasks/delete.js +120 -0
- package/dist/tasks/delete.js.map +1 -0
- package/dist/tasks/deletion-strategy.js +200 -0
- package/dist/tasks/deletion-strategy.js.map +1 -0
- package/dist/tasks/dependency-check.js +278 -0
- package/dist/tasks/dependency-check.js.map +1 -0
- package/dist/tasks/deps-ready.js +32 -0
- package/dist/tasks/deps-ready.js.map +1 -0
- package/dist/tasks/enforcement.js +86 -0
- package/dist/tasks/enforcement.js.map +1 -0
- package/dist/tasks/epic-enforcement.js +294 -0
- package/dist/tasks/epic-enforcement.js.map +1 -0
- package/dist/tasks/find.js +154 -0
- package/dist/tasks/find.js.map +1 -0
- package/dist/tasks/graph-cache.js +127 -0
- package/dist/tasks/graph-cache.js.map +1 -0
- package/dist/tasks/graph-ops.js +171 -0
- package/dist/tasks/graph-ops.js.map +1 -0
- package/dist/tasks/graph-rag.js +328 -0
- package/dist/tasks/graph-rag.js.map +1 -0
- package/dist/tasks/hierarchy-policy.js +149 -0
- package/dist/tasks/hierarchy-policy.js.map +1 -0
- package/dist/tasks/hierarchy.js +185 -0
- package/dist/tasks/hierarchy.js.map +1 -0
- package/dist/tasks/id-generator.js +65 -0
- package/dist/tasks/id-generator.js.map +1 -0
- package/dist/tasks/index.js +14 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/tasks/labels.js +52 -0
- package/dist/tasks/labels.js.map +1 -0
- package/dist/tasks/list.js +75 -0
- package/dist/tasks/list.js.map +1 -0
- package/dist/tasks/phase-tracking.js +133 -0
- package/dist/tasks/phase-tracking.js.map +1 -0
- package/dist/tasks/pipeline-stage.js +248 -0
- package/dist/tasks/pipeline-stage.js.map +1 -0
- package/dist/tasks/plan.js +268 -0
- package/dist/tasks/plan.js.map +1 -0
- package/dist/tasks/relates.js +89 -0
- package/dist/tasks/relates.js.map +1 -0
- package/dist/tasks/show.js +80 -0
- package/dist/tasks/show.js.map +1 -0
- package/dist/tasks/size-weighting.js +86 -0
- package/dist/tasks/size-weighting.js.map +1 -0
- package/dist/tasks/staleness.js +86 -0
- package/dist/tasks/staleness.js.map +1 -0
- package/dist/tasks/task-ops.js +1741 -0
- package/dist/tasks/task-ops.js.map +1 -0
- package/dist/tasks/update.js +277 -0
- package/dist/tasks/update.js.map +1 -0
- package/dist/templates/index.js +10 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/parser.js +254 -0
- package/dist/templates/parser.js.map +1 -0
- package/dist/ui/aliases.js +153 -0
- package/dist/ui/aliases.js.map +1 -0
- package/dist/ui/changelog.js +184 -0
- package/dist/ui/changelog.js.map +1 -0
- package/dist/ui/command-registry.js +168 -0
- package/dist/ui/command-registry.js.map +1 -0
- package/dist/ui/flags.js +94 -0
- package/dist/ui/flags.js.map +1 -0
- package/dist/ui/index.js +24 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/upgrade.js +1148 -0
- package/dist/upgrade.js.map +1 -0
- package/dist/validation/chain-validation.js +146 -0
- package/dist/validation/chain-validation.js.map +1 -0
- package/dist/validation/compliance.js +155 -0
- package/dist/validation/compliance.js.map +1 -0
- package/dist/validation/docs-sync.js +212 -0
- package/dist/validation/docs-sync.js.map +1 -0
- package/dist/validation/doctor/checks.js +1069 -0
- package/dist/validation/doctor/checks.js.map +1 -0
- package/dist/validation/doctor/index.js +9 -0
- package/dist/validation/doctor/index.js.map +1 -0
- package/dist/validation/doctor/project-cache.js +160 -0
- package/dist/validation/doctor/project-cache.js.map +1 -0
- package/dist/validation/doctor/utils.js +155 -0
- package/dist/validation/doctor/utils.js.map +1 -0
- package/dist/validation/engine.js +902 -0
- package/dist/validation/engine.js.map +1 -0
- package/dist/validation/gap-check.js +175 -0
- package/dist/validation/gap-check.js.map +1 -0
- package/dist/validation/index.js +40 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/manifest.js +237 -0
- package/dist/validation/manifest.js.map +1 -0
- package/dist/validation/operation-gate-validators.js +724 -0
- package/dist/validation/operation-gate-validators.js.map +1 -0
- package/dist/validation/operation-verification-gates.js +532 -0
- package/dist/validation/operation-verification-gates.js.map +1 -0
- package/dist/validation/param-utils.js +139 -0
- package/dist/validation/param-utils.js.map +1 -0
- package/dist/validation/protocol-common.js +300 -0
- package/dist/validation/protocol-common.js.map +1 -0
- package/dist/validation/protocols/_shared.js +75 -0
- package/dist/validation/protocols/_shared.js.map +1 -0
- package/dist/validation/protocols/architecture-decision.js +31 -0
- package/dist/validation/protocols/architecture-decision.js.map +1 -0
- package/dist/validation/protocols/artifact-publish.js +28 -0
- package/dist/validation/protocols/artifact-publish.js.map +1 -0
- package/dist/validation/protocols/consensus.js +41 -0
- package/dist/validation/protocols/consensus.js.map +1 -0
- package/dist/validation/protocols/contribution.js +27 -0
- package/dist/validation/protocols/contribution.js.map +1 -0
- package/dist/validation/protocols/decomposition.js +28 -0
- package/dist/validation/protocols/decomposition.js.map +1 -0
- package/dist/validation/protocols/implementation.js +24 -0
- package/dist/validation/protocols/implementation.js.map +1 -0
- package/dist/validation/protocols/provenance.js +29 -0
- package/dist/validation/protocols/provenance.js.map +1 -0
- package/dist/validation/protocols/release.js +29 -0
- package/dist/validation/protocols/release.js.map +1 -0
- package/dist/validation/protocols/research.js +24 -0
- package/dist/validation/protocols/research.js.map +1 -0
- package/dist/validation/protocols/specification.js +27 -0
- package/dist/validation/protocols/specification.js.map +1 -0
- package/dist/validation/protocols/testing.js +30 -0
- package/dist/validation/protocols/testing.js.map +1 -0
- package/dist/validation/protocols/validation.js +30 -0
- package/dist/validation/protocols/validation.js.map +1 -0
- package/dist/validation/schema-integrity.js +170 -0
- package/dist/validation/schema-integrity.js.map +1 -0
- package/dist/validation/schema-validator.js +176 -0
- package/dist/validation/schema-validator.js.map +1 -0
- package/dist/validation/validate-ops.js +937 -0
- package/dist/validation/validate-ops.js.map +1 -0
- package/dist/validation/validation-rules.js +226 -0
- package/dist/validation/validation-rules.js.map +1 -0
- package/dist/validation/verification.js +321 -0
- package/dist/validation/verification.js.map +1 -0
- package/package.json +10 -8
- package/src/__tests__/paths-walkup.test.ts +305 -0
- package/src/__tests__/paths.test.ts +61 -17
- package/src/hooks/handlers/session-hooks.ts +42 -0
- package/src/internal.ts +19 -2
- package/src/paths.ts +91 -14
- package/src/scaffold.ts +22 -3
- package/src/store/__tests__/cleanup-legacy.test.ts +268 -0
- package/src/store/__tests__/database-topology-integration.test.ts +504 -0
- package/src/store/__tests__/sqlite-backup-global.test.ts +281 -0
- package/src/store/__tests__/sqlite-backup.test.ts +118 -10
- package/src/store/cleanup-legacy.ts +208 -0
- package/src/store/index.ts +7 -0
- package/src/store/nexus-sqlite.ts +32 -3
- package/src/store/sqlite-backup.ts +368 -37
- package/src/store/sqlite.ts +19 -3
- package/src/system/__tests__/backup.test.ts +237 -0
- package/src/system/backup.ts +248 -28
- package/templates/cleo-gitignore +19 -3
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,1759 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory & file scaffolding utilities.
|
|
3
|
+
*
|
|
4
|
+
* Shared ensure/check functions extracted from init.ts for reuse
|
|
5
|
+
* by init.ts, upgrade.ts, and doctor health checks.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
* - All ensure functions are idempotent (safe to call multiple times)
|
|
9
|
+
* - All check functions are read-only (no side effects)
|
|
10
|
+
* - Uses imports from ./paths.js for path resolution
|
|
11
|
+
*/
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
14
|
+
import { existsSync, constants as fsConstants, readFileSync, statSync } from 'node:fs';
|
|
15
|
+
import { access, copyFile, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { homedir as getHomedir } from 'node:os';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { promisify } from 'node:util';
|
|
20
|
+
import { generateProjectHash } from './nexus/hash.js';
|
|
21
|
+
import { getCleoCantWorkflowsDir, getCleoDirAbsolute, getCleoGlobalAgentsDir, getCleoGlobalRecipesDir, getCleoHome, getCleoPiExtensionsDir, getCleoTemplatesDir, getConfigPath, } from './paths.js';
|
|
22
|
+
import { saveJson } from './store/json.js';
|
|
23
|
+
const execFileAsync = promisify(execFile);
|
|
24
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
25
|
+
/** Required subdirectories under .cleo/. */
|
|
26
|
+
export const REQUIRED_CLEO_SUBDIRS = [
|
|
27
|
+
'backups/operational',
|
|
28
|
+
'backups/safety',
|
|
29
|
+
'agent-outputs',
|
|
30
|
+
'logs',
|
|
31
|
+
'rcasd',
|
|
32
|
+
'adrs',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Embedded fallback for .cleo/.gitignore content (deny-by-default).
|
|
36
|
+
*
|
|
37
|
+
* Must stay in sync with `packages/core/templates/cleo-gitignore`. The
|
|
38
|
+
* template file is the source of truth; this constant is only used when
|
|
39
|
+
* the template file cannot be located at runtime (bundled binary edge
|
|
40
|
+
* cases, air-gapped installs, etc.).
|
|
41
|
+
*
|
|
42
|
+
* ADR-013 §9 (2026-04-07, T5158): config.json / project-info.json are
|
|
43
|
+
* NOT re-included here. Runtime state files (tasks.db, brain.db,
|
|
44
|
+
* config.json, project-info.json) are protected by explicit deny rules
|
|
45
|
+
* so that nested-.gitignore allow rules cannot unignore them at the
|
|
46
|
+
* project repository level.
|
|
47
|
+
*/
|
|
48
|
+
export const CLEO_GITIGNORE_FALLBACK = `# .cleo/.gitignore — Deny-by-default for CLEO project data
|
|
49
|
+
# Ignore everything, then explicitly allow only tracked files.
|
|
50
|
+
#
|
|
51
|
+
# ADR-013 §9 (2026-04-07, T5158): config.json + project-info.json are
|
|
52
|
+
# runtime snapshots regenerated by \`cleo init\`, not tracked in git.
|
|
53
|
+
# Recovery for all four runtime files (tasks.db, brain.db, config.json,
|
|
54
|
+
# project-info.json) is provided by \`cleo backup add\` snapshots under
|
|
55
|
+
# .cleo/backups/. See .cleo/adrs/ADR-013 for the full recovery story.
|
|
56
|
+
|
|
57
|
+
# Step 1: Ignore everything
|
|
58
|
+
*
|
|
59
|
+
|
|
60
|
+
# Allow list
|
|
61
|
+
!.gitignore
|
|
62
|
+
!project-context.json
|
|
63
|
+
!setup-otel.sh
|
|
64
|
+
!DATA-SAFETY-IMPLEMENTATION-SUMMARY.md
|
|
65
|
+
!adrs/
|
|
66
|
+
!adrs/**
|
|
67
|
+
!rcasd/
|
|
68
|
+
!rcasd/**
|
|
69
|
+
!agent-outputs/
|
|
70
|
+
!agent-outputs/**
|
|
71
|
+
|
|
72
|
+
# Explicit deny safety net
|
|
73
|
+
*.db
|
|
74
|
+
*.db-shm
|
|
75
|
+
*.db-wal
|
|
76
|
+
*.db-journal
|
|
77
|
+
config.json
|
|
78
|
+
project-info.json
|
|
79
|
+
log.json
|
|
80
|
+
bypass-log.json
|
|
81
|
+
qa-log.json
|
|
82
|
+
.deps-cache/
|
|
83
|
+
.context-alert-state.json
|
|
84
|
+
.context-state*.json
|
|
85
|
+
context-states/
|
|
86
|
+
.git-checkpoint-state
|
|
87
|
+
.migration-state.json
|
|
88
|
+
migrations.json
|
|
89
|
+
sync/
|
|
90
|
+
metrics/
|
|
91
|
+
.backups/
|
|
92
|
+
backups/
|
|
93
|
+
`;
|
|
94
|
+
// ── Pure helpers ─────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Check if a file exists and is readable.
|
|
97
|
+
*
|
|
98
|
+
* @param path - Absolute path to the file to check
|
|
99
|
+
* @returns True if the file exists and is readable, false otherwise
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* Uses `fs.access` with `R_OK` to verify both existence and read permission.
|
|
103
|
+
* Swallows all errors and returns false on failure.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* if (await fileExists('/project/.cleo/config.json')) {
|
|
108
|
+
* // safe to read
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export async function fileExists(path) {
|
|
113
|
+
try {
|
|
114
|
+
await access(path, fsConstants.R_OK);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Strip legacy CLEO:START/CLEO:END blocks from a file.
|
|
123
|
+
* Called before CAAMP injection to prevent competing blocks.
|
|
124
|
+
*
|
|
125
|
+
* @param filePath - Absolute path to the file to strip
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* Handles both bare markers (`<!-- CLEO:START -->`) and versioned markers
|
|
129
|
+
* (`<!-- CLEO:START v0.53.4 -->`). No-op if the file does not exist or
|
|
130
|
+
* contains no CLEO blocks.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* await stripCLEOBlocks('/project/CLAUDE.md');
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export async function stripCLEOBlocks(filePath) {
|
|
138
|
+
if (!existsSync(filePath))
|
|
139
|
+
return;
|
|
140
|
+
const content = await readFile(filePath, 'utf8');
|
|
141
|
+
const stripped = content.replace(/\n?<!-- CLEO:START[^>]*-->[\s\S]*?<!-- CLEO:END -->\n?/g, '');
|
|
142
|
+
if (stripped !== content)
|
|
143
|
+
await writeFile(filePath, stripped, 'utf8');
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Remove .cleo/ or .cleo entries from the project root .gitignore.
|
|
147
|
+
*
|
|
148
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
149
|
+
* @returns Whether any lines were removed from the .gitignore
|
|
150
|
+
*
|
|
151
|
+
* @remarks
|
|
152
|
+
* Filters out lines matching `/.cleo/`, `.cleo/`, `.cleo`, etc. from the
|
|
153
|
+
* root-level `.gitignore`. Returns `{ removed: false }` if the file does not
|
|
154
|
+
* exist or contains no matching entries.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const { removed } = await removeCleoFromRootGitignore('/project');
|
|
159
|
+
* if (removed) console.log('.cleo entries cleaned from .gitignore');
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export async function removeCleoFromRootGitignore(projectRoot) {
|
|
163
|
+
const rootGitignorePath = join(projectRoot, '.gitignore');
|
|
164
|
+
if (!(await fileExists(rootGitignorePath))) {
|
|
165
|
+
return { removed: false };
|
|
166
|
+
}
|
|
167
|
+
const content = await readFile(rootGitignorePath, 'utf-8');
|
|
168
|
+
const lines = content.split('\n');
|
|
169
|
+
const filtered = lines.filter((line) => {
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
return !/^\/?\.cleo\/?(\*)?$/.test(trimmed);
|
|
172
|
+
});
|
|
173
|
+
if (filtered.length === lines.length) {
|
|
174
|
+
return { removed: false };
|
|
175
|
+
}
|
|
176
|
+
await writeFile(rootGitignorePath, filtered.join('\n'));
|
|
177
|
+
return { removed: true };
|
|
178
|
+
}
|
|
179
|
+
// generateProjectHash moved to src/core/nexus/hash.ts (canonical location)
|
|
180
|
+
export { generateProjectHash } from './nexus/hash.js';
|
|
181
|
+
/**
|
|
182
|
+
* Resolve the package root directory (where schemas/ and templates/ live).
|
|
183
|
+
* scaffold.ts lives in packages/core/src/, so 1 level up reaches the package root.
|
|
184
|
+
*
|
|
185
|
+
* @returns Absolute path to the @cleocode/core package root
|
|
186
|
+
*
|
|
187
|
+
* @remarks
|
|
188
|
+
* Uses `import.meta.url` to determine the file location at runtime, which
|
|
189
|
+
* works correctly in both development and installed npm package contexts.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const root = getPackageRoot();
|
|
194
|
+
* const schemaPath = join(root, 'schemas', 'config.schema.json');
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export function getPackageRoot() {
|
|
198
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
199
|
+
return resolve(dirname(thisFile), '..');
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Load the gitignore template from the package's templates/ directory.
|
|
203
|
+
* Falls back to embedded content if file not found.
|
|
204
|
+
*
|
|
205
|
+
* @returns The .cleo/.gitignore template content string
|
|
206
|
+
*
|
|
207
|
+
* @remarks
|
|
208
|
+
* First attempts to read from the package's `templates/cleo-gitignore` file.
|
|
209
|
+
* Falls back to the embedded `CLEO_GITIGNORE_FALLBACK` constant if the
|
|
210
|
+
* template file is missing or unreadable.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const content = getGitignoreContent();
|
|
215
|
+
* await writeFile('.cleo/.gitignore', content);
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export function getGitignoreContent() {
|
|
219
|
+
try {
|
|
220
|
+
const packageRoot = getPackageRoot();
|
|
221
|
+
const templatePath = join(packageRoot, 'templates', 'cleo-gitignore');
|
|
222
|
+
if (existsSync(templatePath)) {
|
|
223
|
+
return readFileSync(templatePath, 'utf-8');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// fallback
|
|
228
|
+
}
|
|
229
|
+
return CLEO_GITIGNORE_FALLBACK;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Read CLEO version from package.json.
|
|
233
|
+
*
|
|
234
|
+
* @returns Semver version string, or "0.0.0" if unavailable
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* Reads the `version` field from the @cleocode/core package.json at runtime.
|
|
238
|
+
* Returns "0.0.0" if the file cannot be read or parsed.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const version = getCleoVersion(); // "2026.4.0"
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function getCleoVersion() {
|
|
246
|
+
try {
|
|
247
|
+
const pkgPath = join(getPackageRoot(), 'package.json');
|
|
248
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
249
|
+
return pkg.version ?? '0.0.0';
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return '0.0.0';
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Detect whether projectRoot is the CLEO source repository itself.
|
|
257
|
+
* Verified by fingerprinting the expected source layout and package identity.
|
|
258
|
+
* Only the canonical CLEO repository matches all criteria (ADR-029).
|
|
259
|
+
*/
|
|
260
|
+
function isCleoContributorProject(projectRoot) {
|
|
261
|
+
const exists = (p) => existsSync(join(projectRoot, p));
|
|
262
|
+
// Must have all three canonical source directories
|
|
263
|
+
if (!exists('src/dispatch') || !exists('src/core'))
|
|
264
|
+
return false;
|
|
265
|
+
// Must have package.json identifying as @cleocode/cleo
|
|
266
|
+
try {
|
|
267
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
|
|
268
|
+
return pkg.name === '@cleocode/cleo';
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Create default config.json content.
|
|
276
|
+
*
|
|
277
|
+
* @returns A plain object with the default CLEO configuration structure
|
|
278
|
+
*
|
|
279
|
+
* @remarks
|
|
280
|
+
* Returns a version-stamped config with defaults for output formatting, backup
|
|
281
|
+
* limits, hierarchy depth, session behavior, and lifecycle mode.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* const config = createDefaultConfig();
|
|
286
|
+
* await saveJson(configPath, config);
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
export function createDefaultConfig() {
|
|
290
|
+
return {
|
|
291
|
+
version: '2.10.0',
|
|
292
|
+
output: {
|
|
293
|
+
defaultFormat: 'json',
|
|
294
|
+
showColor: true,
|
|
295
|
+
showUnicode: true,
|
|
296
|
+
dateFormat: 'relative',
|
|
297
|
+
},
|
|
298
|
+
backup: {
|
|
299
|
+
maxOperationalBackups: 10,
|
|
300
|
+
maxSafetyBackups: 5,
|
|
301
|
+
},
|
|
302
|
+
hierarchy: {
|
|
303
|
+
maxDepth: 3,
|
|
304
|
+
maxSiblings: 0,
|
|
305
|
+
},
|
|
306
|
+
session: {
|
|
307
|
+
autoStart: false,
|
|
308
|
+
multiSession: false,
|
|
309
|
+
},
|
|
310
|
+
lifecycle: {
|
|
311
|
+
mode: 'strict',
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// ── ensure* functions (idempotent) ───────────────────────────────────
|
|
316
|
+
/**
|
|
317
|
+
* Create .cleo/ directory and all required subdirectories.
|
|
318
|
+
* Idempotent: skips directories that already exist.
|
|
319
|
+
*
|
|
320
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
321
|
+
* @returns Scaffold result indicating whether the directory was created or already existed
|
|
322
|
+
*
|
|
323
|
+
* @remarks
|
|
324
|
+
* Refuses to scaffold inside the global CLEO home to prevent pollution.
|
|
325
|
+
* Creates all directories listed in `REQUIRED_CLEO_SUBDIRS`.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* const result = await ensureCleoStructure('/my/project');
|
|
330
|
+
* console.log(result.action); // "created" or "skipped"
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
export async function ensureCleoStructure(projectRoot) {
|
|
334
|
+
// Guard: reject global home as project root to prevent project-level
|
|
335
|
+
// subdirectories (adrs/, rcasd/, backups/, etc.) from polluting ~/.cleo/
|
|
336
|
+
const resolvedRoot = resolve(projectRoot);
|
|
337
|
+
if (resolvedRoot === resolve(getCleoHome())) {
|
|
338
|
+
return {
|
|
339
|
+
action: 'skipped',
|
|
340
|
+
path: join(resolvedRoot, '.cleo'),
|
|
341
|
+
details: 'Refused to scaffold project structure inside global CLEO home',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
345
|
+
const alreadyExists = existsSync(cleoDir);
|
|
346
|
+
await mkdir(cleoDir, { recursive: true });
|
|
347
|
+
for (const subdir of REQUIRED_CLEO_SUBDIRS) {
|
|
348
|
+
await mkdir(join(cleoDir, subdir), { recursive: true });
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
action: alreadyExists ? 'skipped' : 'created',
|
|
352
|
+
path: cleoDir,
|
|
353
|
+
details: alreadyExists
|
|
354
|
+
? 'Directory already existed, ensured subdirs'
|
|
355
|
+
: `Created .cleo/ with ${REQUIRED_CLEO_SUBDIRS.length} subdirectories`,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Create or repair .cleo/.gitignore from template.
|
|
360
|
+
* Idempotent: skips if file already exists with correct content.
|
|
361
|
+
*
|
|
362
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
363
|
+
* @returns Scaffold result indicating whether the gitignore was created, repaired, or skipped
|
|
364
|
+
*
|
|
365
|
+
* @remarks
|
|
366
|
+
* Compares normalized content (trimmed, LF line endings) against the template.
|
|
367
|
+
* If the file exists but differs, it is overwritten with the canonical template.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* const result = await ensureGitignore('/my/project');
|
|
372
|
+
* if (result.action === 'repaired') console.log('Gitignore updated');
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
export async function ensureGitignore(projectRoot) {
|
|
376
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
377
|
+
const gitignorePath = join(cleoDir, '.gitignore');
|
|
378
|
+
const templateContent = getGitignoreContent();
|
|
379
|
+
if (existsSync(gitignorePath)) {
|
|
380
|
+
const existing = readFileSync(gitignorePath, 'utf-8');
|
|
381
|
+
const normalize = (s) => s.trim().replace(/\r\n/g, '\n');
|
|
382
|
+
if (normalize(existing) === normalize(templateContent)) {
|
|
383
|
+
return { action: 'skipped', path: gitignorePath, details: 'Already matches template' };
|
|
384
|
+
}
|
|
385
|
+
await writeFile(gitignorePath, templateContent);
|
|
386
|
+
return { action: 'repaired', path: gitignorePath, details: 'Updated to match template' };
|
|
387
|
+
}
|
|
388
|
+
await writeFile(gitignorePath, templateContent);
|
|
389
|
+
return { action: 'created', path: gitignorePath };
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Create default config.json if missing.
|
|
393
|
+
* Idempotent: skips if file already exists.
|
|
394
|
+
*
|
|
395
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
396
|
+
* @param opts - Optional configuration
|
|
397
|
+
* @param opts.force - When true, overwrite the existing config
|
|
398
|
+
* @returns Scaffold result indicating the action taken
|
|
399
|
+
*
|
|
400
|
+
* @remarks
|
|
401
|
+
* On skip, still checks for and backfills the ADR-029 contributor block
|
|
402
|
+
* if the project is detected as the CLEO source repository.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* const result = await ensureConfig('/my/project', { force: true });
|
|
407
|
+
* console.log(result.path);
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
export async function ensureConfig(projectRoot, opts) {
|
|
411
|
+
const configPath = getConfigPath(projectRoot);
|
|
412
|
+
if (existsSync(configPath) && !opts?.force) {
|
|
413
|
+
// Backfill contributor block for existing configs that predate ADR-029
|
|
414
|
+
try {
|
|
415
|
+
const existing = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
416
|
+
if (!existing['contributor'] && isCleoContributorProject(projectRoot)) {
|
|
417
|
+
existing['contributor'] = {
|
|
418
|
+
isContributorProject: true,
|
|
419
|
+
devCli: 'cleo-dev',
|
|
420
|
+
verifiedAt: new Date().toISOString(),
|
|
421
|
+
};
|
|
422
|
+
await writeFile(configPath, JSON.stringify(existing, null, 2));
|
|
423
|
+
return {
|
|
424
|
+
action: 'repaired',
|
|
425
|
+
path: configPath,
|
|
426
|
+
details: 'Added contributor block (ADR-029)',
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
/* non-fatal */
|
|
432
|
+
}
|
|
433
|
+
return { action: 'skipped', path: configPath, details: 'Config already exists' };
|
|
434
|
+
}
|
|
435
|
+
const config = createDefaultConfig();
|
|
436
|
+
if (isCleoContributorProject(projectRoot)) {
|
|
437
|
+
config['contributor'] = {
|
|
438
|
+
isContributorProject: true,
|
|
439
|
+
devCli: 'cleo-dev',
|
|
440
|
+
verifiedAt: new Date().toISOString(),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
await saveJson(configPath, config);
|
|
444
|
+
return {
|
|
445
|
+
action: existsSync(configPath) ? 'repaired' : 'created',
|
|
446
|
+
path: configPath,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Create or refresh project-info.json.
|
|
451
|
+
* Idempotent: skips if file already exists (unless force).
|
|
452
|
+
*
|
|
453
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
454
|
+
* @param opts - Optional configuration
|
|
455
|
+
* @param opts.force - When true, regenerate even if the file exists
|
|
456
|
+
* @returns Scaffold result indicating the action taken
|
|
457
|
+
*
|
|
458
|
+
* @remarks
|
|
459
|
+
* Backfills a `projectId` (UUID) on existing files that lack one (T5333).
|
|
460
|
+
* The generated file includes project hash, CLEO version, schema versions,
|
|
461
|
+
* and initial health/feature flags.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```typescript
|
|
465
|
+
* const result = await ensureProjectInfo('/my/project');
|
|
466
|
+
* console.log(result.action); // "created", "repaired", or "skipped"
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
export async function ensureProjectInfo(projectRoot, opts) {
|
|
470
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
471
|
+
const projectInfoPath = join(cleoDir, 'project-info.json');
|
|
472
|
+
// Backfill projectId on existing files that lack it (T5333)
|
|
473
|
+
if (existsSync(projectInfoPath) && !opts?.force) {
|
|
474
|
+
try {
|
|
475
|
+
const existing = JSON.parse(readFileSync(projectInfoPath, 'utf-8'));
|
|
476
|
+
if (typeof existing.projectId !== 'string' || existing.projectId.length === 0) {
|
|
477
|
+
existing.projectId = randomUUID();
|
|
478
|
+
existing.lastUpdated = new Date().toISOString();
|
|
479
|
+
await writeFile(projectInfoPath, JSON.stringify(existing, null, 2));
|
|
480
|
+
return { action: 'repaired', path: projectInfoPath, details: 'Added projectId' };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// If parse fails, fall through to regenerate
|
|
485
|
+
}
|
|
486
|
+
return { action: 'skipped', path: projectInfoPath, details: 'Already exists' };
|
|
487
|
+
}
|
|
488
|
+
const projectHash = generateProjectHash(projectRoot);
|
|
489
|
+
const cleoVersion = getCleoVersion();
|
|
490
|
+
const now = new Date().toISOString();
|
|
491
|
+
const { readSchemaVersionFromFile } = await import('./validation/schema-integrity.js');
|
|
492
|
+
const { SQLITE_SCHEMA_VERSION } = await import('./store/sqlite.js');
|
|
493
|
+
const configSchemaVersion = readSchemaVersionFromFile('config.schema.json') ?? cleoVersion;
|
|
494
|
+
const projectContextSchemaVersion = readSchemaVersionFromFile('project-context.schema.json') ?? '1.0.0';
|
|
495
|
+
const projectInfo = {
|
|
496
|
+
$schema: './schemas/project-info.schema.json',
|
|
497
|
+
schemaVersion: '1.0.0',
|
|
498
|
+
projectId: randomUUID(),
|
|
499
|
+
projectHash,
|
|
500
|
+
cleoVersion,
|
|
501
|
+
lastUpdated: now,
|
|
502
|
+
schemas: {
|
|
503
|
+
config: configSchemaVersion,
|
|
504
|
+
sqlite: SQLITE_SCHEMA_VERSION,
|
|
505
|
+
projectContext: projectContextSchemaVersion,
|
|
506
|
+
},
|
|
507
|
+
injection: {
|
|
508
|
+
'CLAUDE.md': null,
|
|
509
|
+
'AGENTS.md': null,
|
|
510
|
+
'GEMINI.md': null,
|
|
511
|
+
},
|
|
512
|
+
health: {
|
|
513
|
+
status: 'unknown',
|
|
514
|
+
lastCheck: null,
|
|
515
|
+
issues: [],
|
|
516
|
+
},
|
|
517
|
+
features: {
|
|
518
|
+
multiSession: false,
|
|
519
|
+
verification: false,
|
|
520
|
+
contextAlerts: false,
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
await writeFile(projectInfoPath, JSON.stringify(projectInfo, null, 2));
|
|
524
|
+
return { action: 'created', path: projectInfoPath };
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* No-op. Kept for API compatibility.
|
|
528
|
+
*
|
|
529
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
530
|
+
* @returns Scaffold result with action "skipped"
|
|
531
|
+
*
|
|
532
|
+
* @remarks
|
|
533
|
+
* This function was removed in Phase 2 production readiness but the export
|
|
534
|
+
* is preserved to avoid breaking downstream callers.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```typescript
|
|
538
|
+
* const result = await ensureContributorMcp('/my/project');
|
|
539
|
+
* // result.action === 'skipped'
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
export async function ensureContributorMcp(projectRoot) {
|
|
543
|
+
return {
|
|
544
|
+
action: 'skipped',
|
|
545
|
+
path: projectRoot,
|
|
546
|
+
details: 'Removed (Phase 2 production readiness)',
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Detect and write project-context.json.
|
|
551
|
+
* Idempotent: skips if file exists and is less than staleDays old (default: 30).
|
|
552
|
+
*
|
|
553
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
554
|
+
* @param opts - Optional configuration
|
|
555
|
+
* @param opts.force - When true, regenerate even if the file is fresh
|
|
556
|
+
* @param opts.staleDays - Age threshold in days before regeneration (default: 30)
|
|
557
|
+
* @returns Scaffold result indicating the action taken
|
|
558
|
+
*
|
|
559
|
+
* @remarks
|
|
560
|
+
* Runs project type detection (Node, Python, Rust, etc.) and writes the result.
|
|
561
|
+
* Validates against the project-context schema before writing (best-effort).
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```typescript
|
|
565
|
+
* const result = await ensureProjectContext('/my/project', { staleDays: 7 });
|
|
566
|
+
* if (result.action === 'repaired') console.log('Context refreshed');
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
export async function ensureProjectContext(projectRoot, opts) {
|
|
570
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
571
|
+
const contextPath = join(cleoDir, 'project-context.json');
|
|
572
|
+
const staleDays = opts?.staleDays ?? 30;
|
|
573
|
+
if (existsSync(contextPath) && !opts?.force) {
|
|
574
|
+
try {
|
|
575
|
+
const content = JSON.parse(readFileSync(contextPath, 'utf-8'));
|
|
576
|
+
if (content.detectedAt) {
|
|
577
|
+
const detectedAt = new Date(content.detectedAt);
|
|
578
|
+
const ageMs = Date.now() - detectedAt.getTime();
|
|
579
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
580
|
+
if (ageDays < staleDays) {
|
|
581
|
+
return {
|
|
582
|
+
action: 'skipped',
|
|
583
|
+
path: contextPath,
|
|
584
|
+
details: `Fresh (${Math.floor(ageDays)}d old)`,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
// If we can't parse it, regenerate
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const { detectProjectType } = await import('./store/project-detect.js');
|
|
594
|
+
const context = detectProjectType(projectRoot);
|
|
595
|
+
// Validate against schema before writing (best-effort, never blocks write)
|
|
596
|
+
try {
|
|
597
|
+
const schemaPath = join(dirname(fileURLToPath(import.meta.url)), '../schemas/project-context.schema.json');
|
|
598
|
+
if (existsSync(schemaPath)) {
|
|
599
|
+
const AjvModule = await import('ajv');
|
|
600
|
+
const ajvMod = AjvModule;
|
|
601
|
+
const AjvClass = (typeof ajvMod.default === 'function' ? ajvMod.default : AjvModule.default);
|
|
602
|
+
const schema = JSON.parse(readFileSync(schemaPath, 'utf-8'));
|
|
603
|
+
const ajv = new AjvClass({ strict: false });
|
|
604
|
+
const valid = ajv.validate(schema, context);
|
|
605
|
+
if (!valid) {
|
|
606
|
+
// eslint-disable-next-line no-console
|
|
607
|
+
console.warn('[CLEO] project-context.json schema validation warnings:', ajv.errors);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
// Schema validation is best-effort — never block the write
|
|
613
|
+
}
|
|
614
|
+
await writeFile(contextPath, JSON.stringify(context, null, 2));
|
|
615
|
+
return {
|
|
616
|
+
action: existsSync(contextPath) ? 'repaired' : 'created',
|
|
617
|
+
path: contextPath,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Initialize isolated .cleo/.git checkpoint repository.
|
|
622
|
+
* Idempotent: skips if .cleo/.git already exists.
|
|
623
|
+
*
|
|
624
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
625
|
+
* @returns Scaffold result indicating the action taken
|
|
626
|
+
*
|
|
627
|
+
* @remarks
|
|
628
|
+
* Creates an isolated git repository inside .cleo/ for checkpoint tracking.
|
|
629
|
+
* The repo uses `GIT_DIR` and `GIT_WORK_TREE` environment variables to avoid
|
|
630
|
+
* interfering with the project's own git repository.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```typescript
|
|
634
|
+
* const result = await ensureCleoGitRepo('/my/project');
|
|
635
|
+
* console.log(result.action); // "created" or "skipped"
|
|
636
|
+
* ```
|
|
637
|
+
*/
|
|
638
|
+
export async function ensureCleoGitRepo(projectRoot) {
|
|
639
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
640
|
+
const cleoGitDir = join(cleoDir, '.git');
|
|
641
|
+
if (existsSync(cleoGitDir)) {
|
|
642
|
+
return { action: 'skipped', path: cleoGitDir, details: 'Already initialized' };
|
|
643
|
+
}
|
|
644
|
+
const gitEnv = {
|
|
645
|
+
...process.env,
|
|
646
|
+
GIT_DIR: cleoGitDir,
|
|
647
|
+
GIT_WORK_TREE: cleoDir,
|
|
648
|
+
};
|
|
649
|
+
await execFileAsync('git', ['init', '--quiet'], { cwd: cleoDir, env: gitEnv });
|
|
650
|
+
await execFileAsync('git', ['config', 'user.email', 'cleo@local'], { cwd: cleoDir, env: gitEnv });
|
|
651
|
+
await execFileAsync('git', ['config', 'user.name', 'CLEO'], { cwd: cleoDir, env: gitEnv });
|
|
652
|
+
return { action: 'created', path: cleoGitDir, details: 'Isolated checkpoint repository' };
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Create SQLite database if missing.
|
|
656
|
+
* Idempotent: skips if tasks.db already exists.
|
|
657
|
+
*
|
|
658
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
659
|
+
* @returns Scaffold result indicating the action taken
|
|
660
|
+
*
|
|
661
|
+
* @remarks
|
|
662
|
+
* Initializes the tasks.db SQLite database by calling `getDb()` which runs
|
|
663
|
+
* schema migrations. Returns a skipped result with error details if initialization fails.
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```typescript
|
|
667
|
+
* const result = await ensureSqliteDb('/my/project');
|
|
668
|
+
* if (result.action === 'created') console.log('Database ready');
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
export async function ensureSqliteDb(projectRoot) {
|
|
672
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
673
|
+
const dbPath = join(cleoDir, 'tasks.db');
|
|
674
|
+
if (existsSync(dbPath)) {
|
|
675
|
+
return { action: 'skipped', path: dbPath, details: 'tasks.db already exists' };
|
|
676
|
+
}
|
|
677
|
+
try {
|
|
678
|
+
const { getDb } = await import('./store/sqlite.js');
|
|
679
|
+
await getDb(projectRoot);
|
|
680
|
+
return { action: 'created', path: dbPath, details: 'SQLite database initialized' };
|
|
681
|
+
}
|
|
682
|
+
catch (err) {
|
|
683
|
+
return {
|
|
684
|
+
action: 'skipped',
|
|
685
|
+
path: dbPath,
|
|
686
|
+
details: `Failed to initialize SQLite: ${err instanceof Error ? err.message : String(err)}`,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// ── check* functions (read-only) ─────────────────────────────────────
|
|
691
|
+
/**
|
|
692
|
+
* Verify all required .cleo/ subdirectories exist.
|
|
693
|
+
*
|
|
694
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
695
|
+
* @returns Check result with status and list of any missing subdirectories
|
|
696
|
+
*
|
|
697
|
+
* @remarks
|
|
698
|
+
* Read-only diagnostic. Checks for the .cleo/ directory and all entries
|
|
699
|
+
* in `REQUIRED_CLEO_SUBDIRS`. Reports "failed" if .cleo/ is missing,
|
|
700
|
+
* "warning" if subdirectories are missing, "passed" otherwise.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* ```typescript
|
|
704
|
+
* const check = checkCleoStructure('/my/project');
|
|
705
|
+
* if (check.status === 'failed') console.log(check.fix);
|
|
706
|
+
* ```
|
|
707
|
+
*/
|
|
708
|
+
export function checkCleoStructure(projectRoot) {
|
|
709
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
710
|
+
const missing = [];
|
|
711
|
+
if (!existsSync(cleoDir)) {
|
|
712
|
+
return {
|
|
713
|
+
id: 'cleo_structure',
|
|
714
|
+
category: 'scaffold',
|
|
715
|
+
status: 'failed',
|
|
716
|
+
message: '.cleo/ directory does not exist',
|
|
717
|
+
details: { path: cleoDir, exists: false },
|
|
718
|
+
fix: 'cleo init',
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
for (const subdir of REQUIRED_CLEO_SUBDIRS) {
|
|
722
|
+
if (!existsSync(join(cleoDir, subdir))) {
|
|
723
|
+
missing.push(subdir);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (missing.length > 0) {
|
|
727
|
+
return {
|
|
728
|
+
id: 'cleo_structure',
|
|
729
|
+
category: 'scaffold',
|
|
730
|
+
status: 'warning',
|
|
731
|
+
message: `Missing subdirectories: ${missing.join(', ')}`,
|
|
732
|
+
details: { path: cleoDir, missing },
|
|
733
|
+
fix: 'cleo init',
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
id: 'cleo_structure',
|
|
738
|
+
category: 'scaffold',
|
|
739
|
+
status: 'passed',
|
|
740
|
+
message: 'All required .cleo/ subdirectories exist',
|
|
741
|
+
details: { path: cleoDir, subdirs: [...REQUIRED_CLEO_SUBDIRS] },
|
|
742
|
+
fix: null,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Verify .cleo/.gitignore exists and matches template.
|
|
747
|
+
*
|
|
748
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
749
|
+
* @returns Check result indicating whether the gitignore matches the template
|
|
750
|
+
*
|
|
751
|
+
* @remarks
|
|
752
|
+
* Read-only diagnostic. Normalizes whitespace before comparison.
|
|
753
|
+
* Reports "warning" if the file is missing or drifted from the template.
|
|
754
|
+
*
|
|
755
|
+
* @example
|
|
756
|
+
* ```typescript
|
|
757
|
+
* const check = checkGitignore('/my/project');
|
|
758
|
+
* if (check.status === 'warning') console.log('Gitignore drifted');
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
761
|
+
export function checkGitignore(projectRoot) {
|
|
762
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
763
|
+
const gitignorePath = join(cleoDir, '.gitignore');
|
|
764
|
+
if (!existsSync(gitignorePath)) {
|
|
765
|
+
return {
|
|
766
|
+
id: 'cleo_gitignore',
|
|
767
|
+
category: 'scaffold',
|
|
768
|
+
status: 'warning',
|
|
769
|
+
message: '.cleo/.gitignore not found',
|
|
770
|
+
details: { path: gitignorePath, exists: false },
|
|
771
|
+
fix: 'cleo init --force',
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
const installed = readFileSync(gitignorePath, 'utf-8');
|
|
775
|
+
const template = getGitignoreContent();
|
|
776
|
+
const normalize = (s) => s.trim().replace(/\r\n/g, '\n');
|
|
777
|
+
const matches = normalize(installed) === normalize(template);
|
|
778
|
+
return {
|
|
779
|
+
id: 'cleo_gitignore',
|
|
780
|
+
category: 'scaffold',
|
|
781
|
+
status: matches ? 'passed' : 'warning',
|
|
782
|
+
message: matches
|
|
783
|
+
? '.cleo/.gitignore matches template'
|
|
784
|
+
: '.cleo/.gitignore has drifted from template',
|
|
785
|
+
details: { path: gitignorePath, matchesTemplate: matches },
|
|
786
|
+
fix: matches ? null : 'cleo upgrade',
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Verify config.json exists and is valid JSON.
|
|
791
|
+
*
|
|
792
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
793
|
+
* @returns Check result indicating whether config.json is present and valid
|
|
794
|
+
*
|
|
795
|
+
* @remarks
|
|
796
|
+
* Read-only diagnostic. Reports "failed" if the file is missing or contains
|
|
797
|
+
* invalid JSON. Does not validate config schema, only JSON syntax.
|
|
798
|
+
*
|
|
799
|
+
* @example
|
|
800
|
+
* ```typescript
|
|
801
|
+
* const check = checkConfig('/my/project');
|
|
802
|
+
* if (check.status === 'passed') console.log('Config OK');
|
|
803
|
+
* ```
|
|
804
|
+
*/
|
|
805
|
+
export function checkConfig(projectRoot) {
|
|
806
|
+
const configPath = getConfigPath(projectRoot);
|
|
807
|
+
if (!existsSync(configPath)) {
|
|
808
|
+
return {
|
|
809
|
+
id: 'cleo_config',
|
|
810
|
+
category: 'scaffold',
|
|
811
|
+
status: 'failed',
|
|
812
|
+
message: 'config.json not found',
|
|
813
|
+
details: { path: configPath, exists: false },
|
|
814
|
+
fix: 'cleo init',
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
819
|
+
}
|
|
820
|
+
catch (err) {
|
|
821
|
+
return {
|
|
822
|
+
id: 'cleo_config',
|
|
823
|
+
category: 'scaffold',
|
|
824
|
+
status: 'failed',
|
|
825
|
+
message: `config.json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
826
|
+
details: { path: configPath, valid: false },
|
|
827
|
+
fix: 'cleo init --force',
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
id: 'cleo_config',
|
|
832
|
+
category: 'scaffold',
|
|
833
|
+
status: 'passed',
|
|
834
|
+
message: 'config.json exists and is valid JSON',
|
|
835
|
+
details: { path: configPath, valid: true },
|
|
836
|
+
fix: null,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Verify project-info.json exists with required fields.
|
|
841
|
+
*
|
|
842
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
843
|
+
* @returns Check result indicating whether project-info.json is valid
|
|
844
|
+
*
|
|
845
|
+
* @remarks
|
|
846
|
+
* Read-only diagnostic. Checks for presence and required fields: projectHash,
|
|
847
|
+
* cleoVersion, and lastUpdated. Reports "warning" on missing fields, "failed" on
|
|
848
|
+
* missing file or invalid JSON.
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* ```typescript
|
|
852
|
+
* const check = checkProjectInfo('/my/project');
|
|
853
|
+
* if (check.status !== 'passed') console.log(check.fix);
|
|
854
|
+
* ```
|
|
855
|
+
*/
|
|
856
|
+
export function checkProjectInfo(projectRoot) {
|
|
857
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
858
|
+
const infoPath = join(cleoDir, 'project-info.json');
|
|
859
|
+
if (!existsSync(infoPath)) {
|
|
860
|
+
return {
|
|
861
|
+
id: 'cleo_project_info',
|
|
862
|
+
category: 'scaffold',
|
|
863
|
+
status: 'warning',
|
|
864
|
+
message: 'project-info.json not found',
|
|
865
|
+
details: { path: infoPath, exists: false },
|
|
866
|
+
fix: 'cleo init',
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
const content = JSON.parse(readFileSync(infoPath, 'utf-8'));
|
|
871
|
+
const requiredFields = ['projectHash', 'cleoVersion', 'lastUpdated'];
|
|
872
|
+
const missing = requiredFields.filter((f) => !(f in content));
|
|
873
|
+
if (missing.length > 0) {
|
|
874
|
+
return {
|
|
875
|
+
id: 'cleo_project_info',
|
|
876
|
+
category: 'scaffold',
|
|
877
|
+
status: 'warning',
|
|
878
|
+
message: `project-info.json missing fields: ${missing.join(', ')}`,
|
|
879
|
+
details: { path: infoPath, missingFields: missing },
|
|
880
|
+
fix: 'cleo init --force',
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
id: 'cleo_project_info',
|
|
885
|
+
category: 'scaffold',
|
|
886
|
+
status: 'passed',
|
|
887
|
+
message: 'project-info.json exists with all required fields',
|
|
888
|
+
details: { path: infoPath, valid: true },
|
|
889
|
+
fix: null,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
return {
|
|
894
|
+
id: 'cleo_project_info',
|
|
895
|
+
category: 'scaffold',
|
|
896
|
+
status: 'failed',
|
|
897
|
+
message: `project-info.json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
898
|
+
details: { path: infoPath, valid: false },
|
|
899
|
+
fix: 'cleo init --force',
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Verify project-context.json exists and is not stale (default: 30 days).
|
|
905
|
+
*
|
|
906
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
907
|
+
* @param staleDays - Age threshold in days before reporting as stale (default: 30)
|
|
908
|
+
* @returns Check result with freshness assessment
|
|
909
|
+
*
|
|
910
|
+
* @remarks
|
|
911
|
+
* Read-only diagnostic. Checks for the `detectedAt` timestamp and compares
|
|
912
|
+
* its age against the staleness threshold. Reports "warning" if stale or
|
|
913
|
+
* missing, "failed" if the file contains invalid JSON.
|
|
914
|
+
*
|
|
915
|
+
* @example
|
|
916
|
+
* ```typescript
|
|
917
|
+
* const check = checkProjectContext('/my/project', 14);
|
|
918
|
+
* if (check.status === 'warning') console.log('Context is stale');
|
|
919
|
+
* ```
|
|
920
|
+
*/
|
|
921
|
+
export function checkProjectContext(projectRoot, staleDays = 30) {
|
|
922
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
923
|
+
const contextPath = join(cleoDir, 'project-context.json');
|
|
924
|
+
if (!existsSync(contextPath)) {
|
|
925
|
+
return {
|
|
926
|
+
id: 'cleo_project_context',
|
|
927
|
+
category: 'scaffold',
|
|
928
|
+
status: 'warning',
|
|
929
|
+
message: 'project-context.json not found',
|
|
930
|
+
details: { path: contextPath, exists: false },
|
|
931
|
+
fix: 'cleo init --detect',
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
const content = JSON.parse(readFileSync(contextPath, 'utf-8'));
|
|
936
|
+
if (!content.detectedAt) {
|
|
937
|
+
return {
|
|
938
|
+
id: 'cleo_project_context',
|
|
939
|
+
category: 'scaffold',
|
|
940
|
+
status: 'warning',
|
|
941
|
+
message: 'project-context.json missing detectedAt timestamp',
|
|
942
|
+
details: { path: contextPath, hasTimestamp: false },
|
|
943
|
+
fix: 'cleo init --detect',
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
const detectedAt = new Date(content.detectedAt);
|
|
947
|
+
const ageMs = Date.now() - detectedAt.getTime();
|
|
948
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
949
|
+
if (ageDays > staleDays) {
|
|
950
|
+
return {
|
|
951
|
+
id: 'cleo_project_context',
|
|
952
|
+
category: 'scaffold',
|
|
953
|
+
status: 'warning',
|
|
954
|
+
message: `project-context.json is stale (${Math.floor(ageDays)} days old, threshold: ${staleDays})`,
|
|
955
|
+
details: { path: contextPath, ageDays: Math.floor(ageDays), staleDays },
|
|
956
|
+
fix: 'cleo init --detect',
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
id: 'cleo_project_context',
|
|
961
|
+
category: 'scaffold',
|
|
962
|
+
status: 'passed',
|
|
963
|
+
message: `project-context.json is fresh (${Math.floor(ageDays)} days old)`,
|
|
964
|
+
details: { path: contextPath, ageDays: Math.floor(ageDays), staleDays },
|
|
965
|
+
fix: null,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
catch (err) {
|
|
969
|
+
return {
|
|
970
|
+
id: 'cleo_project_context',
|
|
971
|
+
category: 'scaffold',
|
|
972
|
+
status: 'failed',
|
|
973
|
+
message: `project-context.json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
974
|
+
details: { path: contextPath, valid: false },
|
|
975
|
+
fix: 'cleo init --detect',
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Verify .cleo/.git checkpoint repository exists.
|
|
981
|
+
*
|
|
982
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
983
|
+
* @returns Check result indicating whether the checkpoint repo is present
|
|
984
|
+
*
|
|
985
|
+
* @remarks
|
|
986
|
+
* Read-only diagnostic. Reports "warning" if the .cleo/.git directory is missing.
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* ```typescript
|
|
990
|
+
* const check = checkCleoGitRepo('/my/project');
|
|
991
|
+
* console.log(check.status); // "passed" or "warning"
|
|
992
|
+
* ```
|
|
993
|
+
*/
|
|
994
|
+
export function checkCleoGitRepo(projectRoot) {
|
|
995
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
996
|
+
const cleoGitDir = join(cleoDir, '.git');
|
|
997
|
+
if (!existsSync(cleoGitDir)) {
|
|
998
|
+
return {
|
|
999
|
+
id: 'cleo_git_repo',
|
|
1000
|
+
category: 'scaffold',
|
|
1001
|
+
status: 'warning',
|
|
1002
|
+
message: '.cleo/.git checkpoint repository not found',
|
|
1003
|
+
details: { path: cleoGitDir, exists: false },
|
|
1004
|
+
fix: 'cleo init',
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
id: 'cleo_git_repo',
|
|
1009
|
+
category: 'scaffold',
|
|
1010
|
+
status: 'passed',
|
|
1011
|
+
message: '.cleo/.git checkpoint repository exists',
|
|
1012
|
+
details: { path: cleoGitDir, exists: true },
|
|
1013
|
+
fix: null,
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Verify .cleo/tasks.db exists and is non-empty.
|
|
1018
|
+
*
|
|
1019
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
1020
|
+
* @returns Check result with database existence and size information
|
|
1021
|
+
*
|
|
1022
|
+
* @remarks
|
|
1023
|
+
* Read-only diagnostic. Reports "failed" if the file is missing, "warning" if
|
|
1024
|
+
* the file exists but is 0 bytes, "passed" otherwise.
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```typescript
|
|
1028
|
+
* const check = checkSqliteDb('/my/project');
|
|
1029
|
+
* if (check.status === 'failed') console.log('No database:', check.fix);
|
|
1030
|
+
* ```
|
|
1031
|
+
*/
|
|
1032
|
+
export function checkSqliteDb(projectRoot) {
|
|
1033
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
1034
|
+
const dbPath = join(cleoDir, 'tasks.db');
|
|
1035
|
+
if (!existsSync(dbPath)) {
|
|
1036
|
+
return {
|
|
1037
|
+
id: 'sqlite_db',
|
|
1038
|
+
category: 'scaffold',
|
|
1039
|
+
status: 'failed',
|
|
1040
|
+
message: 'tasks.db not found',
|
|
1041
|
+
details: { path: dbPath, exists: false },
|
|
1042
|
+
fix: 'cleo init',
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
const stat = statSync(dbPath);
|
|
1046
|
+
if (stat.size === 0) {
|
|
1047
|
+
return {
|
|
1048
|
+
id: 'sqlite_db',
|
|
1049
|
+
category: 'scaffold',
|
|
1050
|
+
status: 'warning',
|
|
1051
|
+
message: 'tasks.db exists but is empty (0 bytes)',
|
|
1052
|
+
details: { path: dbPath, exists: true, size: 0 },
|
|
1053
|
+
fix: 'cleo upgrade',
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
id: 'sqlite_db',
|
|
1058
|
+
category: 'scaffold',
|
|
1059
|
+
status: 'passed',
|
|
1060
|
+
message: `tasks.db exists (${stat.size} bytes)`,
|
|
1061
|
+
details: { path: dbPath, exists: true, size: stat.size },
|
|
1062
|
+
fix: null,
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Create brain.db if missing.
|
|
1067
|
+
* Idempotent: skips if brain.db already exists.
|
|
1068
|
+
*
|
|
1069
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
1070
|
+
* @returns Scaffold result indicating the action taken
|
|
1071
|
+
*
|
|
1072
|
+
* @remarks
|
|
1073
|
+
* Initializes brain.db with Drizzle schema and eagerly creates FTS5 virtual
|
|
1074
|
+
* tables for full-text search (T5698). Also ensures FTS5 tables on pre-existing
|
|
1075
|
+
* databases that may lack them.
|
|
1076
|
+
*
|
|
1077
|
+
* @example
|
|
1078
|
+
* ```typescript
|
|
1079
|
+
* const result = await ensureBrainDb('/my/project');
|
|
1080
|
+
* if (result.action === 'created') console.log('Brain database ready');
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
export async function ensureBrainDb(projectRoot) {
|
|
1084
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
1085
|
+
const dbPath = join(cleoDir, 'brain.db');
|
|
1086
|
+
if (existsSync(dbPath)) {
|
|
1087
|
+
// Ensure FTS5 tables exist even for pre-existing brain.db (T5698)
|
|
1088
|
+
try {
|
|
1089
|
+
const { getBrainNativeDb } = await import('./store/brain-sqlite.js');
|
|
1090
|
+
const nativeDb = getBrainNativeDb();
|
|
1091
|
+
if (nativeDb) {
|
|
1092
|
+
const { ensureFts5Tables } = await import('./memory/brain-search.js');
|
|
1093
|
+
ensureFts5Tables(nativeDb);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
catch {
|
|
1097
|
+
// Non-fatal — FTS5 may not be available
|
|
1098
|
+
}
|
|
1099
|
+
return { action: 'skipped', path: dbPath, details: 'brain.db already exists' };
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
const { getBrainDb, getBrainNativeDb } = await import('./store/brain-sqlite.js');
|
|
1103
|
+
await getBrainDb(projectRoot);
|
|
1104
|
+
// Create FTS5 virtual tables eagerly so search works immediately (T5698)
|
|
1105
|
+
try {
|
|
1106
|
+
const nativeDb = getBrainNativeDb();
|
|
1107
|
+
if (nativeDb) {
|
|
1108
|
+
const { ensureFts5Tables } = await import('./memory/brain-search.js');
|
|
1109
|
+
ensureFts5Tables(nativeDb);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
catch {
|
|
1113
|
+
// FTS5 may not be available in all SQLite builds — non-fatal
|
|
1114
|
+
}
|
|
1115
|
+
return { action: 'created', path: dbPath, details: 'Brain database initialized with FTS5' };
|
|
1116
|
+
}
|
|
1117
|
+
catch (err) {
|
|
1118
|
+
return {
|
|
1119
|
+
action: 'skipped',
|
|
1120
|
+
path: dbPath,
|
|
1121
|
+
details: `Failed to initialize brain.db: ${err instanceof Error ? err.message : String(err)}`,
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Verify .cleo/brain.db exists and is non-empty.
|
|
1127
|
+
*
|
|
1128
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
1129
|
+
* @returns Check result with database existence and size information
|
|
1130
|
+
*
|
|
1131
|
+
* @remarks
|
|
1132
|
+
* Read-only diagnostic. Reports "failed" if the file is missing, "warning" if
|
|
1133
|
+
* the file exists but is 0 bytes, "passed" otherwise.
|
|
1134
|
+
*
|
|
1135
|
+
* @example
|
|
1136
|
+
* ```typescript
|
|
1137
|
+
* const check = checkBrainDb('/my/project');
|
|
1138
|
+
* console.log(check.status);
|
|
1139
|
+
* ```
|
|
1140
|
+
*/
|
|
1141
|
+
export function checkBrainDb(projectRoot) {
|
|
1142
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
1143
|
+
const dbPath = join(cleoDir, 'brain.db');
|
|
1144
|
+
if (!existsSync(dbPath)) {
|
|
1145
|
+
return {
|
|
1146
|
+
id: 'brain_db',
|
|
1147
|
+
category: 'scaffold',
|
|
1148
|
+
status: 'failed',
|
|
1149
|
+
message: 'brain.db not found',
|
|
1150
|
+
details: { path: dbPath, exists: false },
|
|
1151
|
+
fix: 'cleo init',
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const stat = statSync(dbPath);
|
|
1155
|
+
if (stat.size === 0) {
|
|
1156
|
+
return {
|
|
1157
|
+
id: 'brain_db',
|
|
1158
|
+
category: 'scaffold',
|
|
1159
|
+
status: 'warning',
|
|
1160
|
+
message: 'brain.db exists but is empty (0 bytes)',
|
|
1161
|
+
details: { path: dbPath, exists: true, size: 0 },
|
|
1162
|
+
fix: 'cleo upgrade',
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return {
|
|
1166
|
+
id: 'brain_db',
|
|
1167
|
+
category: 'scaffold',
|
|
1168
|
+
status: 'passed',
|
|
1169
|
+
message: `brain.db exists (${stat.size} bytes)`,
|
|
1170
|
+
details: { path: dbPath, exists: true, size: stat.size },
|
|
1171
|
+
fix: null,
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Verify .cleo/memory-bridge.md exists.
|
|
1176
|
+
* Warning level if missing (not failure) -- it is auto-generated.
|
|
1177
|
+
*
|
|
1178
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
1179
|
+
* @returns Check result indicating presence of the memory bridge file
|
|
1180
|
+
*
|
|
1181
|
+
* @remarks
|
|
1182
|
+
* Read-only diagnostic. The memory bridge is auto-generated from brain.db
|
|
1183
|
+
* content, so its absence is only a warning (not a failure).
|
|
1184
|
+
*
|
|
1185
|
+
* @example
|
|
1186
|
+
* ```typescript
|
|
1187
|
+
* const check = checkMemoryBridge('/my/project');
|
|
1188
|
+
* if (check.status === 'warning') console.log('Run: cleo refresh-memory');
|
|
1189
|
+
* ```
|
|
1190
|
+
*/
|
|
1191
|
+
export function checkMemoryBridge(projectRoot) {
|
|
1192
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
1193
|
+
const bridgePath = join(cleoDir, 'memory-bridge.md');
|
|
1194
|
+
if (!existsSync(bridgePath)) {
|
|
1195
|
+
return {
|
|
1196
|
+
id: 'memory_bridge',
|
|
1197
|
+
category: 'scaffold',
|
|
1198
|
+
status: 'warning',
|
|
1199
|
+
message: 'memory-bridge.md not found',
|
|
1200
|
+
details: { path: bridgePath, exists: false },
|
|
1201
|
+
fix: 'cleo init or cleo refresh-memory',
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
id: 'memory_bridge',
|
|
1206
|
+
category: 'scaffold',
|
|
1207
|
+
status: 'passed',
|
|
1208
|
+
message: 'memory-bridge.md exists',
|
|
1209
|
+
details: { path: bridgePath, exists: true },
|
|
1210
|
+
fix: null,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
// ── Global (~/.cleo) scaffold functions ──────────────────────────────
|
|
1214
|
+
/**
|
|
1215
|
+
* Required subdirectories under the global ~/.cleo/ home.
|
|
1216
|
+
* These are infrastructure directories managed by CLEO itself,
|
|
1217
|
+
* not project-specific data.
|
|
1218
|
+
*
|
|
1219
|
+
* Truly global:
|
|
1220
|
+
* - logs — global log output
|
|
1221
|
+
* - templates — CLEO-INJECTION.md symlink target
|
|
1222
|
+
*
|
|
1223
|
+
* Note: nexus.db lives directly in ~/.cleo/, not a subdir.
|
|
1224
|
+
* Schemas are read at runtime from getPackageRoot()/schemas/ — no copy needed.
|
|
1225
|
+
* Project-level dirs (adrs/, rcasd/, agent-outputs/, backups/) live in .cleo/ only.
|
|
1226
|
+
*/
|
|
1227
|
+
export const REQUIRED_GLOBAL_SUBDIRS = [
|
|
1228
|
+
'logs',
|
|
1229
|
+
'templates',
|
|
1230
|
+
// CleoOS hub (Phase 1): cross-project recipe library, Pi extensions, CANT workflows
|
|
1231
|
+
'global-recipes',
|
|
1232
|
+
'pi-extensions',
|
|
1233
|
+
'cant-workflows',
|
|
1234
|
+
'agents',
|
|
1235
|
+
];
|
|
1236
|
+
/**
|
|
1237
|
+
* Stale entries that must NOT exist at the global ~/.cleo/ level.
|
|
1238
|
+
* These were project-level artefacts accidentally placed in the global home
|
|
1239
|
+
* by old versions of CLEO. They are safe to remove because:
|
|
1240
|
+
* - adrs/, rcasd/, agent-outputs/, backups/ — project-level, live in .cleo/
|
|
1241
|
+
* - sandbox/ — unused
|
|
1242
|
+
* - tasks.db / tasks.db-shm / tasks.db-wal — project-level database files
|
|
1243
|
+
* - brain-worker.pid — stale PID file
|
|
1244
|
+
* - VERSION — redundant (read from package.json)
|
|
1245
|
+
* - schemas/ — now read from npm binary at runtime
|
|
1246
|
+
* - bin/ — stale dev symlink directory
|
|
1247
|
+
*/
|
|
1248
|
+
export const STALE_GLOBAL_ENTRIES = [
|
|
1249
|
+
'adrs',
|
|
1250
|
+
'rcasd',
|
|
1251
|
+
'agent-outputs',
|
|
1252
|
+
'backups',
|
|
1253
|
+
'sandbox',
|
|
1254
|
+
'tasks.db',
|
|
1255
|
+
'tasks.db-shm',
|
|
1256
|
+
'tasks.db-wal',
|
|
1257
|
+
'brain-worker.pid',
|
|
1258
|
+
'VERSION',
|
|
1259
|
+
'schemas',
|
|
1260
|
+
'bin',
|
|
1261
|
+
'.install-state',
|
|
1262
|
+
'templates/templates',
|
|
1263
|
+
];
|
|
1264
|
+
/**
|
|
1265
|
+
* Ensure the global ~/.cleo/ home directory and its required
|
|
1266
|
+
* subdirectories exist. Idempotent: skips directories that already exist.
|
|
1267
|
+
*
|
|
1268
|
+
* Also removes known stale project-level entries that old CLEO versions
|
|
1269
|
+
* incorrectly placed at the global level (see STALE_GLOBAL_ENTRIES).
|
|
1270
|
+
*
|
|
1271
|
+
* This is the SSoT for global home scaffolding, replacing raw mkdirSync
|
|
1272
|
+
* calls that were previously scattered across global-bootstrap.ts.
|
|
1273
|
+
*
|
|
1274
|
+
* @returns Scaffold result indicating the action taken
|
|
1275
|
+
*
|
|
1276
|
+
* @remarks
|
|
1277
|
+
* Also creates a global config.json from template if absent. Cleans up
|
|
1278
|
+
* stale entries from both the current and legacy (~/.cleo/) global home paths.
|
|
1279
|
+
*
|
|
1280
|
+
* @example
|
|
1281
|
+
* ```typescript
|
|
1282
|
+
* const result = await ensureGlobalHome();
|
|
1283
|
+
* console.log(result.action); // "created" or "skipped"
|
|
1284
|
+
* ```
|
|
1285
|
+
*/
|
|
1286
|
+
export async function ensureGlobalHome() {
|
|
1287
|
+
const cleoHome = getCleoHome();
|
|
1288
|
+
const alreadyExists = existsSync(cleoHome);
|
|
1289
|
+
await mkdir(cleoHome, { recursive: true });
|
|
1290
|
+
for (const subdir of REQUIRED_GLOBAL_SUBDIRS) {
|
|
1291
|
+
await mkdir(join(cleoHome, subdir), { recursive: true });
|
|
1292
|
+
}
|
|
1293
|
+
// Create global config.json from template if it doesn't exist
|
|
1294
|
+
const globalConfigPath = join(cleoHome, 'config.json');
|
|
1295
|
+
if (!existsSync(globalConfigPath)) {
|
|
1296
|
+
const templatePath = join(getPackageRoot(), 'templates', 'global-config.template.json');
|
|
1297
|
+
if (existsSync(templatePath)) {
|
|
1298
|
+
const template = readFileSync(templatePath, 'utf-8');
|
|
1299
|
+
const resolved = template.replace('{{SCHEMA_VERSION_GLOBAL_CONFIG}}', '1.0.0');
|
|
1300
|
+
await writeFile(globalConfigPath, resolved);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
// Remove stale project-level entries from both the current global home
|
|
1304
|
+
// AND the legacy ~/.cleo/ path (old CLEO versions used ~/.cleo/ before
|
|
1305
|
+
// env-paths moved it to the XDG-compliant location).
|
|
1306
|
+
const homedir = (await import('node:os')).homedir();
|
|
1307
|
+
const legacyCleoHome = join(homedir, '.cleo');
|
|
1308
|
+
const cleanupPaths = [cleoHome];
|
|
1309
|
+
if (legacyCleoHome !== cleoHome && existsSync(legacyCleoHome)) {
|
|
1310
|
+
cleanupPaths.push(legacyCleoHome);
|
|
1311
|
+
}
|
|
1312
|
+
for (const homeDir of cleanupPaths) {
|
|
1313
|
+
for (const stale of STALE_GLOBAL_ENTRIES) {
|
|
1314
|
+
const stalePath = join(homeDir, stale);
|
|
1315
|
+
if (existsSync(stalePath)) {
|
|
1316
|
+
try {
|
|
1317
|
+
await rm(stalePath, { recursive: true, force: true });
|
|
1318
|
+
// eslint-disable-next-line no-console
|
|
1319
|
+
console.warn(`[CLEO] Removed stale global entry: ${stalePath}`);
|
|
1320
|
+
}
|
|
1321
|
+
catch (err) {
|
|
1322
|
+
// eslint-disable-next-line no-console
|
|
1323
|
+
console.warn(`[CLEO] Could not remove stale global entry ${stalePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
action: alreadyExists ? 'skipped' : 'created',
|
|
1330
|
+
path: cleoHome,
|
|
1331
|
+
details: alreadyExists
|
|
1332
|
+
? 'Global home already existed, ensured subdirs'
|
|
1333
|
+
: `Created ~/.cleo/ with ${REQUIRED_GLOBAL_SUBDIRS.length} subdirectories`,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Ensure the global CLEO injection template is installed.
|
|
1338
|
+
* Delegates to injection.ts for the template content, but owns the
|
|
1339
|
+
* filesystem write to maintain SSoT for scaffolding.
|
|
1340
|
+
*
|
|
1341
|
+
* Idempotent: skips if the template already exists with correct content.
|
|
1342
|
+
*
|
|
1343
|
+
* @returns Scaffold result indicating the action taken
|
|
1344
|
+
*
|
|
1345
|
+
* @remarks
|
|
1346
|
+
* Lazy-imports injection.ts to avoid circular dependencies. Compares file
|
|
1347
|
+
* content byte-for-byte; repairs if the installed version differs from the
|
|
1348
|
+
* bundled template.
|
|
1349
|
+
*
|
|
1350
|
+
* @example
|
|
1351
|
+
* ```typescript
|
|
1352
|
+
* const result = await ensureGlobalTemplates();
|
|
1353
|
+
* if (result.action === 'repaired') console.log('Template updated');
|
|
1354
|
+
* ```
|
|
1355
|
+
*/
|
|
1356
|
+
export async function ensureGlobalTemplates() {
|
|
1357
|
+
// Lazy import to avoid circular dependency (injection imports scaffold)
|
|
1358
|
+
const { getInjectionTemplateContent } = await import('./injection.js');
|
|
1359
|
+
const templatesDir = getCleoTemplatesDir();
|
|
1360
|
+
const injectionPath = join(templatesDir, 'CLEO-INJECTION.md');
|
|
1361
|
+
// Ensure directory exists (idempotent via ensureGlobalHome, but defensive)
|
|
1362
|
+
await mkdir(templatesDir, { recursive: true });
|
|
1363
|
+
const templateContent = getInjectionTemplateContent();
|
|
1364
|
+
if (!templateContent) {
|
|
1365
|
+
return {
|
|
1366
|
+
action: 'skipped',
|
|
1367
|
+
path: injectionPath,
|
|
1368
|
+
details: 'Bundled injection template not found; skipped',
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
if (existsSync(injectionPath)) {
|
|
1372
|
+
const existing = readFileSync(injectionPath, 'utf-8');
|
|
1373
|
+
if (existing === templateContent) {
|
|
1374
|
+
return { action: 'skipped', path: injectionPath, details: 'Template already current' };
|
|
1375
|
+
}
|
|
1376
|
+
// Content differs — repair
|
|
1377
|
+
await writeFile(injectionPath, templateContent, 'utf-8');
|
|
1378
|
+
return {
|
|
1379
|
+
action: 'repaired',
|
|
1380
|
+
path: injectionPath,
|
|
1381
|
+
details: 'Updated injection template to match bundled version',
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
await writeFile(injectionPath, templateContent, 'utf-8');
|
|
1385
|
+
return { action: 'created', path: injectionPath };
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Perform a complete global scaffold operation: ensure home and templates
|
|
1389
|
+
* are all present and current. This is the single entry point for global
|
|
1390
|
+
* infrastructure scaffolding.
|
|
1391
|
+
*
|
|
1392
|
+
* Schemas are NOT copied here — they are read at runtime from the npm
|
|
1393
|
+
* package path (getPackageRoot() + '/schemas/'). Use ensureGlobalSchemas()
|
|
1394
|
+
* explicitly from init or upgrade if a copy is needed for a specific workflow.
|
|
1395
|
+
*
|
|
1396
|
+
* Used by:
|
|
1397
|
+
* - CLI startup (via startupHealthCheck in health.ts)
|
|
1398
|
+
* - init (for first-time global setup)
|
|
1399
|
+
* - upgrade (for global repair)
|
|
1400
|
+
*
|
|
1401
|
+
* @returns Combined scaffold results for home and templates
|
|
1402
|
+
*
|
|
1403
|
+
* @remarks
|
|
1404
|
+
* Calls ensureGlobalHome and ensureGlobalTemplates in sequence.
|
|
1405
|
+
* Schemas are NOT copied here -- they are read at runtime from the npm
|
|
1406
|
+
* package path.
|
|
1407
|
+
*
|
|
1408
|
+
* @example
|
|
1409
|
+
* ```typescript
|
|
1410
|
+
* const { home, templates } = await ensureGlobalScaffold();
|
|
1411
|
+
* console.log(home.action, templates.action);
|
|
1412
|
+
* ```
|
|
1413
|
+
*/
|
|
1414
|
+
export async function ensureGlobalScaffold() {
|
|
1415
|
+
const home = await ensureGlobalHome();
|
|
1416
|
+
const templates = await ensureGlobalTemplates();
|
|
1417
|
+
const cleoosHub = await ensureCleoOsHub();
|
|
1418
|
+
return { home, templates, cleoosHub };
|
|
1419
|
+
}
|
|
1420
|
+
// ── CleoOS Hub scaffolding (Phase 1) ─────────────────────────────────
|
|
1421
|
+
/**
|
|
1422
|
+
* Recursively copy a template tree from {@link srcDir} into {@link dstDir}.
|
|
1423
|
+
*
|
|
1424
|
+
* Never overwrites existing files: any file that already exists at the
|
|
1425
|
+
* destination is left untouched, preserving user/agent edits. Missing
|
|
1426
|
+
* subdirectories are created on demand. Symbolic links and special files
|
|
1427
|
+
* are skipped silently.
|
|
1428
|
+
*
|
|
1429
|
+
* @param srcDir - Absolute path to the template source directory
|
|
1430
|
+
* @param dstDir - Absolute path to the destination directory
|
|
1431
|
+
* @returns Counts of files copied vs files that already existed and were kept
|
|
1432
|
+
*
|
|
1433
|
+
* @remarks
|
|
1434
|
+
* Returns `{ copied: 0, kept: 0 }` if the source directory does not exist.
|
|
1435
|
+
* Designed as the workhorse for {@link ensureCleoOsHub} and any other
|
|
1436
|
+
* idempotent template scaffolding that ships with the npm package.
|
|
1437
|
+
*
|
|
1438
|
+
* @example
|
|
1439
|
+
* ```typescript
|
|
1440
|
+
* const { copied, kept } = await copyTemplateTree(
|
|
1441
|
+
* '/usr/lib/node_modules/@cleocode/cleo/templates/cleoos-hub/pi-extensions',
|
|
1442
|
+
* '/home/me/.local/share/cleo/pi-extensions',
|
|
1443
|
+
* );
|
|
1444
|
+
* console.log(`copied ${copied}, kept ${kept}`);
|
|
1445
|
+
* ```
|
|
1446
|
+
*/
|
|
1447
|
+
async function copyTemplateTree(srcDir, dstDir) {
|
|
1448
|
+
if (!existsSync(srcDir)) {
|
|
1449
|
+
return { copied: 0, kept: 0 };
|
|
1450
|
+
}
|
|
1451
|
+
await mkdir(dstDir, { recursive: true });
|
|
1452
|
+
let copied = 0;
|
|
1453
|
+
let kept = 0;
|
|
1454
|
+
let entries;
|
|
1455
|
+
try {
|
|
1456
|
+
entries = await readdir(srcDir, { withFileTypes: true });
|
|
1457
|
+
}
|
|
1458
|
+
catch (err) {
|
|
1459
|
+
// eslint-disable-next-line no-console
|
|
1460
|
+
console.warn(`[CLEO] Could not read template dir ${srcDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1461
|
+
return { copied: 0, kept: 0 };
|
|
1462
|
+
}
|
|
1463
|
+
for (const entry of entries) {
|
|
1464
|
+
const srcPath = join(srcDir, entry.name);
|
|
1465
|
+
const dstPath = join(dstDir, entry.name);
|
|
1466
|
+
if (entry.isDirectory()) {
|
|
1467
|
+
const sub = await copyTemplateTree(srcPath, dstPath);
|
|
1468
|
+
copied += sub.copied;
|
|
1469
|
+
kept += sub.kept;
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
if (!entry.isFile()) {
|
|
1473
|
+
// Skip symlinks, sockets, devices, etc.
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
if (existsSync(dstPath)) {
|
|
1477
|
+
kept += 1;
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
try {
|
|
1481
|
+
await copyFile(srcPath, dstPath);
|
|
1482
|
+
copied += 1;
|
|
1483
|
+
}
|
|
1484
|
+
catch (err) {
|
|
1485
|
+
// eslint-disable-next-line no-console
|
|
1486
|
+
console.warn(`[CLEO] Could not copy template file ${srcPath} -> ${dstPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return { copied, kept };
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Resolve the absolute root directory of the bundled CleoOS hub templates.
|
|
1493
|
+
*
|
|
1494
|
+
* The templates ship inside the `@cleocode/cleo` package at
|
|
1495
|
+
* `templates/cleoos-hub/`. Because `scaffold.ts` is authored in
|
|
1496
|
+
* `@cleocode/core` but is also esbuilt directly into the `@cleocode/cleo`
|
|
1497
|
+
* CLI bundle, the runtime location of `import.meta.url` (and therefore
|
|
1498
|
+
* {@link getPackageRoot}) varies across deployment shapes. The candidate
|
|
1499
|
+
* list below covers every layout we ship:
|
|
1500
|
+
*
|
|
1501
|
+
* 1. **Bundled cleo (npm install)** — `dist/cli/index.js` lives at
|
|
1502
|
+
* `node_modules/@cleocode/cleo/dist/cli/index.js`, so
|
|
1503
|
+
* `getPackageRoot()` resolves to `node_modules/@cleocode/cleo/dist`
|
|
1504
|
+
* and `../templates/cleoos-hub` reaches the package's templates dir.
|
|
1505
|
+
* 2. **Bundled cleo (dev workspace via build.mjs)** — `dist/cli/index.js`
|
|
1506
|
+
* lives at `packages/cleo/dist/cli/index.js`; same `../templates/...`
|
|
1507
|
+
* candidate as above.
|
|
1508
|
+
* 3. **Unbundled core (workspace tsc)** — scaffold.ts is loaded as
|
|
1509
|
+
* `packages/core/src/scaffold.ts`, so `getPackageRoot()` resolves to
|
|
1510
|
+
* `packages/core/` and `../cleo/templates/cleoos-hub` reaches the
|
|
1511
|
+
* sibling cleo package.
|
|
1512
|
+
* 4. **Unbundled core (npm install of just @cleocode/core)** — the
|
|
1513
|
+
* scope-sibling layout puts both packages under
|
|
1514
|
+
* `node_modules/@cleocode/`, so the same `../cleo/templates/...`
|
|
1515
|
+
* candidate works.
|
|
1516
|
+
* 5. **Legacy fallback** — if the cleo package ever bundles the templates
|
|
1517
|
+
* directly under core, fall through to
|
|
1518
|
+
* `getPackageRoot()/templates/cleoos-hub`.
|
|
1519
|
+
*
|
|
1520
|
+
* @returns Absolute path to the existing template root, or `null` if none found
|
|
1521
|
+
*
|
|
1522
|
+
* @remarks
|
|
1523
|
+
* Returning `null` allows callers to skip the scaffold gracefully instead of
|
|
1524
|
+
* crashing — important for the never-crash scaffold contract.
|
|
1525
|
+
*/
|
|
1526
|
+
function resolveCleoOsHubTemplateRoot() {
|
|
1527
|
+
const packageRoot = getPackageRoot();
|
|
1528
|
+
const candidates = [
|
|
1529
|
+
// Bundled cleo: getPackageRoot() = .../@cleocode/cleo/dist
|
|
1530
|
+
join(packageRoot, '..', 'templates', 'cleoos-hub'),
|
|
1531
|
+
// Unbundled core in workspace or scope-sibling install
|
|
1532
|
+
join(packageRoot, '..', 'cleo', 'templates', 'cleoos-hub'),
|
|
1533
|
+
// Workspace from monorepo root resolution (defensive)
|
|
1534
|
+
join(packageRoot, '..', '..', 'packages', 'cleo', 'templates', 'cleoos-hub'),
|
|
1535
|
+
// Legacy in-place fallback
|
|
1536
|
+
join(packageRoot, 'templates', 'cleoos-hub'),
|
|
1537
|
+
];
|
|
1538
|
+
return candidates.find((p) => existsSync(p)) ?? null;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Ensure the CleoOS Hub subdirectories exist under the global CLEO home,
|
|
1542
|
+
* and seed all bundled hub templates if they are not already present.
|
|
1543
|
+
*
|
|
1544
|
+
* This is the Phase 1 scaffolding entry point. Idempotent: re-running is
|
|
1545
|
+
* safe and will never overwrite a file that already exists — human and
|
|
1546
|
+
* agent edits to the installed copies are always preserved.
|
|
1547
|
+
*
|
|
1548
|
+
* @returns Scaffold result for the CleoOS hub root
|
|
1549
|
+
*
|
|
1550
|
+
* @remarks
|
|
1551
|
+
* The CleoOS hub currently consists of:
|
|
1552
|
+
* - `global-recipes/` (Justfile Hub: justfile + README)
|
|
1553
|
+
* - `pi-extensions/` (Pi extensions: orchestrator, stage-guide, cant-bridge)
|
|
1554
|
+
* - `cant-workflows/` (CANT workflow library — created empty for agents)
|
|
1555
|
+
* - `agents/` (Global CANT agent definitions — created empty)
|
|
1556
|
+
*
|
|
1557
|
+
* Templates are sourced from the `@cleocode/cleo` package's bundled
|
|
1558
|
+
* `templates/cleoos-hub/` tree. The directory is resolved at runtime through
|
|
1559
|
+
* {@link resolveCleoOsHubTemplateRoot} so the same code path works in the
|
|
1560
|
+
* monorepo workspace and in an installed npm package layout.
|
|
1561
|
+
*
|
|
1562
|
+
* @example
|
|
1563
|
+
* ```typescript
|
|
1564
|
+
* const result = await ensureCleoOsHub();
|
|
1565
|
+
* console.log(result.action); // "created" or "skipped"
|
|
1566
|
+
* ```
|
|
1567
|
+
*/
|
|
1568
|
+
export async function ensureCleoOsHub() {
|
|
1569
|
+
const recipesDir = getCleoGlobalRecipesDir();
|
|
1570
|
+
const piExtDir = getCleoPiExtensionsDir();
|
|
1571
|
+
const cantWorkflowsDir = getCleoCantWorkflowsDir();
|
|
1572
|
+
const agentsDir = getCleoGlobalAgentsDir();
|
|
1573
|
+
// Always make sure the hub directory skeleton exists, even if templates
|
|
1574
|
+
// can't be located (so downstream tools writing into them don't ENOENT).
|
|
1575
|
+
try {
|
|
1576
|
+
await mkdir(recipesDir, { recursive: true });
|
|
1577
|
+
await mkdir(piExtDir, { recursive: true });
|
|
1578
|
+
await mkdir(cantWorkflowsDir, { recursive: true });
|
|
1579
|
+
await mkdir(agentsDir, { recursive: true });
|
|
1580
|
+
}
|
|
1581
|
+
catch (err) {
|
|
1582
|
+
return {
|
|
1583
|
+
action: 'skipped',
|
|
1584
|
+
path: recipesDir,
|
|
1585
|
+
details: `Failed to create CleoOS hub directories: ${err instanceof Error ? err.message : String(err)}`,
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
const templateRoot = resolveCleoOsHubTemplateRoot();
|
|
1589
|
+
if (!templateRoot) {
|
|
1590
|
+
return {
|
|
1591
|
+
action: 'skipped',
|
|
1592
|
+
path: recipesDir,
|
|
1593
|
+
details: 'CleoOS hub template directory not found in any expected location',
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
let piResult;
|
|
1597
|
+
let recipesResult;
|
|
1598
|
+
try {
|
|
1599
|
+
piResult = await copyTemplateTree(join(templateRoot, 'pi-extensions'), piExtDir);
|
|
1600
|
+
recipesResult = await copyTemplateTree(join(templateRoot, 'global-recipes'), recipesDir);
|
|
1601
|
+
}
|
|
1602
|
+
catch (err) {
|
|
1603
|
+
return {
|
|
1604
|
+
action: 'skipped',
|
|
1605
|
+
path: recipesDir,
|
|
1606
|
+
details: `Failed to seed CleoOS hub templates: ${err instanceof Error ? err.message : String(err)}`,
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
const totalCopied = piResult.copied + recipesResult.copied;
|
|
1610
|
+
return {
|
|
1611
|
+
action: totalCopied > 0 ? 'created' : 'skipped',
|
|
1612
|
+
path: recipesDir,
|
|
1613
|
+
details: `pi-extensions: ${piResult.copied} created/${piResult.kept} kept, global-recipes: ${recipesResult.copied} created/${recipesResult.kept} kept`,
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
// ── Global check* functions (read-only diagnostics) ──────────────────
|
|
1617
|
+
/**
|
|
1618
|
+
* Check that the global ~/.cleo/ home and its required subdirectories exist.
|
|
1619
|
+
* Read-only: no side effects.
|
|
1620
|
+
*
|
|
1621
|
+
* @returns Check result with status and missing subdirectory details
|
|
1622
|
+
*
|
|
1623
|
+
* @remarks
|
|
1624
|
+
* Reports "failed" if the home directory itself is absent, "warning" if
|
|
1625
|
+
* subdirectories are missing, "passed" if all infrastructure is present.
|
|
1626
|
+
*
|
|
1627
|
+
* @example
|
|
1628
|
+
* ```typescript
|
|
1629
|
+
* const check = checkGlobalHome();
|
|
1630
|
+
* if (check.status !== 'passed') console.log(check.fix);
|
|
1631
|
+
* ```
|
|
1632
|
+
*/
|
|
1633
|
+
export function checkGlobalHome() {
|
|
1634
|
+
const cleoHome = getCleoHome();
|
|
1635
|
+
if (!existsSync(cleoHome)) {
|
|
1636
|
+
return {
|
|
1637
|
+
id: 'global_home',
|
|
1638
|
+
category: 'global',
|
|
1639
|
+
status: 'failed',
|
|
1640
|
+
message: 'Global ~/.cleo/ directory not found',
|
|
1641
|
+
details: { path: cleoHome, exists: false },
|
|
1642
|
+
fix: 'cleo init',
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
const missingDirs = REQUIRED_GLOBAL_SUBDIRS.filter((dir) => !existsSync(join(cleoHome, dir)));
|
|
1646
|
+
if (missingDirs.length > 0) {
|
|
1647
|
+
return {
|
|
1648
|
+
id: 'global_home',
|
|
1649
|
+
category: 'global',
|
|
1650
|
+
status: 'warning',
|
|
1651
|
+
message: `Global home exists but missing subdirs: ${missingDirs.join(', ')}`,
|
|
1652
|
+
details: { path: cleoHome, exists: true, missingDirs },
|
|
1653
|
+
fix: 'cleo upgrade --include-global',
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
return {
|
|
1657
|
+
id: 'global_home',
|
|
1658
|
+
category: 'global',
|
|
1659
|
+
status: 'passed',
|
|
1660
|
+
message: 'Global ~/.cleo/ home and subdirectories present',
|
|
1661
|
+
details: { path: cleoHome, exists: true, subdirs: REQUIRED_GLOBAL_SUBDIRS.length },
|
|
1662
|
+
fix: null,
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Check that the global injection template is present and current.
|
|
1667
|
+
* Read-only: no side effects.
|
|
1668
|
+
*
|
|
1669
|
+
* @returns Check result with template version and sync status
|
|
1670
|
+
*
|
|
1671
|
+
* @remarks
|
|
1672
|
+
* Also checks for version drift between the XDG-compliant path and the
|
|
1673
|
+
* legacy ~/.cleo/templates/ path. Reports "warning" if versions differ.
|
|
1674
|
+
*
|
|
1675
|
+
* @example
|
|
1676
|
+
* ```typescript
|
|
1677
|
+
* const check = checkGlobalTemplates();
|
|
1678
|
+
* console.log(check.details.version);
|
|
1679
|
+
* ```
|
|
1680
|
+
*/
|
|
1681
|
+
export function checkGlobalTemplates() {
|
|
1682
|
+
const templatesDir = getCleoTemplatesDir();
|
|
1683
|
+
const injectionPath = join(templatesDir, 'CLEO-INJECTION.md');
|
|
1684
|
+
if (!existsSync(injectionPath)) {
|
|
1685
|
+
return {
|
|
1686
|
+
id: 'global_templates',
|
|
1687
|
+
category: 'global',
|
|
1688
|
+
status: 'failed',
|
|
1689
|
+
message: 'CLEO-INJECTION.md template not found in global templates',
|
|
1690
|
+
details: { path: injectionPath, exists: false },
|
|
1691
|
+
fix: 'cleo init',
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
// Check version sync between XDG and legacy paths
|
|
1695
|
+
const xdgContent = readFileSync(injectionPath, 'utf-8');
|
|
1696
|
+
const xdgVersion = xdgContent.match(/^Version:\s*(.+)$/m)?.[1]?.trim();
|
|
1697
|
+
const home = getHomedir();
|
|
1698
|
+
const legacyPath = join(home, '.cleo', 'templates', 'CLEO-INJECTION.md');
|
|
1699
|
+
if (existsSync(legacyPath)) {
|
|
1700
|
+
const legacyContent = readFileSync(legacyPath, 'utf-8');
|
|
1701
|
+
const legacyVersion = legacyContent.match(/^Version:\s*(.+)$/m)?.[1]?.trim();
|
|
1702
|
+
if (legacyVersion && xdgVersion && legacyVersion !== xdgVersion) {
|
|
1703
|
+
return {
|
|
1704
|
+
id: 'global_templates',
|
|
1705
|
+
category: 'global',
|
|
1706
|
+
status: 'warning',
|
|
1707
|
+
message: `Legacy template version (${legacyVersion}) out of sync with XDG (${xdgVersion})`,
|
|
1708
|
+
details: { path: injectionPath, exists: true, xdgVersion, legacyVersion, legacyPath },
|
|
1709
|
+
fix: 'npm install -g @cleocode/cleo (reinstall syncs both paths)',
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return {
|
|
1714
|
+
id: 'global_templates',
|
|
1715
|
+
category: 'global',
|
|
1716
|
+
status: 'passed',
|
|
1717
|
+
message: `Global injection template present (v${xdgVersion ?? 'unknown'})`,
|
|
1718
|
+
details: { path: injectionPath, exists: true, version: xdgVersion },
|
|
1719
|
+
fix: null,
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Check that the project log directory exists.
|
|
1724
|
+
* Read-only: no side effects.
|
|
1725
|
+
*
|
|
1726
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
1727
|
+
* @returns Check result indicating whether .cleo/logs/ is present
|
|
1728
|
+
*
|
|
1729
|
+
* @remarks
|
|
1730
|
+
* Reports "warning" if the log directory is absent, "passed" otherwise.
|
|
1731
|
+
*
|
|
1732
|
+
* @example
|
|
1733
|
+
* ```typescript
|
|
1734
|
+
* const check = checkLogDir('/my/project');
|
|
1735
|
+
* console.log(check.status);
|
|
1736
|
+
* ```
|
|
1737
|
+
*/
|
|
1738
|
+
export function checkLogDir(projectRoot) {
|
|
1739
|
+
const logDir = join(getCleoDirAbsolute(projectRoot), 'logs');
|
|
1740
|
+
if (!existsSync(logDir)) {
|
|
1741
|
+
return {
|
|
1742
|
+
id: 'log_dir',
|
|
1743
|
+
category: 'scaffold',
|
|
1744
|
+
status: 'warning',
|
|
1745
|
+
message: 'Log directory .cleo/logs/ not found',
|
|
1746
|
+
details: { path: logDir, exists: false },
|
|
1747
|
+
fix: 'cleo upgrade',
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return {
|
|
1751
|
+
id: 'log_dir',
|
|
1752
|
+
category: 'scaffold',
|
|
1753
|
+
status: 'passed',
|
|
1754
|
+
message: 'Log directory .cleo/logs/ present',
|
|
1755
|
+
details: { path: logDir, exists: true },
|
|
1756
|
+
fix: null,
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
//# sourceMappingURL=scaffold.js.map
|