@cleocode/cleo 2026.4.59 → 2026.4.60
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/cli/index.js +497 -13
- package/dist/cli/index.js.map +3 -3
- package/package.json +8 -8
- package/dist/cli/commander-shim.d.ts +0 -112
- package/dist/cli/commander-shim.d.ts.map +0 -1
- package/dist/cli/commander-shim.js +0 -233
- package/dist/cli/commander-shim.js.map +0 -1
- package/dist/cli/commands/adapter.d.ts +0 -21
- package/dist/cli/commands/adapter.d.ts.map +0 -1
- package/dist/cli/commands/adapter.js +0 -73
- package/dist/cli/commands/adapter.js.map +0 -1
- package/dist/cli/commands/add-batch.d.ts +0 -16
- package/dist/cli/commands/add-batch.d.ts.map +0 -1
- package/dist/cli/commands/add-batch.js +0 -133
- package/dist/cli/commands/add-batch.js.map +0 -1
- package/dist/cli/commands/add.d.ts +0 -12
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -238
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/admin.d.ts +0 -15
- package/dist/cli/commands/admin.d.ts.map +0 -1
- package/dist/cli/commands/admin.js +0 -143
- package/dist/cli/commands/admin.js.map +0 -1
- package/dist/cli/commands/adr.d.ts +0 -27
- package/dist/cli/commands/adr.d.ts.map +0 -1
- package/dist/cli/commands/adr.js +0 -79
- package/dist/cli/commands/adr.js.map +0 -1
- package/dist/cli/commands/agent-profile-status.d.ts +0 -98
- package/dist/cli/commands/agent-profile-status.d.ts.map +0 -1
- package/dist/cli/commands/agent-profile-status.js +0 -71
- package/dist/cli/commands/agent-profile-status.js.map +0 -1
- package/dist/cli/commands/agent.d.ts +0 -41
- package/dist/cli/commands/agent.d.ts.map +0 -1
- package/dist/cli/commands/agent.js +0 -2270
- package/dist/cli/commands/agent.js.map +0 -1
- package/dist/cli/commands/agents.d.ts +0 -17
- package/dist/cli/commands/agents.d.ts.map +0 -1
- package/dist/cli/commands/agents.js +0 -20
- package/dist/cli/commands/agents.js.map +0 -1
- package/dist/cli/commands/analyze.d.ts +0 -12
- package/dist/cli/commands/analyze.d.ts.map +0 -1
- package/dist/cli/commands/analyze.js +0 -20
- package/dist/cli/commands/analyze.js.map +0 -1
- package/dist/cli/commands/archive-stats.d.ts +0 -18
- package/dist/cli/commands/archive-stats.d.ts.map +0 -1
- package/dist/cli/commands/archive-stats.js +0 -49
- package/dist/cli/commands/archive-stats.js.map +0 -1
- package/dist/cli/commands/archive.d.ts +0 -12
- package/dist/cli/commands/archive.d.ts.map +0 -1
- package/dist/cli/commands/archive.js +0 -32
- package/dist/cli/commands/archive.js.map +0 -1
- package/dist/cli/commands/backfill.d.ts +0 -38
- package/dist/cli/commands/backfill.d.ts.map +0 -1
- package/dist/cli/commands/backfill.js +0 -144
- package/dist/cli/commands/backfill.js.map +0 -1
- package/dist/cli/commands/backup-inspect.d.ts +0 -24
- package/dist/cli/commands/backup-inspect.d.ts.map +0 -1
- package/dist/cli/commands/backup-inspect.js +0 -417
- package/dist/cli/commands/backup-inspect.js.map +0 -1
- package/dist/cli/commands/backup.d.ts +0 -13
- package/dist/cli/commands/backup.d.ts.map +0 -1
- package/dist/cli/commands/backup.js +0 -479
- package/dist/cli/commands/backup.js.map +0 -1
- package/dist/cli/commands/blockers.d.ts +0 -7
- package/dist/cli/commands/blockers.d.ts.map +0 -1
- package/dist/cli/commands/blockers.js +0 -16
- package/dist/cli/commands/blockers.js.map +0 -1
- package/dist/cli/commands/brain.d.ts +0 -29
- package/dist/cli/commands/brain.d.ts.map +0 -1
- package/dist/cli/commands/brain.js +0 -358
- package/dist/cli/commands/brain.js.map +0 -1
- package/dist/cli/commands/briefing.d.ts +0 -22
- package/dist/cli/commands/briefing.d.ts.map +0 -1
- package/dist/cli/commands/briefing.js +0 -45
- package/dist/cli/commands/briefing.js.map +0 -1
- package/dist/cli/commands/bug.d.ts +0 -12
- package/dist/cli/commands/bug.d.ts.map +0 -1
- package/dist/cli/commands/bug.js +0 -81
- package/dist/cli/commands/bug.js.map +0 -1
- package/dist/cli/commands/cancel.d.ts +0 -17
- package/dist/cli/commands/cancel.d.ts.map +0 -1
- package/dist/cli/commands/cancel.js +0 -28
- package/dist/cli/commands/cancel.js.map +0 -1
- package/dist/cli/commands/cant.d.ts +0 -32
- package/dist/cli/commands/cant.d.ts.map +0 -1
- package/dist/cli/commands/cant.js +0 -264
- package/dist/cli/commands/cant.js.map +0 -1
- package/dist/cli/commands/chain.d.ts +0 -16
- package/dist/cli/commands/chain.d.ts.map +0 -1
- package/dist/cli/commands/chain.js +0 -52
- package/dist/cli/commands/chain.js.map +0 -1
- package/dist/cli/commands/check.d.ts +0 -25
- package/dist/cli/commands/check.d.ts.map +0 -1
- package/dist/cli/commands/check.js +0 -193
- package/dist/cli/commands/check.js.map +0 -1
- package/dist/cli/commands/checkpoint.d.ts +0 -15
- package/dist/cli/commands/checkpoint.d.ts.map +0 -1
- package/dist/cli/commands/checkpoint.js +0 -91
- package/dist/cli/commands/checkpoint.js.map +0 -1
- package/dist/cli/commands/claim.d.ts +0 -27
- package/dist/cli/commands/claim.d.ts.map +0 -1
- package/dist/cli/commands/claim.js +0 -45
- package/dist/cli/commands/claim.js.map +0 -1
- package/dist/cli/commands/code.d.ts +0 -11
- package/dist/cli/commands/code.d.ts.map +0 -1
- package/dist/cli/commands/code.js +0 -114
- package/dist/cli/commands/code.js.map +0 -1
- package/dist/cli/commands/commands.d.ts +0 -13
- package/dist/cli/commands/commands.d.ts.map +0 -1
- package/dist/cli/commands/commands.js +0 -29
- package/dist/cli/commands/commands.js.map +0 -1
- package/dist/cli/commands/complete.d.ts +0 -12
- package/dist/cli/commands/complete.d.ts.map +0 -1
- package/dist/cli/commands/complete.js +0 -92
- package/dist/cli/commands/complete.js.map +0 -1
- package/dist/cli/commands/complexity.d.ts +0 -17
- package/dist/cli/commands/complexity.d.ts.map +0 -1
- package/dist/cli/commands/complexity.js +0 -25
- package/dist/cli/commands/complexity.js.map +0 -1
- package/dist/cli/commands/compliance.d.ts +0 -9
- package/dist/cli/commands/compliance.d.ts.map +0 -1
- package/dist/cli/commands/compliance.js +0 -110
- package/dist/cli/commands/compliance.js.map +0 -1
- package/dist/cli/commands/config.d.ts +0 -10
- package/dist/cli/commands/config.d.ts.map +0 -1
- package/dist/cli/commands/config.js +0 -69
- package/dist/cli/commands/config.js.map +0 -1
- package/dist/cli/commands/consensus.d.ts +0 -13
- package/dist/cli/commands/consensus.d.ts.map +0 -1
- package/dist/cli/commands/consensus.js +0 -45
- package/dist/cli/commands/consensus.js.map +0 -1
- package/dist/cli/commands/context.d.ts +0 -15
- package/dist/cli/commands/context.d.ts.map +0 -1
- package/dist/cli/commands/context.js +0 -76
- package/dist/cli/commands/context.js.map +0 -1
- package/dist/cli/commands/contribution.d.ts +0 -13
- package/dist/cli/commands/contribution.d.ts.map +0 -1
- package/dist/cli/commands/contribution.js +0 -41
- package/dist/cli/commands/contribution.js.map +0 -1
- package/dist/cli/commands/current.d.ts +0 -13
- package/dist/cli/commands/current.d.ts.map +0 -1
- package/dist/cli/commands/current.js +0 -20
- package/dist/cli/commands/current.js.map +0 -1
- package/dist/cli/commands/dash.d.ts +0 -12
- package/dist/cli/commands/dash.d.ts.map +0 -1
- package/dist/cli/commands/dash.js +0 -20
- package/dist/cli/commands/dash.js.map +0 -1
- package/dist/cli/commands/decomposition.d.ts +0 -13
- package/dist/cli/commands/decomposition.d.ts.map +0 -1
- package/dist/cli/commands/decomposition.js +0 -45
- package/dist/cli/commands/decomposition.js.map +0 -1
- package/dist/cli/commands/delete.d.ts +0 -12
- package/dist/cli/commands/delete.d.ts.map +0 -1
- package/dist/cli/commands/delete.js +0 -37
- package/dist/cli/commands/delete.js.map +0 -1
- package/dist/cli/commands/deps.d.ts +0 -24
- package/dist/cli/commands/deps.d.ts.map +0 -1
- package/dist/cli/commands/deps.js +0 -98
- package/dist/cli/commands/deps.js.map +0 -1
- package/dist/cli/commands/detect-drift.d.ts +0 -15
- package/dist/cli/commands/detect-drift.d.ts.map +0 -1
- package/dist/cli/commands/detect-drift.js +0 -428
- package/dist/cli/commands/detect-drift.js.map +0 -1
- package/dist/cli/commands/detect.d.ts +0 -13
- package/dist/cli/commands/detect.d.ts.map +0 -1
- package/dist/cli/commands/detect.js +0 -20
- package/dist/cli/commands/detect.js.map +0 -1
- package/dist/cli/commands/diagnostics.d.ts +0 -16
- package/dist/cli/commands/diagnostics.d.ts.map +0 -1
- package/dist/cli/commands/diagnostics.js +0 -58
- package/dist/cli/commands/diagnostics.js.map +0 -1
- package/dist/cli/commands/docs.d.ts +0 -13
- package/dist/cli/commands/docs.d.ts.map +0 -1
- package/dist/cli/commands/docs.js +0 -169
- package/dist/cli/commands/docs.js.map +0 -1
- package/dist/cli/commands/doctor.d.ts +0 -15
- package/dist/cli/commands/doctor.d.ts.map +0 -1
- package/dist/cli/commands/doctor.js +0 -133
- package/dist/cli/commands/doctor.js.map +0 -1
- package/dist/cli/commands/dynamic.d.ts +0 -24
- package/dist/cli/commands/dynamic.d.ts.map +0 -1
- package/dist/cli/commands/dynamic.js +0 -27
- package/dist/cli/commands/dynamic.js.map +0 -1
- package/dist/cli/commands/env.d.ts +0 -12
- package/dist/cli/commands/env.d.ts.map +0 -1
- package/dist/cli/commands/env.js +0 -44
- package/dist/cli/commands/env.js.map +0 -1
- package/dist/cli/commands/exists.d.ts +0 -24
- package/dist/cli/commands/exists.d.ts.map +0 -1
- package/dist/cli/commands/exists.js +0 -53
- package/dist/cli/commands/exists.js.map +0 -1
- package/dist/cli/commands/export-tasks.d.ts +0 -10
- package/dist/cli/commands/export-tasks.d.ts.map +0 -1
- package/dist/cli/commands/export-tasks.js +0 -47
- package/dist/cli/commands/export-tasks.js.map +0 -1
- package/dist/cli/commands/export.d.ts +0 -9
- package/dist/cli/commands/export.d.ts.map +0 -1
- package/dist/cli/commands/export.js +0 -46
- package/dist/cli/commands/export.js.map +0 -1
- package/dist/cli/commands/find.d.ts +0 -14
- package/dist/cli/commands/find.d.ts.map +0 -1
- package/dist/cli/commands/find.js +0 -152
- package/dist/cli/commands/find.js.map +0 -1
- package/dist/cli/commands/generate-changelog.d.ts +0 -14
- package/dist/cli/commands/generate-changelog.d.ts.map +0 -1
- package/dist/cli/commands/generate-changelog.js +0 -252
- package/dist/cli/commands/generate-changelog.js.map +0 -1
- package/dist/cli/commands/grade.d.ts +0 -13
- package/dist/cli/commands/grade.d.ts.map +0 -1
- package/dist/cli/commands/grade.js +0 -26
- package/dist/cli/commands/grade.js.map +0 -1
- package/dist/cli/commands/history.d.ts +0 -9
- package/dist/cli/commands/history.d.ts.map +0 -1
- package/dist/cli/commands/history.js +0 -33
- package/dist/cli/commands/history.js.map +0 -1
- package/dist/cli/commands/implementation.d.ts +0 -13
- package/dist/cli/commands/implementation.d.ts.map +0 -1
- package/dist/cli/commands/implementation.js +0 -41
- package/dist/cli/commands/implementation.js.map +0 -1
- package/dist/cli/commands/import-tasks.d.ts +0 -10
- package/dist/cli/commands/import-tasks.d.ts.map +0 -1
- package/dist/cli/commands/import-tasks.js +0 -38
- package/dist/cli/commands/import-tasks.js.map +0 -1
- package/dist/cli/commands/import.d.ts +0 -9
- package/dist/cli/commands/import.d.ts.map +0 -1
- package/dist/cli/commands/import.js +0 -28
- package/dist/cli/commands/import.js.map +0 -1
- package/dist/cli/commands/init.d.ts +0 -34
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/init.js +0 -96
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/cli/commands/inject.d.ts +0 -8
- package/dist/cli/commands/inject.d.ts.map +0 -1
- package/dist/cli/commands/inject.js +0 -28
- package/dist/cli/commands/inject.js.map +0 -1
- package/dist/cli/commands/intelligence.d.ts +0 -22
- package/dist/cli/commands/intelligence.d.ts.map +0 -1
- package/dist/cli/commands/intelligence.js +0 -72
- package/dist/cli/commands/intelligence.js.map +0 -1
- package/dist/cli/commands/issue.d.ts +0 -17
- package/dist/cli/commands/issue.d.ts.map +0 -1
- package/dist/cli/commands/issue.js +0 -107
- package/dist/cli/commands/issue.js.map +0 -1
- package/dist/cli/commands/labels.d.ts +0 -17
- package/dist/cli/commands/labels.d.ts.map +0 -1
- package/dist/cli/commands/labels.js +0 -48
- package/dist/cli/commands/labels.js.map +0 -1
- package/dist/cli/commands/lifecycle.d.ts +0 -8
- package/dist/cli/commands/lifecycle.d.ts.map +0 -1
- package/dist/cli/commands/lifecycle.js +0 -128
- package/dist/cli/commands/lifecycle.js.map +0 -1
- package/dist/cli/commands/list.d.ts +0 -14
- package/dist/cli/commands/list.d.ts.map +0 -1
- package/dist/cli/commands/list.js +0 -143
- package/dist/cli/commands/list.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -12
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -30
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/map.d.ts +0 -10
- package/dist/cli/commands/map.d.ts.map +0 -1
- package/dist/cli/commands/map.js +0 -23
- package/dist/cli/commands/map.js.map +0 -1
- package/dist/cli/commands/memory-brain.d.ts +0 -15
- package/dist/cli/commands/memory-brain.d.ts.map +0 -1
- package/dist/cli/commands/memory-brain.js +0 -436
- package/dist/cli/commands/memory-brain.js.map +0 -1
- package/dist/cli/commands/migrate-claude-mem.d.ts +0 -23
- package/dist/cli/commands/migrate-claude-mem.d.ts.map +0 -1
- package/dist/cli/commands/migrate-claude-mem.js +0 -79
- package/dist/cli/commands/migrate-claude-mem.js.map +0 -1
- package/dist/cli/commands/next.d.ts +0 -9
- package/dist/cli/commands/next.d.ts.map +0 -1
- package/dist/cli/commands/next.js +0 -20
- package/dist/cli/commands/next.js.map +0 -1
- package/dist/cli/commands/nexus.d.ts +0 -20
- package/dist/cli/commands/nexus.d.ts.map +0 -1
- package/dist/cli/commands/nexus.js +0 -2290
- package/dist/cli/commands/nexus.js.map +0 -1
- package/dist/cli/commands/observe.d.ts +0 -13
- package/dist/cli/commands/observe.d.ts.map +0 -1
- package/dist/cli/commands/observe.js +0 -30
- package/dist/cli/commands/observe.js.map +0 -1
- package/dist/cli/commands/ops.d.ts +0 -10
- package/dist/cli/commands/ops.d.ts.map +0 -1
- package/dist/cli/commands/ops.js +0 -19
- package/dist/cli/commands/ops.js.map +0 -1
- package/dist/cli/commands/orchestrate.d.ts +0 -24
- package/dist/cli/commands/orchestrate.d.ts.map +0 -1
- package/dist/cli/commands/orchestrate.js +0 -254
- package/dist/cli/commands/orchestrate.js.map +0 -1
- package/dist/cli/commands/otel.d.ts +0 -12
- package/dist/cli/commands/otel.d.ts.map +0 -1
- package/dist/cli/commands/otel.js +0 -128
- package/dist/cli/commands/otel.js.map +0 -1
- package/dist/cli/commands/phase.d.ts +0 -12
- package/dist/cli/commands/phase.d.ts.map +0 -1
- package/dist/cli/commands/phase.js +0 -91
- package/dist/cli/commands/phase.js.map +0 -1
- package/dist/cli/commands/phases.d.ts +0 -14
- package/dist/cli/commands/phases.d.ts.map +0 -1
- package/dist/cli/commands/phases.js +0 -42
- package/dist/cli/commands/phases.js.map +0 -1
- package/dist/cli/commands/plan.d.ts +0 -8
- package/dist/cli/commands/plan.d.ts.map +0 -1
- package/dist/cli/commands/plan.js +0 -15
- package/dist/cli/commands/plan.js.map +0 -1
- package/dist/cli/commands/promote.d.ts +0 -11
- package/dist/cli/commands/promote.d.ts.map +0 -1
- package/dist/cli/commands/promote.js +0 -18
- package/dist/cli/commands/promote.js.map +0 -1
- package/dist/cli/commands/provider.d.ts +0 -21
- package/dist/cli/commands/provider.d.ts.map +0 -1
- package/dist/cli/commands/provider.js +0 -88
- package/dist/cli/commands/provider.js.map +0 -1
- package/dist/cli/commands/reason.d.ts +0 -36
- package/dist/cli/commands/reason.d.ts.map +0 -1
- package/dist/cli/commands/reason.js +0 -85
- package/dist/cli/commands/reason.js.map +0 -1
- package/dist/cli/commands/refresh-memory.d.ts +0 -9
- package/dist/cli/commands/refresh-memory.d.ts.map +0 -1
- package/dist/cli/commands/refresh-memory.js +0 -24
- package/dist/cli/commands/refresh-memory.js.map +0 -1
- package/dist/cli/commands/relates.d.ts +0 -12
- package/dist/cli/commands/relates.d.ts.map +0 -1
- package/dist/cli/commands/relates.js +0 -53
- package/dist/cli/commands/relates.js.map +0 -1
- package/dist/cli/commands/release.d.ts +0 -8
- package/dist/cli/commands/release.d.ts.map +0 -1
- package/dist/cli/commands/release.js +0 -82
- package/dist/cli/commands/release.js.map +0 -1
- package/dist/cli/commands/remote.d.ts +0 -12
- package/dist/cli/commands/remote.d.ts.map +0 -1
- package/dist/cli/commands/remote.js +0 -207
- package/dist/cli/commands/remote.js.map +0 -1
- package/dist/cli/commands/reorder.d.ts +0 -17
- package/dist/cli/commands/reorder.d.ts.map +0 -1
- package/dist/cli/commands/reorder.js +0 -41
- package/dist/cli/commands/reorder.js.map +0 -1
- package/dist/cli/commands/reparent.d.ts +0 -10
- package/dist/cli/commands/reparent.d.ts.map +0 -1
- package/dist/cli/commands/reparent.js +0 -19
- package/dist/cli/commands/reparent.js.map +0 -1
- package/dist/cli/commands/research.d.ts +0 -8
- package/dist/cli/commands/research.d.ts.map +0 -1
- package/dist/cli/commands/research.js +0 -164
- package/dist/cli/commands/research.js.map +0 -1
- package/dist/cli/commands/restore.d.ts +0 -54
- package/dist/cli/commands/restore.d.ts.map +0 -1
- package/dist/cli/commands/restore.js +0 -470
- package/dist/cli/commands/restore.js.map +0 -1
- package/dist/cli/commands/roadmap.d.ts +0 -14
- package/dist/cli/commands/roadmap.d.ts.map +0 -1
- package/dist/cli/commands/roadmap.js +0 -26
- package/dist/cli/commands/roadmap.js.map +0 -1
- package/dist/cli/commands/safestop.d.ts +0 -14
- package/dist/cli/commands/safestop.d.ts.map +0 -1
- package/dist/cli/commands/safestop.js +0 -32
- package/dist/cli/commands/safestop.js.map +0 -1
- package/dist/cli/commands/schema.d.ts +0 -27
- package/dist/cli/commands/schema.d.ts.map +0 -1
- package/dist/cli/commands/schema.js +0 -160
- package/dist/cli/commands/schema.js.map +0 -1
- package/dist/cli/commands/self-update.d.ts +0 -15
- package/dist/cli/commands/self-update.d.ts.map +0 -1
- package/dist/cli/commands/self-update.js +0 -363
- package/dist/cli/commands/self-update.js.map +0 -1
- package/dist/cli/commands/sequence.d.ts +0 -11
- package/dist/cli/commands/sequence.d.ts.map +0 -1
- package/dist/cli/commands/sequence.js +0 -40
- package/dist/cli/commands/sequence.js.map +0 -1
- package/dist/cli/commands/session.d.ts +0 -12
- package/dist/cli/commands/session.d.ts.map +0 -1
- package/dist/cli/commands/session.js +0 -219
- package/dist/cli/commands/session.js.map +0 -1
- package/dist/cli/commands/show.d.ts +0 -13
- package/dist/cli/commands/show.d.ts.map +0 -1
- package/dist/cli/commands/show.js +0 -40
- package/dist/cli/commands/show.js.map +0 -1
- package/dist/cli/commands/skills.d.ts +0 -13
- package/dist/cli/commands/skills.d.ts.map +0 -1
- package/dist/cli/commands/skills.js +0 -161
- package/dist/cli/commands/skills.js.map +0 -1
- package/dist/cli/commands/snapshot.d.ts +0 -9
- package/dist/cli/commands/snapshot.d.ts.map +0 -1
- package/dist/cli/commands/snapshot.js +0 -50
- package/dist/cli/commands/snapshot.js.map +0 -1
- package/dist/cli/commands/specification.d.ts +0 -13
- package/dist/cli/commands/specification.d.ts.map +0 -1
- package/dist/cli/commands/specification.js +0 -45
- package/dist/cli/commands/specification.js.map +0 -1
- package/dist/cli/commands/start.d.ts +0 -13
- package/dist/cli/commands/start.d.ts.map +0 -1
- package/dist/cli/commands/start.js +0 -20
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/stats.d.ts +0 -12
- package/dist/cli/commands/stats.d.ts.map +0 -1
- package/dist/cli/commands/stats.js +0 -35
- package/dist/cli/commands/stats.js.map +0 -1
- package/dist/cli/commands/sticky.d.ts +0 -16
- package/dist/cli/commands/sticky.d.ts.map +0 -1
- package/dist/cli/commands/sticky.js +0 -218
- package/dist/cli/commands/sticky.js.map +0 -1
- package/dist/cli/commands/stop.d.ts +0 -13
- package/dist/cli/commands/stop.d.ts.map +0 -1
- package/dist/cli/commands/stop.js +0 -20
- package/dist/cli/commands/stop.js.map +0 -1
- package/dist/cli/commands/sync.d.ts +0 -26
- package/dist/cli/commands/sync.d.ts.map +0 -1
- package/dist/cli/commands/sync.js +0 -82
- package/dist/cli/commands/sync.js.map +0 -1
- package/dist/cli/commands/testing.d.ts +0 -13
- package/dist/cli/commands/testing.d.ts.map +0 -1
- package/dist/cli/commands/testing.js +0 -65
- package/dist/cli/commands/testing.js.map +0 -1
- package/dist/cli/commands/token.d.ts +0 -10
- package/dist/cli/commands/token.d.ts.map +0 -1
- package/dist/cli/commands/token.js +0 -135
- package/dist/cli/commands/token.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -12
- package/dist/cli/commands/update.d.ts.map +0 -1
- package/dist/cli/commands/update.js +0 -83
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/commands/upgrade.d.ts +0 -18
- package/dist/cli/commands/upgrade.d.ts.map +0 -1
- package/dist/cli/commands/upgrade.js +0 -103
- package/dist/cli/commands/upgrade.js.map +0 -1
- package/dist/cli/commands/validate.d.ts +0 -12
- package/dist/cli/commands/validate.d.ts.map +0 -1
- package/dist/cli/commands/validate.js +0 -24
- package/dist/cli/commands/validate.js.map +0 -1
- package/dist/cli/commands/verify.d.ts +0 -8
- package/dist/cli/commands/verify.d.ts.map +0 -1
- package/dist/cli/commands/verify.js +0 -28
- package/dist/cli/commands/verify.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -19
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -371
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/field-context.d.ts +0 -32
- package/dist/cli/field-context.d.ts.map +0 -1
- package/dist/cli/field-context.js +0 -47
- package/dist/cli/field-context.js.map +0 -1
- package/dist/cli/format-context.d.ts +0 -32
- package/dist/cli/format-context.d.ts.map +0 -1
- package/dist/cli/format-context.js +0 -50
- package/dist/cli/format-context.js.map +0 -1
- package/dist/cli/help-generator.d.ts +0 -74
- package/dist/cli/help-generator.d.ts.map +0 -1
- package/dist/cli/help-generator.js +0 -229
- package/dist/cli/help-generator.js.map +0 -1
- package/dist/cli/help-renderer.d.ts +0 -28
- package/dist/cli/help-renderer.d.ts.map +0 -1
- package/dist/cli/help-renderer.js +0 -301
- package/dist/cli/help-renderer.js.map +0 -1
- package/dist/cli/index.d.ts +0 -9
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/logger-bootstrap.d.ts +0 -6
- package/dist/cli/logger-bootstrap.d.ts.map +0 -1
- package/dist/cli/logger-bootstrap.js +0 -10
- package/dist/cli/logger-bootstrap.js.map +0 -1
- package/dist/cli/middleware/output-format.d.ts +0 -30
- package/dist/cli/middleware/output-format.d.ts.map +0 -1
- package/dist/cli/middleware/output-format.js +0 -35
- package/dist/cli/middleware/output-format.js.map +0 -1
- package/dist/cli/progress.d.ts +0 -84
- package/dist/cli/progress.d.ts.map +0 -1
- package/dist/cli/progress.js +0 -169
- package/dist/cli/progress.js.map +0 -1
- package/dist/cli/renderers/colors.d.ts +0 -32
- package/dist/cli/renderers/colors.d.ts.map +0 -1
- package/dist/cli/renderers/colors.js +0 -141
- package/dist/cli/renderers/colors.js.map +0 -1
- package/dist/cli/renderers/error.d.ts +0 -13
- package/dist/cli/renderers/error.d.ts.map +0 -1
- package/dist/cli/renderers/error.js +0 -42
- package/dist/cli/renderers/error.js.map +0 -1
- package/dist/cli/renderers/index.d.ts +0 -87
- package/dist/cli/renderers/index.d.ts.map +0 -1
- package/dist/cli/renderers/index.js +0 -262
- package/dist/cli/renderers/index.js.map +0 -1
- package/dist/cli/renderers/lafs-validator.d.ts +0 -91
- package/dist/cli/renderers/lafs-validator.d.ts.map +0 -1
- package/dist/cli/renderers/lafs-validator.js +0 -176
- package/dist/cli/renderers/lafs-validator.js.map +0 -1
- package/dist/cli/renderers/normalizer.d.ts +0 -21
- package/dist/cli/renderers/normalizer.d.ts.map +0 -1
- package/dist/cli/renderers/normalizer.js +0 -106
- package/dist/cli/renderers/normalizer.js.map +0 -1
- package/dist/cli/renderers/system.d.ts +0 -25
- package/dist/cli/renderers/system.d.ts.map +0 -1
- package/dist/cli/renderers/system.js +0 -416
- package/dist/cli/renderers/system.js.map +0 -1
- package/dist/cli/renderers/tasks.d.ts +0 -28
- package/dist/cli/renderers/tasks.d.ts.map +0 -1
- package/dist/cli/renderers/tasks.js +0 -306
- package/dist/cli/renderers/tasks.js.map +0 -1
- package/dist/dispatch/adapters/cli.d.ts +0 -67
- package/dist/dispatch/adapters/cli.d.ts.map +0 -1
- package/dist/dispatch/adapters/cli.js +0 -331
- package/dist/dispatch/adapters/cli.js.map +0 -1
- package/dist/dispatch/context/session-context.d.ts +0 -54
- package/dist/dispatch/context/session-context.d.ts.map +0 -1
- package/dist/dispatch/context/session-context.js +0 -61
- package/dist/dispatch/context/session-context.js.map +0 -1
- package/dist/dispatch/dispatcher.d.ts +0 -23
- package/dist/dispatch/dispatcher.d.ts.map +0 -1
- package/dist/dispatch/dispatcher.js +0 -84
- package/dist/dispatch/dispatcher.js.map +0 -1
- package/dist/dispatch/domains/_base.d.ts +0 -59
- package/dist/dispatch/domains/_base.d.ts.map +0 -1
- package/dist/dispatch/domains/_base.js +0 -77
- package/dist/dispatch/domains/_base.js.map +0 -1
- package/dist/dispatch/domains/_meta.d.ts +0 -23
- package/dist/dispatch/domains/_meta.d.ts.map +0 -1
- package/dist/dispatch/domains/_meta.js +0 -25
- package/dist/dispatch/domains/_meta.js.map +0 -1
- package/dist/dispatch/domains/_routing.d.ts +0 -8
- package/dist/dispatch/domains/_routing.d.ts.map +0 -1
- package/dist/dispatch/domains/_routing.js +0 -20
- package/dist/dispatch/domains/_routing.js.map +0 -1
- package/dist/dispatch/domains/admin.d.ts +0 -25
- package/dist/dispatch/domains/admin.d.ts.map +0 -1
- package/dist/dispatch/domains/admin.js +0 -791
- package/dist/dispatch/domains/admin.js.map +0 -1
- package/dist/dispatch/domains/check.d.ts +0 -22
- package/dist/dispatch/domains/check.d.ts.map +0 -1
- package/dist/dispatch/domains/check.js +0 -381
- package/dist/dispatch/domains/check.js.map +0 -1
- package/dist/dispatch/domains/conduit.d.ts +0 -38
- package/dist/dispatch/domains/conduit.d.ts.map +0 -1
- package/dist/dispatch/domains/conduit.js +0 -360
- package/dist/dispatch/domains/conduit.js.map +0 -1
- package/dist/dispatch/domains/diagnostics.d.ts +0 -20
- package/dist/dispatch/domains/diagnostics.d.ts.map +0 -1
- package/dist/dispatch/domains/diagnostics.js +0 -77
- package/dist/dispatch/domains/diagnostics.js.map +0 -1
- package/dist/dispatch/domains/index.d.ts +0 -29
- package/dist/dispatch/domains/index.d.ts.map +0 -1
- package/dist/dispatch/domains/index.js +0 -45
- package/dist/dispatch/domains/index.js.map +0 -1
- package/dist/dispatch/domains/intelligence.d.ts +0 -26
- package/dist/dispatch/domains/intelligence.d.ts.map +0 -1
- package/dist/dispatch/domains/intelligence.js +0 -154
- package/dist/dispatch/domains/intelligence.js.map +0 -1
- package/dist/dispatch/domains/memory.d.ts +0 -22
- package/dist/dispatch/domains/memory.d.ts.map +0 -1
- package/dist/dispatch/domains/memory.js +0 -387
- package/dist/dispatch/domains/memory.js.map +0 -1
- package/dist/dispatch/domains/nexus.d.ts +0 -22
- package/dist/dispatch/domains/nexus.d.ts.map +0 -1
- package/dist/dispatch/domains/nexus.js +0 -286
- package/dist/dispatch/domains/nexus.js.map +0 -1
- package/dist/dispatch/domains/orchestrate.d.ts +0 -26
- package/dist/dispatch/domains/orchestrate.d.ts.map +0 -1
- package/dist/dispatch/domains/orchestrate.js +0 -691
- package/dist/dispatch/domains/orchestrate.js.map +0 -1
- package/dist/dispatch/domains/pipeline.d.ts +0 -35
- package/dist/dispatch/domains/pipeline.d.ts.map +0 -1
- package/dist/dispatch/domains/pipeline.js +0 -593
- package/dist/dispatch/domains/pipeline.js.map +0 -1
- package/dist/dispatch/domains/session.d.ts +0 -22
- package/dist/dispatch/domains/session.d.ts.map +0 -1
- package/dist/dispatch/domains/session.js +0 -267
- package/dist/dispatch/domains/session.js.map +0 -1
- package/dist/dispatch/domains/sticky.d.ts +0 -20
- package/dist/dispatch/domains/sticky.d.ts.map +0 -1
- package/dist/dispatch/domains/sticky.js +0 -167
- package/dist/dispatch/domains/sticky.js.map +0 -1
- package/dist/dispatch/domains/tasks.d.ts +0 -25
- package/dist/dispatch/domains/tasks.d.ts.map +0 -1
- package/dist/dispatch/domains/tasks.js +0 -368
- package/dist/dispatch/domains/tasks.js.map +0 -1
- package/dist/dispatch/domains/tools.d.ts +0 -37
- package/dist/dispatch/domains/tools.d.ts.map +0 -1
- package/dist/dispatch/domains/tools.js +0 -481
- package/dist/dispatch/domains/tools.js.map +0 -1
- package/dist/dispatch/engines/_error.d.ts +0 -119
- package/dist/dispatch/engines/_error.d.ts.map +0 -1
- package/dist/dispatch/engines/_error.js +0 -298
- package/dist/dispatch/engines/_error.js.map +0 -1
- package/dist/dispatch/engines/code-engine.d.ts +0 -18
- package/dist/dispatch/engines/code-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/code-engine.js +0 -71
- package/dist/dispatch/engines/code-engine.js.map +0 -1
- package/dist/dispatch/engines/codebase-map-engine.d.ts +0 -31
- package/dist/dispatch/engines/codebase-map-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/codebase-map-engine.js +0 -43
- package/dist/dispatch/engines/codebase-map-engine.js.map +0 -1
- package/dist/dispatch/engines/config-engine.d.ts +0 -32
- package/dist/dispatch/engines/config-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/config-engine.js +0 -70
- package/dist/dispatch/engines/config-engine.js.map +0 -1
- package/dist/dispatch/engines/diagnostics-engine.d.ts +0 -57
- package/dist/dispatch/engines/diagnostics-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/diagnostics-engine.js +0 -163
- package/dist/dispatch/engines/diagnostics-engine.js.map +0 -1
- package/dist/dispatch/engines/hooks-engine.d.ts +0 -96
- package/dist/dispatch/engines/hooks-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/hooks-engine.js +0 -144
- package/dist/dispatch/engines/hooks-engine.js.map +0 -1
- package/dist/dispatch/engines/init-engine.d.ts +0 -56
- package/dist/dispatch/engines/init-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/init-engine.js +0 -78
- package/dist/dispatch/engines/init-engine.js.map +0 -1
- package/dist/dispatch/engines/lifecycle-engine.d.ts +0 -66
- package/dist/dispatch/engines/lifecycle-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/lifecycle-engine.js +0 -224
- package/dist/dispatch/engines/lifecycle-engine.js.map +0 -1
- package/dist/dispatch/engines/memory-engine.d.ts +0 -10
- package/dist/dispatch/engines/memory-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/memory-engine.js +0 -10
- package/dist/dispatch/engines/memory-engine.js.map +0 -1
- package/dist/dispatch/engines/nexus-engine.d.ts +0 -167
- package/dist/dispatch/engines/nexus-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/nexus-engine.js +0 -356
- package/dist/dispatch/engines/nexus-engine.js.map +0 -1
- package/dist/dispatch/engines/orchestrate-engine.d.ts +0 -141
- package/dist/dispatch/engines/orchestrate-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/orchestrate-engine.js +0 -892
- package/dist/dispatch/engines/orchestrate-engine.js.map +0 -1
- package/dist/dispatch/engines/pipeline-engine.d.ts +0 -51
- package/dist/dispatch/engines/pipeline-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/pipeline-engine.js +0 -191
- package/dist/dispatch/engines/pipeline-engine.js.map +0 -1
- package/dist/dispatch/engines/release-engine.d.ts +0 -94
- package/dist/dispatch/engines/release-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/release-engine.js +0 -763
- package/dist/dispatch/engines/release-engine.js.map +0 -1
- package/dist/dispatch/engines/session-engine.d.ts +0 -387
- package/dist/dispatch/engines/session-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/session-engine.js +0 -924
- package/dist/dispatch/engines/session-engine.js.map +0 -1
- package/dist/dispatch/engines/sticky-engine.d.ts +0 -100
- package/dist/dispatch/engines/sticky-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/sticky-engine.js +0 -181
- package/dist/dispatch/engines/sticky-engine.js.map +0 -1
- package/dist/dispatch/engines/system-engine.d.ts +0 -543
- package/dist/dispatch/engines/system-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/system-engine.js +0 -1273
- package/dist/dispatch/engines/system-engine.js.map +0 -1
- package/dist/dispatch/engines/task-engine.d.ts +0 -971
- package/dist/dispatch/engines/task-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/task-engine.js +0 -1255
- package/dist/dispatch/engines/task-engine.js.map +0 -1
- package/dist/dispatch/engines/template-parser.d.ts +0 -85
- package/dist/dispatch/engines/template-parser.d.ts.map +0 -1
- package/dist/dispatch/engines/template-parser.js +0 -108
- package/dist/dispatch/engines/template-parser.js.map +0 -1
- package/dist/dispatch/engines/tools-engine.d.ts +0 -270
- package/dist/dispatch/engines/tools-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/tools-engine.js +0 -663
- package/dist/dispatch/engines/tools-engine.js.map +0 -1
- package/dist/dispatch/engines/validate-engine.d.ts +0 -218
- package/dist/dispatch/engines/validate-engine.d.ts.map +0 -1
- package/dist/dispatch/engines/validate-engine.js +0 -737
- package/dist/dispatch/engines/validate-engine.js.map +0 -1
- package/dist/dispatch/index.d.ts +0 -20
- package/dist/dispatch/index.d.ts.map +0 -1
- package/dist/dispatch/index.js +0 -19
- package/dist/dispatch/index.js.map +0 -1
- package/dist/dispatch/lib/background-jobs.d.ts +0 -86
- package/dist/dispatch/lib/background-jobs.d.ts.map +0 -1
- package/dist/dispatch/lib/background-jobs.js +0 -183
- package/dist/dispatch/lib/background-jobs.js.map +0 -1
- package/dist/dispatch/lib/budget.d.ts +0 -36
- package/dist/dispatch/lib/budget.d.ts.map +0 -1
- package/dist/dispatch/lib/budget.js +0 -109
- package/dist/dispatch/lib/budget.js.map +0 -1
- package/dist/dispatch/lib/capability-matrix.d.ts +0 -11
- package/dist/dispatch/lib/capability-matrix.d.ts.map +0 -1
- package/dist/dispatch/lib/capability-matrix.js +0 -10
- package/dist/dispatch/lib/capability-matrix.js.map +0 -1
- package/dist/dispatch/lib/config-loader.d.ts +0 -42
- package/dist/dispatch/lib/config-loader.d.ts.map +0 -1
- package/dist/dispatch/lib/config-loader.js +0 -217
- package/dist/dispatch/lib/config-loader.js.map +0 -1
- package/dist/dispatch/lib/config.d.ts +0 -11
- package/dist/dispatch/lib/config.d.ts.map +0 -1
- package/dist/dispatch/lib/config.js +0 -10
- package/dist/dispatch/lib/config.js.map +0 -1
- package/dist/dispatch/lib/defaults.d.ts +0 -115
- package/dist/dispatch/lib/defaults.d.ts.map +0 -1
- package/dist/dispatch/lib/defaults.js +0 -61
- package/dist/dispatch/lib/defaults.js.map +0 -1
- package/dist/dispatch/lib/engine.d.ts +0 -26
- package/dist/dispatch/lib/engine.d.ts.map +0 -1
- package/dist/dispatch/lib/engine.js +0 -46
- package/dist/dispatch/lib/engine.js.map +0 -1
- package/dist/dispatch/lib/exit-codes.d.ts +0 -35
- package/dist/dispatch/lib/exit-codes.d.ts.map +0 -1
- package/dist/dispatch/lib/exit-codes.js +0 -60
- package/dist/dispatch/lib/exit-codes.js.map +0 -1
- package/dist/dispatch/lib/gateway-meta.d.ts +0 -37
- package/dist/dispatch/lib/gateway-meta.d.ts.map +0 -1
- package/dist/dispatch/lib/gateway-meta.js +0 -50
- package/dist/dispatch/lib/gateway-meta.js.map +0 -1
- package/dist/dispatch/lib/job-manager-accessor.d.ts +0 -9
- package/dist/dispatch/lib/job-manager-accessor.d.ts.map +0 -1
- package/dist/dispatch/lib/job-manager-accessor.js +0 -13
- package/dist/dispatch/lib/job-manager-accessor.js.map +0 -1
- package/dist/dispatch/lib/meta.d.ts +0 -26
- package/dist/dispatch/lib/meta.d.ts.map +0 -1
- package/dist/dispatch/lib/meta.js +0 -37
- package/dist/dispatch/lib/meta.js.map +0 -1
- package/dist/dispatch/lib/param-utils.d.ts +0 -11
- package/dist/dispatch/lib/param-utils.d.ts.map +0 -1
- package/dist/dispatch/lib/param-utils.js +0 -10
- package/dist/dispatch/lib/param-utils.js.map +0 -1
- package/dist/dispatch/lib/projections.d.ts +0 -56
- package/dist/dispatch/lib/projections.d.ts.map +0 -1
- package/dist/dispatch/lib/projections.js +0 -65
- package/dist/dispatch/lib/projections.js.map +0 -1
- package/dist/dispatch/lib/proto-envelope.d.ts +0 -56
- package/dist/dispatch/lib/proto-envelope.d.ts.map +0 -1
- package/dist/dispatch/lib/proto-envelope.js +0 -17
- package/dist/dispatch/lib/proto-envelope.js.map +0 -1
- package/dist/dispatch/lib/schema-utils.d.ts +0 -39
- package/dist/dispatch/lib/schema-utils.d.ts.map +0 -1
- package/dist/dispatch/lib/schema-utils.js +0 -88
- package/dist/dispatch/lib/schema-utils.js.map +0 -1
- package/dist/dispatch/lib/security.d.ts +0 -11
- package/dist/dispatch/lib/security.d.ts.map +0 -1
- package/dist/dispatch/lib/security.js +0 -10
- package/dist/dispatch/lib/security.js.map +0 -1
- package/dist/dispatch/middleware/audit.d.ts +0 -23
- package/dist/dispatch/middleware/audit.d.ts.map +0 -1
- package/dist/dispatch/middleware/audit.js +0 -169
- package/dist/dispatch/middleware/audit.js.map +0 -1
- package/dist/dispatch/middleware/field-filter.d.ts +0 -25
- package/dist/dispatch/middleware/field-filter.d.ts.map +0 -1
- package/dist/dispatch/middleware/field-filter.js +0 -70
- package/dist/dispatch/middleware/field-filter.js.map +0 -1
- package/dist/dispatch/middleware/pipeline.d.ts +0 -33
- package/dist/dispatch/middleware/pipeline.d.ts.map +0 -1
- package/dist/dispatch/middleware/pipeline.js +0 -60
- package/dist/dispatch/middleware/pipeline.js.map +0 -1
- package/dist/dispatch/middleware/projection.d.ts +0 -35
- package/dist/dispatch/middleware/projection.d.ts.map +0 -1
- package/dist/dispatch/middleware/projection.js +0 -146
- package/dist/dispatch/middleware/projection.js.map +0 -1
- package/dist/dispatch/middleware/protocol-enforcement.d.ts +0 -30
- package/dist/dispatch/middleware/protocol-enforcement.d.ts.map +0 -1
- package/dist/dispatch/middleware/protocol-enforcement.js +0 -56
- package/dist/dispatch/middleware/protocol-enforcement.js.map +0 -1
- package/dist/dispatch/middleware/rate-limiter.d.ts +0 -72
- package/dist/dispatch/middleware/rate-limiter.d.ts.map +0 -1
- package/dist/dispatch/middleware/rate-limiter.js +0 -127
- package/dist/dispatch/middleware/rate-limiter.js.map +0 -1
- package/dist/dispatch/middleware/sanitizer.d.ts +0 -24
- package/dist/dispatch/middleware/sanitizer.d.ts.map +0 -1
- package/dist/dispatch/middleware/sanitizer.js +0 -56
- package/dist/dispatch/middleware/sanitizer.js.map +0 -1
- package/dist/dispatch/middleware/session-resolver.d.ts +0 -26
- package/dist/dispatch/middleware/session-resolver.d.ts.map +0 -1
- package/dist/dispatch/middleware/session-resolver.js +0 -65
- package/dist/dispatch/middleware/session-resolver.js.map +0 -1
- package/dist/dispatch/middleware/telemetry.d.ts +0 -21
- package/dist/dispatch/middleware/telemetry.d.ts.map +0 -1
- package/dist/dispatch/middleware/telemetry.js +0 -50
- package/dist/dispatch/middleware/telemetry.js.map +0 -1
- package/dist/dispatch/middleware/verification-gates.d.ts +0 -22
- package/dist/dispatch/middleware/verification-gates.d.ts.map +0 -1
- package/dist/dispatch/middleware/verification-gates.js +0 -59
- package/dist/dispatch/middleware/verification-gates.js.map +0 -1
- package/dist/dispatch/registry.d.ts +0 -91
- package/dist/dispatch/registry.d.ts.map +0 -1
- package/dist/dispatch/registry.js +0 -3535
- package/dist/dispatch/registry.js.map +0 -1
- package/dist/dispatch/types.d.ts +0 -206
- package/dist/dispatch/types.d.ts.map +0 -1
- package/dist/dispatch/types.js +0 -27
- package/dist/dispatch/types.js.map +0 -1
|
@@ -1,2290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI nexus command group - Cross-project NEXUS operations.
|
|
3
|
-
*
|
|
4
|
-
* Thin CLI wrappers routing through the dispatch layer.
|
|
5
|
-
* All business logic lives in src/dispatch/domains/nexus.ts.
|
|
6
|
-
*
|
|
7
|
-
* `nexus analyze` is implemented directly here because it requires
|
|
8
|
-
* `@cleocode/nexus` pipeline access and `@cleocode/core` DB access together,
|
|
9
|
-
* and routing through the dispatch layer would create awkward coupling.
|
|
10
|
-
*
|
|
11
|
-
* @task T4554, T5323, T5330, T481, T534
|
|
12
|
-
* @epic T4545
|
|
13
|
-
*/
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { dispatchFromCli } from '../../dispatch/adapters/cli.js';
|
|
16
|
-
/**
|
|
17
|
-
* Priority score for nexus node kinds when ranking symbol search results.
|
|
18
|
-
*
|
|
19
|
-
* Callable symbols (function, method) rank highest so that `nexus context`
|
|
20
|
-
* and `nexus impact` return meaningful callers/callees instead of
|
|
21
|
-
* file/folder structural nodes which have zero `calls` relations.
|
|
22
|
-
*
|
|
23
|
-
* Lower score = higher priority (sort ascending).
|
|
24
|
-
*/
|
|
25
|
-
const NODE_KIND_PRIORITY = {
|
|
26
|
-
function: 0,
|
|
27
|
-
method: 1,
|
|
28
|
-
constructor: 2,
|
|
29
|
-
class: 3,
|
|
30
|
-
interface: 4,
|
|
31
|
-
type_alias: 5,
|
|
32
|
-
enum: 6,
|
|
33
|
-
constant: 7,
|
|
34
|
-
property: 8,
|
|
35
|
-
variable: 9,
|
|
36
|
-
static: 10,
|
|
37
|
-
struct: 11,
|
|
38
|
-
trait: 12,
|
|
39
|
-
impl: 13,
|
|
40
|
-
macro: 14,
|
|
41
|
-
// Structural/module nodes come last — they have no `calls` relations
|
|
42
|
-
module: 20,
|
|
43
|
-
namespace: 21,
|
|
44
|
-
record: 22,
|
|
45
|
-
delegate: 23,
|
|
46
|
-
union: 24,
|
|
47
|
-
typedef: 25,
|
|
48
|
-
annotation: 26,
|
|
49
|
-
template: 27,
|
|
50
|
-
route: 28,
|
|
51
|
-
tool: 29,
|
|
52
|
-
section: 30,
|
|
53
|
-
import: 31,
|
|
54
|
-
export: 32,
|
|
55
|
-
type: 33,
|
|
56
|
-
file: 40,
|
|
57
|
-
folder: 41,
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Generate GEXF (Gephi Graph Exchange XML Format) from nodes and relations.
|
|
61
|
-
*
|
|
62
|
-
* GEXF is a standard format for graph visualization. Supports node attributes,
|
|
63
|
-
* edge weights (confidence), and color coding by node kind.
|
|
64
|
-
*
|
|
65
|
-
* @param nodes - Array of nexus nodes
|
|
66
|
-
* @param relations - Array of nexus relations
|
|
67
|
-
* @returns GEXF XML string
|
|
68
|
-
*/
|
|
69
|
-
function generateGexf(nodes, relations) {
|
|
70
|
-
// Build node by ID map for edge resolution
|
|
71
|
-
const nodeById = new Map();
|
|
72
|
-
for (const n of nodes) {
|
|
73
|
-
nodeById.set(String(n['id']), n);
|
|
74
|
-
}
|
|
75
|
-
// Color map for node kinds (hex colors for visualization)
|
|
76
|
-
const kindColors = {
|
|
77
|
-
function: '#3498db', // blue
|
|
78
|
-
method: '#2980b9', // darker blue
|
|
79
|
-
class: '#e74c3c', // red
|
|
80
|
-
interface: '#e67e22', // orange
|
|
81
|
-
file: '#95a5a6', // gray
|
|
82
|
-
folder: '#34495e', // dark gray
|
|
83
|
-
community: '#9b59b6', // purple
|
|
84
|
-
process: '#1abc9c', // teal
|
|
85
|
-
import: '#f39c12', // amber
|
|
86
|
-
default: '#7f8c8d', // medium gray
|
|
87
|
-
};
|
|
88
|
-
const getNodeColor = (kind) => {
|
|
89
|
-
return kindColors[kind] ?? kindColors['default'];
|
|
90
|
-
};
|
|
91
|
-
// GEXF XML header
|
|
92
|
-
let gexf = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
93
|
-
gexf +=
|
|
94
|
-
'<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:viz="http://www.gexf.net/1.2draft/viz" version="1.2">\n';
|
|
95
|
-
gexf += ' <meta lastmodifieddate="' + new Date().toISOString().split('T')[0] + '">\n';
|
|
96
|
-
gexf += ' <creator>CLEO nexus export</creator>\n';
|
|
97
|
-
gexf += ' <description>Code intelligence graph from CLEO nexus</description>\n';
|
|
98
|
-
gexf += ' </meta>\n';
|
|
99
|
-
gexf += ' <graph mode="static" defaultedgetype="directed">\n';
|
|
100
|
-
// Attributes
|
|
101
|
-
gexf += ' <attributes class="node">\n';
|
|
102
|
-
gexf += ' <attribute id="kind" title="Node Kind" type="string" />\n';
|
|
103
|
-
gexf += ' <attribute id="filePath" title="File Path" type="string" />\n';
|
|
104
|
-
gexf += ' <attribute id="language" title="Language" type="string" />\n';
|
|
105
|
-
gexf += ' <attribute id="startLine" title="Start Line" type="integer" />\n';
|
|
106
|
-
gexf += ' <attribute id="endLine" title="End Line" type="integer" />\n';
|
|
107
|
-
gexf += ' <attribute id="isExported" title="Is Exported" type="boolean" />\n';
|
|
108
|
-
gexf += ' <attribute id="projectId" title="Project ID" type="string" />\n';
|
|
109
|
-
gexf += ' </attributes>\n';
|
|
110
|
-
gexf += ' <attributes class="edge">\n';
|
|
111
|
-
gexf += ' <attribute id="relationType" title="Relation Type" type="string" />\n';
|
|
112
|
-
gexf += ' <attribute id="confidence" title="Confidence" type="double" />\n';
|
|
113
|
-
gexf += ' <attribute id="reason" title="Reason" type="string" />\n';
|
|
114
|
-
gexf += ' </attributes>\n';
|
|
115
|
-
// Nodes
|
|
116
|
-
gexf += ' <nodes>\n';
|
|
117
|
-
for (const node of nodes) {
|
|
118
|
-
const nodeId = String(node['id']).replace(/[<>"'&]/g, (c) => {
|
|
119
|
-
const map = {
|
|
120
|
-
'<': '<',
|
|
121
|
-
'>': '>',
|
|
122
|
-
'"': '"',
|
|
123
|
-
"'": ''',
|
|
124
|
-
'&': '&',
|
|
125
|
-
};
|
|
126
|
-
return map[c];
|
|
127
|
-
});
|
|
128
|
-
const label = String(node['label'] ?? node['id']);
|
|
129
|
-
const kind = String(node['kind'] ?? 'unknown');
|
|
130
|
-
const color = getNodeColor(kind);
|
|
131
|
-
gexf += ` <node id="${nodeId}" label="${escapeXml(label)}">\n`;
|
|
132
|
-
gexf += ` <viz:color r="${hexToRgb(color).r}" g="${hexToRgb(color).g}" b="${hexToRgb(color).b}" />\n`;
|
|
133
|
-
gexf += ' <attvalues>\n';
|
|
134
|
-
gexf += ` <attvalue id="kind" value="${escapeXml(kind)}" />\n`;
|
|
135
|
-
if (node['filePath']) {
|
|
136
|
-
gexf += ` <attvalue id="filePath" value="${escapeXml(String(node['filePath']))}" />\n`;
|
|
137
|
-
}
|
|
138
|
-
if (node['language']) {
|
|
139
|
-
gexf += ` <attvalue id="language" value="${escapeXml(String(node['language']))}" />\n`;
|
|
140
|
-
}
|
|
141
|
-
if (node['startLine'] != null) {
|
|
142
|
-
gexf += ` <attvalue id="startLine" value="${node['startLine']}" />\n`;
|
|
143
|
-
}
|
|
144
|
-
if (node['endLine'] != null) {
|
|
145
|
-
gexf += ` <attvalue id="endLine" value="${node['endLine']}" />\n`;
|
|
146
|
-
}
|
|
147
|
-
if (node['isExported'] != null) {
|
|
148
|
-
gexf += ` <attvalue id="isExported" value="${node['isExported'] ? 'true' : 'false'}" />\n`;
|
|
149
|
-
}
|
|
150
|
-
if (node['projectId']) {
|
|
151
|
-
gexf += ` <attvalue id="projectId" value="${escapeXml(String(node['projectId']))}" />\n`;
|
|
152
|
-
}
|
|
153
|
-
gexf += ' </attvalues>\n';
|
|
154
|
-
gexf += ' </node>\n';
|
|
155
|
-
}
|
|
156
|
-
gexf += ' </nodes>\n';
|
|
157
|
-
// Edges
|
|
158
|
-
gexf += ' <edges>\n';
|
|
159
|
-
for (let i = 0; i < relations.length; i++) {
|
|
160
|
-
const rel = relations[i];
|
|
161
|
-
const sourceId = String(rel['sourceId']).replace(/[<>"'&]/g, (c) => {
|
|
162
|
-
const map = {
|
|
163
|
-
'<': '<',
|
|
164
|
-
'>': '>',
|
|
165
|
-
'"': '"',
|
|
166
|
-
"'": ''',
|
|
167
|
-
'&': '&',
|
|
168
|
-
};
|
|
169
|
-
return map[c];
|
|
170
|
-
});
|
|
171
|
-
const targetId = String(rel['targetId']).replace(/[<>"'&]/g, (c) => {
|
|
172
|
-
const map = {
|
|
173
|
-
'<': '<',
|
|
174
|
-
'>': '>',
|
|
175
|
-
'"': '"',
|
|
176
|
-
"'": ''',
|
|
177
|
-
'&': '&',
|
|
178
|
-
};
|
|
179
|
-
return map[c];
|
|
180
|
-
});
|
|
181
|
-
// Skip edges where source or target don't exist in our node set
|
|
182
|
-
// (external references or unresolved imports)
|
|
183
|
-
if (!nodeById.has(String(rel['sourceId'])) || !nodeById.has(String(rel['targetId']))) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
const confidence = typeof rel['confidence'] === 'number' ? rel['confidence'] : 1.0;
|
|
187
|
-
const relationType = String(rel['type'] ?? 'unknown');
|
|
188
|
-
const reason = rel['reason'] ? String(rel['reason']) : '';
|
|
189
|
-
gexf += ` <edge id="e${i}" source="${sourceId}" target="${targetId}" weight="${confidence}">\n`;
|
|
190
|
-
gexf += ' <attvalues>\n';
|
|
191
|
-
gexf += ` <attvalue id="relationType" value="${escapeXml(relationType)}" />\n`;
|
|
192
|
-
gexf += ` <attvalue id="confidence" value="${confidence}" />\n`;
|
|
193
|
-
if (reason) {
|
|
194
|
-
gexf += ` <attvalue id="reason" value="${escapeXml(reason)}" />\n`;
|
|
195
|
-
}
|
|
196
|
-
gexf += ' </attvalues>\n';
|
|
197
|
-
gexf += ' </edge>\n';
|
|
198
|
-
}
|
|
199
|
-
gexf += ' </edges>\n';
|
|
200
|
-
gexf += ' </graph>\n';
|
|
201
|
-
gexf += '</gexf>\n';
|
|
202
|
-
return gexf;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Escape XML special characters.
|
|
206
|
-
*/
|
|
207
|
-
function escapeXml(str) {
|
|
208
|
-
return String(str).replace(/[<>"'&]/g, (c) => {
|
|
209
|
-
const map = {
|
|
210
|
-
'<': '<',
|
|
211
|
-
'>': '>',
|
|
212
|
-
'"': '"',
|
|
213
|
-
"'": ''',
|
|
214
|
-
'&': '&',
|
|
215
|
-
};
|
|
216
|
-
return map[c];
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Convert hex color to RGB object.
|
|
221
|
-
*/
|
|
222
|
-
function hexToRgb(hex) {
|
|
223
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
224
|
-
return result
|
|
225
|
-
? {
|
|
226
|
-
r: parseInt(result[1], 16),
|
|
227
|
-
g: parseInt(result[2], 16),
|
|
228
|
-
b: parseInt(result[3], 16),
|
|
229
|
-
}
|
|
230
|
-
: { r: 127, g: 140, b: 141 };
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Sort symbol search results so that callable nodes (function, method, class)
|
|
234
|
-
* appear before structural nodes (file, folder). Within the same kind, prefer
|
|
235
|
-
* exact name matches over partial matches.
|
|
236
|
-
*/
|
|
237
|
-
function sortMatchingNodes(nodes, symbolName) {
|
|
238
|
-
const lowerSymbol = symbolName.toLowerCase();
|
|
239
|
-
return [...nodes].sort((a, b) => {
|
|
240
|
-
const kindA = String(a['kind'] ?? '');
|
|
241
|
-
const kindB = String(b['kind'] ?? '');
|
|
242
|
-
const prioA = NODE_KIND_PRIORITY[kindA] ?? 15;
|
|
243
|
-
const prioB = NODE_KIND_PRIORITY[kindB] ?? 15;
|
|
244
|
-
if (prioA !== prioB)
|
|
245
|
-
return prioA - prioB;
|
|
246
|
-
// Within same kind: exact name matches before partial matches
|
|
247
|
-
const nameA = String(a['name'] ?? '').toLowerCase();
|
|
248
|
-
const nameB = String(b['name'] ?? '').toLowerCase();
|
|
249
|
-
const exactA = nameA === lowerSymbol ? 0 : 1;
|
|
250
|
-
const exactB = nameB === lowerSymbol ? 0 : 1;
|
|
251
|
-
return exactA - exactB;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Register the nexus command group.
|
|
256
|
-
* @task T4554
|
|
257
|
-
*/
|
|
258
|
-
export function registerNexusCommand(program) {
|
|
259
|
-
const nexus = program.command('nexus').description('Cross-project NEXUS operations');
|
|
260
|
-
// ── nexus init ──────────────────────────────────────────────────────
|
|
261
|
-
nexus
|
|
262
|
-
.command('init')
|
|
263
|
-
.description('Initialize NEXUS directory structure and registry')
|
|
264
|
-
.action(async () => {
|
|
265
|
-
await dispatchFromCli('mutate', 'nexus', 'init', {}, { command: 'nexus' });
|
|
266
|
-
});
|
|
267
|
-
// ── nexus register ──────────────────────────────────────────────────
|
|
268
|
-
nexus
|
|
269
|
-
.command('register <path>')
|
|
270
|
-
.description('Register a project in the global registry')
|
|
271
|
-
.option('--name <name>', 'Custom project name (default: directory name)')
|
|
272
|
-
.option('--permissions <perms>', 'Permissions: read|write|execute', 'read')
|
|
273
|
-
.action(async (projectPath, opts) => {
|
|
274
|
-
await dispatchFromCli('mutate', 'nexus', 'register', {
|
|
275
|
-
path: projectPath,
|
|
276
|
-
name: opts['name'],
|
|
277
|
-
permission: opts['permissions'],
|
|
278
|
-
}, { command: 'nexus' });
|
|
279
|
-
});
|
|
280
|
-
// ── nexus unregister ────────────────────────────────────────────────
|
|
281
|
-
nexus
|
|
282
|
-
.command('unregister <nameOrHash>')
|
|
283
|
-
.description('Remove a project from the registry')
|
|
284
|
-
.action(async (nameOrHash) => {
|
|
285
|
-
await dispatchFromCli('mutate', 'nexus', 'unregister', {
|
|
286
|
-
name: nameOrHash,
|
|
287
|
-
}, { command: 'nexus' });
|
|
288
|
-
});
|
|
289
|
-
// ── nexus list ──────────────────────────────────────────────────────
|
|
290
|
-
nexus
|
|
291
|
-
.command('list')
|
|
292
|
-
.description('List all registered projects')
|
|
293
|
-
.action(async () => {
|
|
294
|
-
await dispatchFromCli('query', 'nexus', 'list', {}, { command: 'nexus' });
|
|
295
|
-
});
|
|
296
|
-
// ── nexus status ────────────────────────────────────────────────────
|
|
297
|
-
// Shows both NEXUS registry status AND code intelligence index freshness.
|
|
298
|
-
// When invoked with a path, shows index freshness for that project.
|
|
299
|
-
nexus
|
|
300
|
-
.command('status [path]')
|
|
301
|
-
.description('Show code intelligence index freshness: file count, node/relation counts, last indexed time, stale files. Falls back to NEXUS registry status if code-intelligence index is unavailable.')
|
|
302
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from path)')
|
|
303
|
-
.option('--json', 'Output as JSON (LAFS envelope format)')
|
|
304
|
-
.action(async (targetPath, opts) => {
|
|
305
|
-
const jsonOutput = !!opts['json'];
|
|
306
|
-
const projectIdOverride = opts['projectId'];
|
|
307
|
-
const repoPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
308
|
-
const startTime = Date.now();
|
|
309
|
-
try {
|
|
310
|
-
const [{ getNexusDb, nexusSchema }, { getIndexStats }] = await Promise.all([
|
|
311
|
-
import('@cleocode/core/store/nexus-sqlite'),
|
|
312
|
-
import('@cleocode/nexus/pipeline'),
|
|
313
|
-
]);
|
|
314
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
315
|
-
const db = await getNexusDb();
|
|
316
|
-
const tables = {
|
|
317
|
-
nexusNodes: nexusSchema.nexusNodes,
|
|
318
|
-
nexusRelations: nexusSchema.nexusRelations,
|
|
319
|
-
};
|
|
320
|
-
const stats = await getIndexStats(projectId, repoPath, db, tables);
|
|
321
|
-
const durationMs = Date.now() - startTime;
|
|
322
|
-
if (jsonOutput) {
|
|
323
|
-
const envelope = {
|
|
324
|
-
success: true,
|
|
325
|
-
data: { projectId, repoPath, ...stats },
|
|
326
|
-
meta: {
|
|
327
|
-
operation: 'nexus.status',
|
|
328
|
-
duration_ms: durationMs,
|
|
329
|
-
timestamp: new Date().toISOString(),
|
|
330
|
-
},
|
|
331
|
-
};
|
|
332
|
-
process.stdout.write(JSON.stringify(envelope, null, 2) + '\n');
|
|
333
|
-
}
|
|
334
|
-
else if (!stats.indexed) {
|
|
335
|
-
process.stdout.write(`[nexus] Index status for: ${repoPath}\n` +
|
|
336
|
-
` Status: NOT INDEXED\n` +
|
|
337
|
-
` Run 'cleo nexus analyze' to build the index.\n`);
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
const staleLabel = stats.staleFileCount < 0
|
|
341
|
-
? 'unknown'
|
|
342
|
-
: stats.staleFileCount === 0
|
|
343
|
-
? 'up to date'
|
|
344
|
-
: `${stats.staleFileCount} stale`;
|
|
345
|
-
process.stdout.write(`[nexus] Index status for: ${repoPath}\n` +
|
|
346
|
-
` Project ID: ${projectId}\n` +
|
|
347
|
-
` Nodes: ${stats.nodeCount}\n` +
|
|
348
|
-
` Relations: ${stats.relationCount}\n` +
|
|
349
|
-
` Files: ${stats.fileCount}\n` +
|
|
350
|
-
` Last indexed: ${stats.lastIndexedAt ?? 'never'}\n` +
|
|
351
|
-
` Staleness: ${staleLabel}\n`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
catch (err) {
|
|
355
|
-
// Fall back to NEXUS registry status on error
|
|
356
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
357
|
-
if (jsonOutput) {
|
|
358
|
-
process.stdout.write(JSON.stringify({
|
|
359
|
-
success: false,
|
|
360
|
-
error: { code: 'E_STATUS_FAILED', message: msg },
|
|
361
|
-
meta: {
|
|
362
|
-
operation: 'nexus.status',
|
|
363
|
-
duration_ms: Date.now() - startTime,
|
|
364
|
-
timestamp: new Date().toISOString(),
|
|
365
|
-
},
|
|
366
|
-
}, null, 2) + '\n');
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
370
|
-
await dispatchFromCli('query', 'nexus', 'status', {}, { command: 'nexus' });
|
|
371
|
-
}
|
|
372
|
-
process.exitCode = 1;
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
// ── nexus show ─────────────────────────────────────────────────────
|
|
376
|
-
nexus
|
|
377
|
-
.command('show <name>')
|
|
378
|
-
.description('Show details for a registered project by name')
|
|
379
|
-
.action(async (name) => {
|
|
380
|
-
await dispatchFromCli('query', 'nexus', 'show', {
|
|
381
|
-
name,
|
|
382
|
-
}, { command: 'nexus' });
|
|
383
|
-
});
|
|
384
|
-
// ── nexus resolve ───────────────────────────────────────────────────
|
|
385
|
-
nexus
|
|
386
|
-
.command('resolve <taskRef>')
|
|
387
|
-
.alias('query')
|
|
388
|
-
.description('Resolve a task reference across projects (project:T### or T###)')
|
|
389
|
-
.action(async (taskRef) => {
|
|
390
|
-
await dispatchFromCli('query', 'nexus', 'resolve', {
|
|
391
|
-
query: taskRef,
|
|
392
|
-
}, { command: 'nexus' });
|
|
393
|
-
});
|
|
394
|
-
// ── nexus discover ──────────────────────────────────────────────────
|
|
395
|
-
nexus
|
|
396
|
-
.command('discover <taskQuery>')
|
|
397
|
-
.description('Find related tasks across projects')
|
|
398
|
-
.option('--method <method>', 'Discovery method: labels|description|files|auto', 'auto')
|
|
399
|
-
.option('--limit <n>', 'Max results', parseInt, 10)
|
|
400
|
-
.action(async (taskQuery, opts) => {
|
|
401
|
-
await dispatchFromCli('query', 'nexus', 'discover', {
|
|
402
|
-
query: taskQuery,
|
|
403
|
-
method: opts['method'],
|
|
404
|
-
limit: opts['limit'],
|
|
405
|
-
}, { command: 'nexus' });
|
|
406
|
-
});
|
|
407
|
-
// ── nexus search ────────────────────────────────────────────────────
|
|
408
|
-
nexus
|
|
409
|
-
.command('search <pattern>')
|
|
410
|
-
.description('Search tasks across projects by pattern')
|
|
411
|
-
.option('--project <name>', 'Limit search to specific project')
|
|
412
|
-
.option('--limit <n>', 'Max results', parseInt, 20)
|
|
413
|
-
.action(async (pattern, opts) => {
|
|
414
|
-
await dispatchFromCli('query', 'nexus', 'search', {
|
|
415
|
-
pattern,
|
|
416
|
-
project: opts['project'],
|
|
417
|
-
limit: opts['limit'],
|
|
418
|
-
}, { command: 'nexus' });
|
|
419
|
-
});
|
|
420
|
-
// ── nexus deps ──────────────────────────────────────────────────────
|
|
421
|
-
nexus
|
|
422
|
-
.command('deps <taskQuery>')
|
|
423
|
-
.description('Show cross-project dependencies')
|
|
424
|
-
.option('--reverse', 'Show reverse dependencies (what depends on this)')
|
|
425
|
-
.action(async (taskQuery, opts) => {
|
|
426
|
-
await dispatchFromCli('query', 'nexus', 'deps', {
|
|
427
|
-
query: taskQuery,
|
|
428
|
-
direction: opts['reverse'] ? 'reverse' : 'forward',
|
|
429
|
-
}, { command: 'nexus' });
|
|
430
|
-
});
|
|
431
|
-
// ── nexus critical-path ───────────────────────────────────────────
|
|
432
|
-
nexus
|
|
433
|
-
.command('critical-path')
|
|
434
|
-
.description('Show global critical path across all registered projects')
|
|
435
|
-
.action(async () => {
|
|
436
|
-
await dispatchFromCli('query', 'nexus', 'path.show', {}, { command: 'nexus' });
|
|
437
|
-
});
|
|
438
|
-
// ── nexus blocking ────────────────────────────────────────────────
|
|
439
|
-
nexus
|
|
440
|
-
.command('blocking <taskQuery>')
|
|
441
|
-
.description('Show blocking impact analysis for a task')
|
|
442
|
-
.action(async (taskQuery) => {
|
|
443
|
-
await dispatchFromCli('query', 'nexus', 'blockers.show', {
|
|
444
|
-
query: taskQuery,
|
|
445
|
-
}, { command: 'nexus' });
|
|
446
|
-
});
|
|
447
|
-
// ── nexus orphans ─────────────────────────────────────────────────
|
|
448
|
-
nexus
|
|
449
|
-
.command('orphans')
|
|
450
|
-
.description('Detect broken cross-project dependency references')
|
|
451
|
-
.action(async () => {
|
|
452
|
-
await dispatchFromCli('query', 'nexus', 'orphans.list', {}, { command: 'nexus' });
|
|
453
|
-
});
|
|
454
|
-
// ── nexus sync ──────────────────────────────────────────────────────
|
|
455
|
-
nexus
|
|
456
|
-
.command('sync [project]')
|
|
457
|
-
.description('Sync project metadata (task count, labels)')
|
|
458
|
-
.action(async (project) => {
|
|
459
|
-
if (project) {
|
|
460
|
-
await dispatchFromCli('mutate', 'nexus', 'sync', {
|
|
461
|
-
name: project,
|
|
462
|
-
}, { command: 'nexus' });
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
await dispatchFromCli('mutate', 'nexus', 'sync', {}, { command: 'nexus' });
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
// ── nexus reconcile ──────────────────────────────────────────────────
|
|
469
|
-
nexus
|
|
470
|
-
.command('reconcile')
|
|
471
|
-
.description('Reconcile current project with NEXUS registry (auto-register if new, update path if moved)')
|
|
472
|
-
.option('--path <path>', 'Project path (default: current directory)')
|
|
473
|
-
.action(async (opts) => {
|
|
474
|
-
await dispatchFromCli('mutate', 'nexus', 'reconcile', {
|
|
475
|
-
projectRoot: opts['path'],
|
|
476
|
-
}, { command: 'nexus' });
|
|
477
|
-
});
|
|
478
|
-
// ── nexus graph ───────────────────────────────────────────────────
|
|
479
|
-
nexus
|
|
480
|
-
.command('graph')
|
|
481
|
-
.description('Show full dependency graph across all registered projects')
|
|
482
|
-
.action(async () => {
|
|
483
|
-
await dispatchFromCli('query', 'nexus', 'graph', {}, { command: 'nexus' });
|
|
484
|
-
});
|
|
485
|
-
// ── nexus share-status ────────────────────────────────────────────
|
|
486
|
-
nexus
|
|
487
|
-
.command('share-status')
|
|
488
|
-
.description('Show multi-contributor sharing status for the current project')
|
|
489
|
-
.action(async () => {
|
|
490
|
-
await dispatchFromCli('query', 'nexus', 'share.status', {}, { command: 'nexus' });
|
|
491
|
-
});
|
|
492
|
-
// ── nexus transfer-preview ────────────────────────────────────────
|
|
493
|
-
nexus
|
|
494
|
-
.command('transfer-preview <taskIds...>')
|
|
495
|
-
.description('Preview a task transfer between projects (dry-run, no changes made)')
|
|
496
|
-
.requiredOption('--from <project>', 'Source project name')
|
|
497
|
-
.requiredOption('--to <project>', 'Target project name')
|
|
498
|
-
.option('--mode <mode>', 'Transfer mode: copy|move', 'copy')
|
|
499
|
-
.option('--scope <scope>', 'Transfer scope: single|subtree', 'subtree')
|
|
500
|
-
.action(async (taskIds, opts) => {
|
|
501
|
-
await dispatchFromCli('query', 'nexus', 'transfer.preview', {
|
|
502
|
-
taskIds,
|
|
503
|
-
sourceProject: opts['from'],
|
|
504
|
-
targetProject: opts['to'],
|
|
505
|
-
mode: opts['mode'],
|
|
506
|
-
scope: opts['scope'],
|
|
507
|
-
}, { command: 'nexus' });
|
|
508
|
-
});
|
|
509
|
-
// ── nexus transfer ────────────────────────────────────────────────
|
|
510
|
-
nexus
|
|
511
|
-
.command('transfer <taskIds...>')
|
|
512
|
-
.description('Transfer tasks from one project to another')
|
|
513
|
-
.requiredOption('--from <project>', 'Source project name')
|
|
514
|
-
.requiredOption('--to <project>', 'Target project name')
|
|
515
|
-
.option('--mode <mode>', 'Transfer mode: copy|move', 'copy')
|
|
516
|
-
.option('--scope <scope>', 'Transfer scope: single|subtree', 'subtree')
|
|
517
|
-
.option('--on-conflict <strategy>', 'Conflict strategy: rename|skip|duplicate|fail', 'rename')
|
|
518
|
-
.option('--transfer-brain', 'Also transfer associated brain memory entries', false)
|
|
519
|
-
.action(async (taskIds, opts) => {
|
|
520
|
-
await dispatchFromCli('mutate', 'nexus', 'transfer', {
|
|
521
|
-
taskIds,
|
|
522
|
-
sourceProject: opts['from'],
|
|
523
|
-
targetProject: opts['to'],
|
|
524
|
-
mode: opts['mode'],
|
|
525
|
-
scope: opts['scope'],
|
|
526
|
-
onConflict: opts['onConflict'],
|
|
527
|
-
transferBrain: opts['transferBrain'],
|
|
528
|
-
}, { command: 'nexus' });
|
|
529
|
-
});
|
|
530
|
-
// ── nexus permission ──────────────────────────────────────────────
|
|
531
|
-
const permission = nexus
|
|
532
|
-
.command('permission')
|
|
533
|
-
.description('Manage permissions for registered projects');
|
|
534
|
-
permission
|
|
535
|
-
.command('set <name> <level>')
|
|
536
|
-
.description('Set permission level for a registered project (read|write|execute)')
|
|
537
|
-
.action(async (name, level) => {
|
|
538
|
-
await dispatchFromCli('mutate', 'nexus', 'permission.set', {
|
|
539
|
-
name,
|
|
540
|
-
level,
|
|
541
|
-
}, { command: 'nexus' });
|
|
542
|
-
});
|
|
543
|
-
// ── nexus share ───────────────────────────────────────────────────
|
|
544
|
-
const share = nexus.command('share').description('Multi-contributor sharing operations');
|
|
545
|
-
share
|
|
546
|
-
.command('export')
|
|
547
|
-
.description('Export a snapshot of current project state for sharing')
|
|
548
|
-
.option('--output <path>', 'Output file path (default: auto-generated in current directory)')
|
|
549
|
-
.action(async (opts) => {
|
|
550
|
-
await dispatchFromCli('mutate', 'nexus', 'share.snapshot.export', {
|
|
551
|
-
outputPath: opts['output'],
|
|
552
|
-
}, { command: 'nexus' });
|
|
553
|
-
});
|
|
554
|
-
share
|
|
555
|
-
.command('import <file>')
|
|
556
|
-
.description('Import a shared project snapshot')
|
|
557
|
-
.action(async (file) => {
|
|
558
|
-
await dispatchFromCli('mutate', 'nexus', 'share.snapshot.import', {
|
|
559
|
-
inputPath: file,
|
|
560
|
-
}, { command: 'nexus' });
|
|
561
|
-
});
|
|
562
|
-
// ── nexus clusters ────────────────────────────────────────────────────────
|
|
563
|
-
nexus
|
|
564
|
-
.command('clusters [path]')
|
|
565
|
-
.description('List all detected communities (Louvain clusters) from the last analysis')
|
|
566
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
567
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from path)')
|
|
568
|
-
.action(async (targetPath, opts) => {
|
|
569
|
-
const startTime = Date.now();
|
|
570
|
-
const jsonOutput = !!opts['json'];
|
|
571
|
-
const projectIdOverride = opts['projectId'];
|
|
572
|
-
const repoPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
573
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
574
|
-
try {
|
|
575
|
-
const { getNexusDb, nexusSchema } = await import('@cleocode/core/store/nexus-sqlite');
|
|
576
|
-
const db = await getNexusDb();
|
|
577
|
-
// Query all nodes for this project, filter to community kind in-memory
|
|
578
|
-
// (avoids complex Drizzle where clause on an enum column).
|
|
579
|
-
// NodeSQLiteDatabase uses sync Drizzle — .all() returns a plain array,
|
|
580
|
-
// not a Promise, so wrap in try-catch rather than using .catch().
|
|
581
|
-
let rows = [];
|
|
582
|
-
try {
|
|
583
|
-
rows = db.select().from(nexusSchema.nexusNodes).all();
|
|
584
|
-
}
|
|
585
|
-
catch {
|
|
586
|
-
rows = [];
|
|
587
|
-
}
|
|
588
|
-
const communities = rows.filter((r) => r['kind'] === 'community' && r['projectId'] === projectId);
|
|
589
|
-
const durationMs = Date.now() - startTime;
|
|
590
|
-
if (jsonOutput) {
|
|
591
|
-
process.stdout.write(JSON.stringify({
|
|
592
|
-
success: true,
|
|
593
|
-
data: {
|
|
594
|
-
projectId,
|
|
595
|
-
repoPath,
|
|
596
|
-
count: communities.length,
|
|
597
|
-
communities: communities.map((c) => {
|
|
598
|
-
const meta = typeof c['metaJson'] === 'string'
|
|
599
|
-
? JSON.parse(c['metaJson'])
|
|
600
|
-
: {};
|
|
601
|
-
return {
|
|
602
|
-
id: c['id'],
|
|
603
|
-
label: c['label'],
|
|
604
|
-
symbolCount: meta['symbolCount'] ?? 0,
|
|
605
|
-
cohesion: meta['cohesion'] ?? 0,
|
|
606
|
-
};
|
|
607
|
-
}),
|
|
608
|
-
},
|
|
609
|
-
meta: {
|
|
610
|
-
operation: 'nexus.clusters',
|
|
611
|
-
duration_ms: durationMs,
|
|
612
|
-
timestamp: new Date().toISOString(),
|
|
613
|
-
},
|
|
614
|
-
}, null, 2) + '\n');
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
if (communities.length === 0) {
|
|
618
|
-
process.stdout.write(`[nexus] No communities found for project ${projectId}.\n` +
|
|
619
|
-
` Run 'cleo nexus analyze' first.\n`);
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
process.stdout.write(`[nexus] Communities for project ${projectId} (${communities.length} total):\n`);
|
|
623
|
-
for (const c of communities) {
|
|
624
|
-
const meta = typeof c['metaJson'] === 'string'
|
|
625
|
-
? JSON.parse(c['metaJson'])
|
|
626
|
-
: {};
|
|
627
|
-
const symbolCount = meta['symbolCount'] ?? 0;
|
|
628
|
-
const cohesion = typeof meta['cohesion'] === 'number'
|
|
629
|
-
? meta['cohesion'].toFixed(3)
|
|
630
|
-
: '0.000';
|
|
631
|
-
process.stdout.write(` ${String(c['id']).padEnd(16)} label=${String(c['label']).padEnd(24)} symbols=${String(symbolCount).padStart(5)} cohesion=${cohesion}\n`);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
catch (err) {
|
|
637
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
638
|
-
if (jsonOutput) {
|
|
639
|
-
process.stdout.write(JSON.stringify({
|
|
640
|
-
success: false,
|
|
641
|
-
error: { code: 'E_CLUSTERS_FAILED', message: msg },
|
|
642
|
-
meta: {
|
|
643
|
-
operation: 'nexus.clusters',
|
|
644
|
-
duration_ms: Date.now() - startTime,
|
|
645
|
-
timestamp: new Date().toISOString(),
|
|
646
|
-
},
|
|
647
|
-
}, null, 2) + '\n');
|
|
648
|
-
}
|
|
649
|
-
else {
|
|
650
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
651
|
-
}
|
|
652
|
-
process.exitCode = 1;
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
// ── nexus flows ───────────────────────────────────────────────────────────
|
|
656
|
-
nexus
|
|
657
|
-
.command('flows [path]')
|
|
658
|
-
.description('List all detected execution flows (processes) from the last analysis')
|
|
659
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
660
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from path)')
|
|
661
|
-
.action(async (targetPath, opts) => {
|
|
662
|
-
const startTime = Date.now();
|
|
663
|
-
const jsonOutput = !!opts['json'];
|
|
664
|
-
const projectIdOverride = opts['projectId'];
|
|
665
|
-
const repoPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
666
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
667
|
-
try {
|
|
668
|
-
const { getNexusDb, nexusSchema } = await import('@cleocode/core/store/nexus-sqlite');
|
|
669
|
-
const db = await getNexusDb();
|
|
670
|
-
// NodeSQLiteDatabase uses sync Drizzle — .all() returns a plain array,
|
|
671
|
-
// not a Promise, so wrap in try-catch rather than using .catch().
|
|
672
|
-
let rows = [];
|
|
673
|
-
try {
|
|
674
|
-
rows = db.select().from(nexusSchema.nexusNodes).all();
|
|
675
|
-
}
|
|
676
|
-
catch {
|
|
677
|
-
rows = [];
|
|
678
|
-
}
|
|
679
|
-
const processes = rows.filter((r) => r['kind'] === 'process' && r['projectId'] === projectId);
|
|
680
|
-
const durationMs = Date.now() - startTime;
|
|
681
|
-
if (jsonOutput) {
|
|
682
|
-
process.stdout.write(JSON.stringify({
|
|
683
|
-
success: true,
|
|
684
|
-
data: {
|
|
685
|
-
projectId,
|
|
686
|
-
repoPath,
|
|
687
|
-
count: processes.length,
|
|
688
|
-
flows: processes.map((p) => {
|
|
689
|
-
const meta = typeof p['metaJson'] === 'string'
|
|
690
|
-
? JSON.parse(p['metaJson'])
|
|
691
|
-
: {};
|
|
692
|
-
return {
|
|
693
|
-
id: p['id'],
|
|
694
|
-
label: p['label'],
|
|
695
|
-
stepCount: meta['stepCount'] ?? 0,
|
|
696
|
-
processType: meta['processType'] ?? 'intra_community',
|
|
697
|
-
entryPointId: meta['entryPointId'] ?? null,
|
|
698
|
-
};
|
|
699
|
-
}),
|
|
700
|
-
},
|
|
701
|
-
meta: {
|
|
702
|
-
operation: 'nexus.flows',
|
|
703
|
-
duration_ms: durationMs,
|
|
704
|
-
timestamp: new Date().toISOString(),
|
|
705
|
-
},
|
|
706
|
-
}, null, 2) + '\n');
|
|
707
|
-
}
|
|
708
|
-
else {
|
|
709
|
-
if (processes.length === 0) {
|
|
710
|
-
process.stdout.write(`[nexus] No execution flows found for project ${projectId}.\n` +
|
|
711
|
-
` Run 'cleo nexus analyze' first.\n`);
|
|
712
|
-
}
|
|
713
|
-
else {
|
|
714
|
-
process.stdout.write(`[nexus] Execution flows for project ${projectId} (${processes.length} total):\n`);
|
|
715
|
-
for (const p of processes) {
|
|
716
|
-
const meta = typeof p['metaJson'] === 'string'
|
|
717
|
-
? JSON.parse(p['metaJson'])
|
|
718
|
-
: {};
|
|
719
|
-
const stepCount = meta['stepCount'] ?? 0;
|
|
720
|
-
const processType = String(meta['processType'] ?? 'intra').replace('_community', '');
|
|
721
|
-
process.stdout.write(` ${String(p['id']).padEnd(30)} steps=${String(stepCount).padStart(3)} type=${processType.padEnd(12)} ${String(p['label'])}\n`);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
catch (err) {
|
|
727
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
728
|
-
if (jsonOutput) {
|
|
729
|
-
process.stdout.write(JSON.stringify({
|
|
730
|
-
success: false,
|
|
731
|
-
error: { code: 'E_FLOWS_FAILED', message: msg },
|
|
732
|
-
meta: {
|
|
733
|
-
operation: 'nexus.flows',
|
|
734
|
-
duration_ms: Date.now() - startTime,
|
|
735
|
-
timestamp: new Date().toISOString(),
|
|
736
|
-
},
|
|
737
|
-
}, null, 2) + '\n');
|
|
738
|
-
}
|
|
739
|
-
else {
|
|
740
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
741
|
-
}
|
|
742
|
-
process.exitCode = 1;
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
// ── nexus context ─────────────────────────────────────────────────────────
|
|
746
|
-
nexus
|
|
747
|
-
.command('context <symbol>')
|
|
748
|
-
.description('Show callers, callees, community membership, and process participation for a code symbol')
|
|
749
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
750
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from cwd)')
|
|
751
|
-
.option('--limit <n>', 'Max callers/callees to show per side', parseInt, 20)
|
|
752
|
-
.action(async (symbolName, opts) => {
|
|
753
|
-
const startTime = Date.now();
|
|
754
|
-
const jsonOutput = !!opts['json'];
|
|
755
|
-
const projectIdOverride = opts['projectId'];
|
|
756
|
-
const repoPath = process.cwd();
|
|
757
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
758
|
-
const limit = opts['limit'] ?? 20;
|
|
759
|
-
try {
|
|
760
|
-
const { getNexusDb, nexusSchema } = await import('@cleocode/core/store/nexus-sqlite');
|
|
761
|
-
const db = await getNexusDb();
|
|
762
|
-
// Find nodes matching the symbol name (case-insensitive partial match).
|
|
763
|
-
// NodeSQLiteDatabase uses sync Drizzle — .all() returns a plain array.
|
|
764
|
-
let allNodes = [];
|
|
765
|
-
try {
|
|
766
|
-
allNodes = db.select().from(nexusSchema.nexusNodes).all();
|
|
767
|
-
}
|
|
768
|
-
catch {
|
|
769
|
-
allNodes = [];
|
|
770
|
-
}
|
|
771
|
-
const lowerSymbol = symbolName.toLowerCase();
|
|
772
|
-
const rawMatchingNodes = allNodes.filter((n) => n['projectId'] === projectId &&
|
|
773
|
-
n['name'] != null &&
|
|
774
|
-
String(n['name']).toLowerCase().includes(lowerSymbol) &&
|
|
775
|
-
// Exclude synthetic graph-level nodes from symbol search
|
|
776
|
-
n['kind'] !== 'community' &&
|
|
777
|
-
n['kind'] !== 'process');
|
|
778
|
-
// Sort so callable symbols (function, method, class) rank before
|
|
779
|
-
// structural nodes (file, folder) — structural nodes have no `calls`
|
|
780
|
-
// relations and would produce empty callers/callees lists.
|
|
781
|
-
const matchingNodes = sortMatchingNodes(rawMatchingNodes, symbolName);
|
|
782
|
-
if (matchingNodes.length === 0) {
|
|
783
|
-
const durationMs = Date.now() - startTime;
|
|
784
|
-
if (jsonOutput) {
|
|
785
|
-
process.stdout.write(JSON.stringify({
|
|
786
|
-
success: false,
|
|
787
|
-
error: {
|
|
788
|
-
code: 'E_NOT_FOUND',
|
|
789
|
-
message: `No symbol found matching '${symbolName}' in project ${projectId}`,
|
|
790
|
-
},
|
|
791
|
-
meta: {
|
|
792
|
-
operation: 'nexus.context',
|
|
793
|
-
duration_ms: durationMs,
|
|
794
|
-
timestamp: new Date().toISOString(),
|
|
795
|
-
},
|
|
796
|
-
}, null, 2) + '\n');
|
|
797
|
-
}
|
|
798
|
-
else {
|
|
799
|
-
process.stdout.write(`[nexus] No symbol found matching '${symbolName}'.\n` +
|
|
800
|
-
` Run 'cleo nexus analyze' first, or check the symbol name.\n`);
|
|
801
|
-
}
|
|
802
|
-
process.exitCode = 4;
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
// Load all relations once — cheaper than N queries per node.
|
|
806
|
-
let allRelations = [];
|
|
807
|
-
try {
|
|
808
|
-
allRelations = db.select().from(nexusSchema.nexusRelations).all();
|
|
809
|
-
}
|
|
810
|
-
catch {
|
|
811
|
-
allRelations = [];
|
|
812
|
-
}
|
|
813
|
-
// Build a node-by-id index for fast lookups.
|
|
814
|
-
const nodeById = new Map();
|
|
815
|
-
for (const n of allNodes) {
|
|
816
|
-
nodeById.set(String(n['id']), n);
|
|
817
|
-
}
|
|
818
|
-
// Build context for each matching node.
|
|
819
|
-
const results = matchingNodes.slice(0, 5).map((node) => {
|
|
820
|
-
const nodeId = String(node['id']);
|
|
821
|
-
// Incoming: who calls/imports/references THIS node (target = nodeId)
|
|
822
|
-
const incoming = allRelations
|
|
823
|
-
.filter((r) => r['targetId'] === nodeId &&
|
|
824
|
-
r['projectId'] === projectId &&
|
|
825
|
-
(r['type'] === 'calls' || r['type'] === 'imports' || r['type'] === 'accesses'))
|
|
826
|
-
.slice(0, limit)
|
|
827
|
-
.map((r) => {
|
|
828
|
-
const src = nodeById.get(String(r['sourceId']));
|
|
829
|
-
return {
|
|
830
|
-
relationType: r['type'],
|
|
831
|
-
nodeId: r['sourceId'],
|
|
832
|
-
name: src?.['name'] ?? r['sourceId'],
|
|
833
|
-
kind: src?.['kind'] ?? 'unknown',
|
|
834
|
-
filePath: src?.['filePath'] ?? null,
|
|
835
|
-
};
|
|
836
|
-
});
|
|
837
|
-
// Outgoing: what THIS node calls/imports/accesses (source = nodeId)
|
|
838
|
-
const outgoing = allRelations
|
|
839
|
-
.filter((r) => r['sourceId'] === nodeId &&
|
|
840
|
-
r['projectId'] === projectId &&
|
|
841
|
-
(r['type'] === 'calls' || r['type'] === 'imports' || r['type'] === 'accesses'))
|
|
842
|
-
.slice(0, limit)
|
|
843
|
-
.map((r) => {
|
|
844
|
-
const tgt = nodeById.get(String(r['targetId']));
|
|
845
|
-
return {
|
|
846
|
-
relationType: r['type'],
|
|
847
|
-
nodeId: r['targetId'],
|
|
848
|
-
name: tgt?.['name'] ?? r['targetId'],
|
|
849
|
-
kind: tgt?.['kind'] ?? 'unknown',
|
|
850
|
-
filePath: tgt?.['filePath'] ?? null,
|
|
851
|
-
};
|
|
852
|
-
});
|
|
853
|
-
// Community membership
|
|
854
|
-
const communityId = node['communityId'];
|
|
855
|
-
const community = communityId ? nodeById.get(communityId) : null;
|
|
856
|
-
// Process participation (step_in_process or entry_point_of relations)
|
|
857
|
-
const processRelations = allRelations.filter((r) => r['sourceId'] === nodeId &&
|
|
858
|
-
r['projectId'] === projectId &&
|
|
859
|
-
(r['type'] === 'step_in_process' || r['type'] === 'entry_point_of'));
|
|
860
|
-
const processes = processRelations
|
|
861
|
-
.map((r) => {
|
|
862
|
-
const proc = nodeById.get(String(r['targetId']));
|
|
863
|
-
return {
|
|
864
|
-
processId: r['targetId'],
|
|
865
|
-
label: proc?.['label'] ?? r['targetId'],
|
|
866
|
-
role: r['type'] === 'entry_point_of' ? 'entry_point' : 'step',
|
|
867
|
-
step: r['step'] ?? null,
|
|
868
|
-
};
|
|
869
|
-
})
|
|
870
|
-
.filter((p) => p.label !== p.processId); // filter unresolved
|
|
871
|
-
return {
|
|
872
|
-
nodeId,
|
|
873
|
-
name: node['name'],
|
|
874
|
-
kind: node['kind'],
|
|
875
|
-
filePath: node['filePath'],
|
|
876
|
-
startLine: node['startLine'],
|
|
877
|
-
endLine: node['endLine'],
|
|
878
|
-
isExported: node['isExported'],
|
|
879
|
-
docSummary: node['docSummary'],
|
|
880
|
-
community: community
|
|
881
|
-
? { id: communityId, label: community['label'] }
|
|
882
|
-
: communityId
|
|
883
|
-
? { id: communityId, label: null }
|
|
884
|
-
: null,
|
|
885
|
-
callers: incoming,
|
|
886
|
-
callees: outgoing,
|
|
887
|
-
processes,
|
|
888
|
-
};
|
|
889
|
-
});
|
|
890
|
-
const durationMs = Date.now() - startTime;
|
|
891
|
-
const primary = results[0];
|
|
892
|
-
if (jsonOutput) {
|
|
893
|
-
process.stdout.write(JSON.stringify({
|
|
894
|
-
success: true,
|
|
895
|
-
data: {
|
|
896
|
-
query: symbolName,
|
|
897
|
-
projectId,
|
|
898
|
-
matchCount: matchingNodes.length,
|
|
899
|
-
results,
|
|
900
|
-
},
|
|
901
|
-
meta: {
|
|
902
|
-
operation: 'nexus.context',
|
|
903
|
-
duration_ms: durationMs,
|
|
904
|
-
timestamp: new Date().toISOString(),
|
|
905
|
-
},
|
|
906
|
-
}, null, 2) + '\n');
|
|
907
|
-
}
|
|
908
|
-
else {
|
|
909
|
-
process.stdout.write(`[nexus] Context for symbol '${symbolName}' (${matchingNodes.length} match${matchingNodes.length !== 1 ? 'es' : ''}):\n`);
|
|
910
|
-
for (const r of results) {
|
|
911
|
-
process.stdout.write(`\n Symbol: ${String(r.name)} (${String(r.kind)})\n` +
|
|
912
|
-
` File: ${r.filePath ? String(r.filePath) : 'n/a'}` +
|
|
913
|
-
(r.startLine ? `:${String(r.startLine)}` : '') +
|
|
914
|
-
'\n' +
|
|
915
|
-
(r.docSummary ? ` Doc: ${String(r.docSummary)}\n` : '') +
|
|
916
|
-
(r.community
|
|
917
|
-
? ` Community: ${String(r.community.label ?? r.community.id)}\n`
|
|
918
|
-
: '') +
|
|
919
|
-
` Callers (${r.callers.length}): ${r.callers.length === 0
|
|
920
|
-
? 'none'
|
|
921
|
-
: r.callers.map((c) => `${String(c.name)}[${String(c.kind)}]`).join(', ')}\n` +
|
|
922
|
-
` Callees (${r.callees.length}): ${r.callees.length === 0
|
|
923
|
-
? 'none'
|
|
924
|
-
: r.callees.map((c) => `${String(c.name)}[${String(c.kind)}]`).join(', ')}\n` +
|
|
925
|
-
(r.processes.length > 0
|
|
926
|
-
? ` Processes: ${r.processes.map((p) => `${String(p.label)}(${String(p.role)})`).join(', ')}\n`
|
|
927
|
-
: ''));
|
|
928
|
-
}
|
|
929
|
-
if (matchingNodes.length > 5) {
|
|
930
|
-
process.stdout.write(`\n (Showing 5 of ${matchingNodes.length} matches — use --json for full list)\n`);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
void primary; // referenced to satisfy lint
|
|
934
|
-
}
|
|
935
|
-
catch (err) {
|
|
936
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
937
|
-
if (jsonOutput) {
|
|
938
|
-
process.stdout.write(JSON.stringify({
|
|
939
|
-
success: false,
|
|
940
|
-
error: { code: 'E_CONTEXT_FAILED', message: msg },
|
|
941
|
-
meta: {
|
|
942
|
-
operation: 'nexus.context',
|
|
943
|
-
duration_ms: Date.now() - startTime,
|
|
944
|
-
timestamp: new Date().toISOString(),
|
|
945
|
-
},
|
|
946
|
-
}, null, 2) + '\n');
|
|
947
|
-
}
|
|
948
|
-
else {
|
|
949
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
950
|
-
}
|
|
951
|
-
process.exitCode = 1;
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
// ── nexus impact ──────────────────────────────────────────────────────────
|
|
955
|
-
nexus
|
|
956
|
-
.command('impact <symbol>')
|
|
957
|
-
.description('Show blast radius for a code symbol — direct callers (d=1), indirect callers (d=2), transitive (d=3)')
|
|
958
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
959
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from cwd)')
|
|
960
|
-
.option('--depth <n>', 'Maximum traversal depth (default: 3)', parseInt, 3)
|
|
961
|
-
.action(async (symbolName, opts) => {
|
|
962
|
-
const startTime = Date.now();
|
|
963
|
-
const jsonOutput = !!opts['json'];
|
|
964
|
-
const projectIdOverride = opts['projectId'];
|
|
965
|
-
const repoPath = process.cwd();
|
|
966
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
967
|
-
const maxDepth = Math.min(opts['depth'] ?? 3, 5);
|
|
968
|
-
try {
|
|
969
|
-
const { getNexusDb, nexusSchema } = await import('@cleocode/core/store/nexus-sqlite');
|
|
970
|
-
const db = await getNexusDb();
|
|
971
|
-
// Load all nodes and relations for this project once.
|
|
972
|
-
let allNodes = [];
|
|
973
|
-
try {
|
|
974
|
-
allNodes = db.select().from(nexusSchema.nexusNodes).all();
|
|
975
|
-
}
|
|
976
|
-
catch {
|
|
977
|
-
allNodes = [];
|
|
978
|
-
}
|
|
979
|
-
const lowerSymbol = symbolName.toLowerCase();
|
|
980
|
-
const rawMatchingNodes = allNodes.filter((n) => n['projectId'] === projectId &&
|
|
981
|
-
n['name'] != null &&
|
|
982
|
-
String(n['name']).toLowerCase().includes(lowerSymbol) &&
|
|
983
|
-
n['kind'] !== 'community' &&
|
|
984
|
-
n['kind'] !== 'process');
|
|
985
|
-
// Sort so callable symbols (function, method, class) rank before
|
|
986
|
-
// structural nodes (file, folder) — structural nodes have no `calls`
|
|
987
|
-
// relations and would produce zero impact.
|
|
988
|
-
const matchingNodes = sortMatchingNodes(rawMatchingNodes, symbolName);
|
|
989
|
-
if (matchingNodes.length === 0) {
|
|
990
|
-
const durationMs = Date.now() - startTime;
|
|
991
|
-
if (jsonOutput) {
|
|
992
|
-
process.stdout.write(JSON.stringify({
|
|
993
|
-
success: false,
|
|
994
|
-
error: {
|
|
995
|
-
code: 'E_NOT_FOUND',
|
|
996
|
-
message: `No symbol found matching '${symbolName}' in project ${projectId}`,
|
|
997
|
-
},
|
|
998
|
-
meta: {
|
|
999
|
-
operation: 'nexus.impact',
|
|
1000
|
-
duration_ms: durationMs,
|
|
1001
|
-
timestamp: new Date().toISOString(),
|
|
1002
|
-
},
|
|
1003
|
-
}, null, 2) + '\n');
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
process.stdout.write(`[nexus] No symbol found matching '${symbolName}'.\n` +
|
|
1007
|
-
` Run 'cleo nexus analyze' first, or check the symbol name.\n`);
|
|
1008
|
-
}
|
|
1009
|
-
process.exitCode = 4;
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
let allRelations = [];
|
|
1013
|
-
try {
|
|
1014
|
-
allRelations = db.select().from(nexusSchema.nexusRelations).all();
|
|
1015
|
-
}
|
|
1016
|
-
catch {
|
|
1017
|
-
allRelations = [];
|
|
1018
|
-
}
|
|
1019
|
-
// Build a node-by-id index for fast lookups.
|
|
1020
|
-
const nodeById = new Map();
|
|
1021
|
-
for (const n of allNodes) {
|
|
1022
|
-
nodeById.set(String(n['id']), n);
|
|
1023
|
-
}
|
|
1024
|
-
// BFS upstream: find all nodes that (transitively) call/import the target.
|
|
1025
|
-
const targetNode = matchingNodes[0];
|
|
1026
|
-
const targetId = String(targetNode['id']);
|
|
1027
|
-
// Build reverse adjacency: targetId → [sourceIds that call it]
|
|
1028
|
-
const reverseAdj = new Map();
|
|
1029
|
-
for (const r of allRelations) {
|
|
1030
|
-
if (r['projectId'] === projectId &&
|
|
1031
|
-
(r['type'] === 'calls' || r['type'] === 'imports' || r['type'] === 'accesses')) {
|
|
1032
|
-
const tid = String(r['targetId']);
|
|
1033
|
-
const sid = String(r['sourceId']);
|
|
1034
|
-
if (!reverseAdj.has(tid))
|
|
1035
|
-
reverseAdj.set(tid, []);
|
|
1036
|
-
reverseAdj.get(tid).push(sid);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
// BFS traversal up to maxDepth levels.
|
|
1040
|
-
const visited = new Set([targetId]);
|
|
1041
|
-
const depthMap = new Map(); // nodeId → depth
|
|
1042
|
-
const queue = [{ id: targetId, depth: 0 }];
|
|
1043
|
-
const impactByDepth = [];
|
|
1044
|
-
while (queue.length > 0) {
|
|
1045
|
-
const item = queue.shift();
|
|
1046
|
-
if (item.depth >= maxDepth)
|
|
1047
|
-
continue;
|
|
1048
|
-
const callers = reverseAdj.get(item.id) ?? [];
|
|
1049
|
-
for (const callerId of callers) {
|
|
1050
|
-
if (visited.has(callerId))
|
|
1051
|
-
continue;
|
|
1052
|
-
visited.add(callerId);
|
|
1053
|
-
const depth = item.depth + 1;
|
|
1054
|
-
depthMap.set(callerId, depth);
|
|
1055
|
-
const callerNode = nodeById.get(callerId);
|
|
1056
|
-
if (!impactByDepth[depth - 1])
|
|
1057
|
-
impactByDepth[depth - 1] = [];
|
|
1058
|
-
impactByDepth[depth - 1].push({
|
|
1059
|
-
nodeId: callerId,
|
|
1060
|
-
name: String(callerNode?.['name'] ?? callerId),
|
|
1061
|
-
kind: String(callerNode?.['kind'] ?? 'unknown'),
|
|
1062
|
-
filePath: callerNode?.['filePath'] ? String(callerNode['filePath']) : null,
|
|
1063
|
-
});
|
|
1064
|
-
queue.push({ id: callerId, depth });
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
const totalImpact = visited.size - 1; // exclude the target itself
|
|
1068
|
-
const riskLevel = totalImpact === 0
|
|
1069
|
-
? 'NONE'
|
|
1070
|
-
: totalImpact <= 3
|
|
1071
|
-
? 'LOW'
|
|
1072
|
-
: totalImpact <= 10
|
|
1073
|
-
? 'MEDIUM'
|
|
1074
|
-
: totalImpact <= 25
|
|
1075
|
-
? 'HIGH'
|
|
1076
|
-
: 'CRITICAL';
|
|
1077
|
-
const durationMs = Date.now() - startTime;
|
|
1078
|
-
if (jsonOutput) {
|
|
1079
|
-
process.stdout.write(JSON.stringify({
|
|
1080
|
-
success: true,
|
|
1081
|
-
data: {
|
|
1082
|
-
query: symbolName,
|
|
1083
|
-
projectId,
|
|
1084
|
-
targetNodeId: targetId,
|
|
1085
|
-
targetName: targetNode['name'],
|
|
1086
|
-
targetKind: targetNode['kind'],
|
|
1087
|
-
targetFilePath: targetNode['filePath'],
|
|
1088
|
-
riskLevel,
|
|
1089
|
-
totalImpactedNodes: totalImpact,
|
|
1090
|
-
maxDepth,
|
|
1091
|
-
impactByDepth: impactByDepth.map((layer, i) => ({
|
|
1092
|
-
depth: i + 1,
|
|
1093
|
-
label: i === 0
|
|
1094
|
-
? 'WILL BREAK (direct callers)'
|
|
1095
|
-
: i === 1
|
|
1096
|
-
? 'LIKELY AFFECTED'
|
|
1097
|
-
: 'MAY NEED TESTING',
|
|
1098
|
-
nodes: layer,
|
|
1099
|
-
})),
|
|
1100
|
-
},
|
|
1101
|
-
meta: {
|
|
1102
|
-
operation: 'nexus.impact',
|
|
1103
|
-
duration_ms: durationMs,
|
|
1104
|
-
timestamp: new Date().toISOString(),
|
|
1105
|
-
},
|
|
1106
|
-
}, null, 2) + '\n');
|
|
1107
|
-
}
|
|
1108
|
-
else {
|
|
1109
|
-
process.stdout.write(`[nexus] Impact analysis for '${symbolName}'\n` +
|
|
1110
|
-
` Target: ${String(targetNode['name'])} (${String(targetNode['kind'])})\n` +
|
|
1111
|
-
` File: ${targetNode['filePath'] ? String(targetNode['filePath']) : 'n/a'}\n` +
|
|
1112
|
-
` Risk: ${riskLevel} (${totalImpact} impacted node${totalImpact !== 1 ? 's' : ''})\n`);
|
|
1113
|
-
if (totalImpact === 0) {
|
|
1114
|
-
process.stdout.write(' No callers found — safe to modify.\n');
|
|
1115
|
-
}
|
|
1116
|
-
else {
|
|
1117
|
-
for (let i = 0; i < impactByDepth.length; i++) {
|
|
1118
|
-
const layer = impactByDepth[i];
|
|
1119
|
-
if (!layer || layer.length === 0)
|
|
1120
|
-
continue;
|
|
1121
|
-
const label = i === 0 ? 'WILL BREAK' : i === 1 ? 'LIKELY AFFECTED' : 'MAY NEED TESTING';
|
|
1122
|
-
process.stdout.write(`\n d=${i + 1} ${label} (${layer.length}):\n`);
|
|
1123
|
-
for (const node of layer.slice(0, 15)) {
|
|
1124
|
-
process.stdout.write(` ${String(node.name).padEnd(36)} ${String(node.kind).padEnd(12)} ${node.filePath ?? ''}\n`);
|
|
1125
|
-
}
|
|
1126
|
-
if (layer.length > 15) {
|
|
1127
|
-
process.stdout.write(` ... and ${layer.length - 15} more\n`);
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
if (matchingNodes.length > 1) {
|
|
1132
|
-
process.stdout.write(`\n (Showing analysis for first match — ${matchingNodes.length} total matches for '${symbolName}')\n`);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
catch (err) {
|
|
1137
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1138
|
-
if (jsonOutput) {
|
|
1139
|
-
process.stdout.write(JSON.stringify({
|
|
1140
|
-
success: false,
|
|
1141
|
-
error: { code: 'E_IMPACT_FAILED', message: msg },
|
|
1142
|
-
meta: {
|
|
1143
|
-
operation: 'nexus.impact',
|
|
1144
|
-
duration_ms: Date.now() - startTime,
|
|
1145
|
-
timestamp: new Date().toISOString(),
|
|
1146
|
-
},
|
|
1147
|
-
}, null, 2) + '\n');
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1151
|
-
}
|
|
1152
|
-
process.exitCode = 1;
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
// ── nexus analyze ─────────────────────────────────────────────────────────
|
|
1156
|
-
nexus
|
|
1157
|
-
.command('analyze [path]')
|
|
1158
|
-
.description('Run code intelligence pipeline on a repository directory')
|
|
1159
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
1160
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected)')
|
|
1161
|
-
.option('--incremental', 'Only re-index files that have changed since the last run (faster)')
|
|
1162
|
-
.action(async (targetPath, opts) => {
|
|
1163
|
-
const startTime = Date.now();
|
|
1164
|
-
const jsonOutput = !!opts['json'];
|
|
1165
|
-
const projectIdOverride = opts['projectId'];
|
|
1166
|
-
const isIncremental = !!opts['incremental'];
|
|
1167
|
-
// Resolve target path
|
|
1168
|
-
const repoPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
1169
|
-
if (!jsonOutput) {
|
|
1170
|
-
process.stderr.write(`[nexus] Analyzing: ${repoPath}${isIncremental ? ' (incremental)' : ''}\n`);
|
|
1171
|
-
}
|
|
1172
|
-
try {
|
|
1173
|
-
// Lazy imports to avoid loading heavy dependencies until needed
|
|
1174
|
-
const [{ getNexusDb, nexusSchema }, { runPipeline }, { getProjectRoot }, { eq }] = await Promise.all([
|
|
1175
|
-
import('@cleocode/core/store/nexus-sqlite'),
|
|
1176
|
-
import('@cleocode/nexus/pipeline'),
|
|
1177
|
-
import('@cleocode/core/internal'),
|
|
1178
|
-
import('drizzle-orm'),
|
|
1179
|
-
]);
|
|
1180
|
-
// Determine project ID — use override or derive from path
|
|
1181
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
1182
|
-
// Get DB and table references
|
|
1183
|
-
const db = await getNexusDb();
|
|
1184
|
-
const tables = {
|
|
1185
|
-
nexusNodes: nexusSchema.nexusNodes,
|
|
1186
|
-
nexusRelations: nexusSchema.nexusRelations,
|
|
1187
|
-
};
|
|
1188
|
-
// For full (non-incremental) runs: delete existing index first.
|
|
1189
|
-
// NodeSQLiteDatabase uses sync Drizzle — no await, wrap in try-catch.
|
|
1190
|
-
if (!isIncremental) {
|
|
1191
|
-
if (!jsonOutput) {
|
|
1192
|
-
process.stderr.write('[nexus] Clearing existing index for project...\n');
|
|
1193
|
-
}
|
|
1194
|
-
try {
|
|
1195
|
-
db.delete(nexusSchema.nexusNodes)
|
|
1196
|
-
.where(eq(nexusSchema.nexusNodes.projectId, projectId))
|
|
1197
|
-
.run();
|
|
1198
|
-
}
|
|
1199
|
-
catch {
|
|
1200
|
-
// Table may not have rows — ignore
|
|
1201
|
-
}
|
|
1202
|
-
try {
|
|
1203
|
-
db.delete(nexusSchema.nexusRelations)
|
|
1204
|
-
.where(eq(nexusSchema.nexusRelations.projectId, projectId))
|
|
1205
|
-
.run();
|
|
1206
|
-
}
|
|
1207
|
-
catch {
|
|
1208
|
-
// Table may not have rows — ignore
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
// Run the pipeline (full or incremental)
|
|
1212
|
-
const result = await runPipeline(repoPath, projectId, db, tables, jsonOutput
|
|
1213
|
-
? undefined
|
|
1214
|
-
: (current, total, filePath) => {
|
|
1215
|
-
if (current % 50 === 0 || current === total) {
|
|
1216
|
-
const pct = total > 0 ? Math.round((current / total) * 100) : 100;
|
|
1217
|
-
process.stderr.write(`[nexus] Progress: ${current}/${total} files (${pct}%) — ${filePath}\n`);
|
|
1218
|
-
}
|
|
1219
|
-
}, { incremental: isIncremental });
|
|
1220
|
-
const durationMs = Date.now() - startTime;
|
|
1221
|
-
// Write nexus-bridge.md after a successful pipeline run (best-effort)
|
|
1222
|
-
try {
|
|
1223
|
-
const { refreshNexusBridge } = await import('@cleocode/core/internal');
|
|
1224
|
-
await refreshNexusBridge(repoPath, projectId);
|
|
1225
|
-
if (!jsonOutput) {
|
|
1226
|
-
process.stderr.write(`[nexus] nexus-bridge.md refreshed at ${repoPath}/.cleo/nexus-bridge.md\n`);
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
catch {
|
|
1230
|
-
// Non-fatal — bridge refresh failure should not fail the analyze command
|
|
1231
|
-
}
|
|
1232
|
-
// Auto-register project and update index stats in the multi-project registry (best-effort)
|
|
1233
|
-
try {
|
|
1234
|
-
const { nexusUpdateIndexStats } = await import('@cleocode/core/internal');
|
|
1235
|
-
await nexusUpdateIndexStats(repoPath, {
|
|
1236
|
-
nodeCount: result.nodeCount,
|
|
1237
|
-
relationCount: result.relationCount,
|
|
1238
|
-
fileCount: result.fileCount,
|
|
1239
|
-
});
|
|
1240
|
-
if (!jsonOutput) {
|
|
1241
|
-
process.stderr.write('[nexus] Project registered/updated in multi-project registry.\n');
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
catch {
|
|
1245
|
-
// Non-fatal — registry update must never fail the analyze command
|
|
1246
|
-
}
|
|
1247
|
-
if (jsonOutput) {
|
|
1248
|
-
const envelope = {
|
|
1249
|
-
success: true,
|
|
1250
|
-
data: {
|
|
1251
|
-
projectId,
|
|
1252
|
-
repoPath,
|
|
1253
|
-
incremental: isIncremental,
|
|
1254
|
-
nodeCount: result.nodeCount,
|
|
1255
|
-
relationCount: result.relationCount,
|
|
1256
|
-
fileCount: result.fileCount,
|
|
1257
|
-
durationMs,
|
|
1258
|
-
},
|
|
1259
|
-
meta: {
|
|
1260
|
-
operation: 'nexus.analyze',
|
|
1261
|
-
duration_ms: durationMs,
|
|
1262
|
-
timestamp: new Date().toISOString(),
|
|
1263
|
-
},
|
|
1264
|
-
};
|
|
1265
|
-
process.stdout.write(JSON.stringify(envelope, null, 2) + '\n');
|
|
1266
|
-
}
|
|
1267
|
-
else {
|
|
1268
|
-
process.stdout.write(`[nexus] Analysis complete${isIncremental ? ' (incremental)' : ''}:\n` +
|
|
1269
|
-
` Project ID: ${projectId}\n` +
|
|
1270
|
-
` Files: ${result.fileCount}\n` +
|
|
1271
|
-
` Nodes: ${result.nodeCount}\n` +
|
|
1272
|
-
` Relations: ${result.relationCount}\n` +
|
|
1273
|
-
` Duration: ${durationMs}ms\n`);
|
|
1274
|
-
}
|
|
1275
|
-
void getProjectRoot; // referenced to satisfy import
|
|
1276
|
-
}
|
|
1277
|
-
catch (err) {
|
|
1278
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1279
|
-
if (jsonOutput) {
|
|
1280
|
-
const envelope = {
|
|
1281
|
-
success: false,
|
|
1282
|
-
error: { code: 'E_PIPELINE_FAILED', message: msg },
|
|
1283
|
-
meta: {
|
|
1284
|
-
operation: 'nexus.analyze',
|
|
1285
|
-
duration_ms: Date.now() - startTime,
|
|
1286
|
-
timestamp: new Date().toISOString(),
|
|
1287
|
-
},
|
|
1288
|
-
};
|
|
1289
|
-
process.stdout.write(JSON.stringify(envelope, null, 2) + '\n');
|
|
1290
|
-
}
|
|
1291
|
-
else {
|
|
1292
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1293
|
-
}
|
|
1294
|
-
process.exitCode = 1;
|
|
1295
|
-
}
|
|
1296
|
-
});
|
|
1297
|
-
// ── nexus projects ────────────────────────────────────────────────────────
|
|
1298
|
-
// A focused subcommand group for the multi-project registry (T622).
|
|
1299
|
-
// Maps to the existing nexus.list / nexus.register / nexus.unregister
|
|
1300
|
-
// operations but adds richer output including db paths and index stats.
|
|
1301
|
-
const projects = nexus.command('projects').description('Multi-project registry management');
|
|
1302
|
-
projects
|
|
1303
|
-
.command('list')
|
|
1304
|
-
.description('List all projects registered in the global nexus registry')
|
|
1305
|
-
.option('--json', 'Output as JSON (LAFS envelope format)')
|
|
1306
|
-
.action(async (opts) => {
|
|
1307
|
-
const startTime = Date.now();
|
|
1308
|
-
const jsonOutput = !!opts['json'];
|
|
1309
|
-
try {
|
|
1310
|
-
const { nexusList } = await import('@cleocode/core/internal');
|
|
1311
|
-
const list = (await nexusList());
|
|
1312
|
-
const durationMs = Date.now() - startTime;
|
|
1313
|
-
if (jsonOutput) {
|
|
1314
|
-
process.stdout.write(JSON.stringify({
|
|
1315
|
-
success: true,
|
|
1316
|
-
data: { projects: list, count: list.length },
|
|
1317
|
-
meta: {
|
|
1318
|
-
operation: 'nexus.projects.list',
|
|
1319
|
-
duration_ms: durationMs,
|
|
1320
|
-
timestamp: new Date().toISOString(),
|
|
1321
|
-
},
|
|
1322
|
-
}, null, 2) + '\n');
|
|
1323
|
-
}
|
|
1324
|
-
else if (list.length === 0) {
|
|
1325
|
-
process.stdout.write('[nexus] No projects registered. Run: cleo nexus projects register\n');
|
|
1326
|
-
}
|
|
1327
|
-
else {
|
|
1328
|
-
process.stdout.write(`[nexus] Registered projects (${list.length}):\n\n`);
|
|
1329
|
-
for (const p of list) {
|
|
1330
|
-
const nodes = p.stats?.nodeCount ?? 0;
|
|
1331
|
-
const rels = p.stats?.relationCount ?? 0;
|
|
1332
|
-
const indexed = p.lastIndexed ? p.lastIndexed.slice(0, 10) : 'never';
|
|
1333
|
-
process.stdout.write(` ${p.name.padEnd(28)} tasks=${String(p.taskCount).padStart(5)} nodes=${String(nodes).padStart(6)} relations=${String(rels).padStart(7)} indexed=${indexed}\n` +
|
|
1334
|
-
` ${''.padEnd(28)} path=${p.path}\n`);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
catch (err) {
|
|
1339
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1340
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1341
|
-
process.exitCode = 1;
|
|
1342
|
-
}
|
|
1343
|
-
});
|
|
1344
|
-
projects
|
|
1345
|
-
.command('register [path]')
|
|
1346
|
-
.description('Register a project in the global nexus registry (default: current directory)')
|
|
1347
|
-
.option('--name <name>', 'Custom project name (default: directory name)')
|
|
1348
|
-
.option('--json', 'Output as JSON (LAFS envelope format)')
|
|
1349
|
-
.action(async (targetPath, opts) => {
|
|
1350
|
-
const startTime = Date.now();
|
|
1351
|
-
const jsonOutput = !!opts['json'];
|
|
1352
|
-
const repoPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
1353
|
-
const name = opts['name'];
|
|
1354
|
-
try {
|
|
1355
|
-
const { nexusRegister } = await import('@cleocode/core/internal');
|
|
1356
|
-
const hash = await nexusRegister(repoPath, name);
|
|
1357
|
-
const durationMs = Date.now() - startTime;
|
|
1358
|
-
if (jsonOutput) {
|
|
1359
|
-
process.stdout.write(JSON.stringify({
|
|
1360
|
-
success: true,
|
|
1361
|
-
data: { hash, path: repoPath },
|
|
1362
|
-
meta: {
|
|
1363
|
-
operation: 'nexus.projects.register',
|
|
1364
|
-
duration_ms: durationMs,
|
|
1365
|
-
timestamp: new Date().toISOString(),
|
|
1366
|
-
},
|
|
1367
|
-
}, null, 2) + '\n');
|
|
1368
|
-
}
|
|
1369
|
-
else {
|
|
1370
|
-
process.stdout.write(`[nexus] Registered: ${repoPath} (hash: ${hash})\n`);
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
catch (err) {
|
|
1374
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1375
|
-
if (jsonOutput) {
|
|
1376
|
-
process.stdout.write(JSON.stringify({
|
|
1377
|
-
success: false,
|
|
1378
|
-
error: { code: 'E_REGISTER_FAILED', message: msg },
|
|
1379
|
-
meta: {
|
|
1380
|
-
operation: 'nexus.projects.register',
|
|
1381
|
-
duration_ms: Date.now() - startTime,
|
|
1382
|
-
timestamp: new Date().toISOString(),
|
|
1383
|
-
},
|
|
1384
|
-
}, null, 2) + '\n');
|
|
1385
|
-
}
|
|
1386
|
-
else {
|
|
1387
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1388
|
-
}
|
|
1389
|
-
process.exitCode = 1;
|
|
1390
|
-
}
|
|
1391
|
-
});
|
|
1392
|
-
projects
|
|
1393
|
-
.command('remove <nameOrHash>')
|
|
1394
|
-
.alias('rm')
|
|
1395
|
-
.description('Remove a project from the global nexus registry')
|
|
1396
|
-
.option('--json', 'Output as JSON (LAFS envelope format)')
|
|
1397
|
-
.action(async (nameOrHash, opts) => {
|
|
1398
|
-
const startTime = Date.now();
|
|
1399
|
-
const jsonOutput = !!opts['json'];
|
|
1400
|
-
try {
|
|
1401
|
-
const { nexusUnregister } = await import('@cleocode/core/internal');
|
|
1402
|
-
await nexusUnregister(nameOrHash);
|
|
1403
|
-
const durationMs = Date.now() - startTime;
|
|
1404
|
-
if (jsonOutput) {
|
|
1405
|
-
process.stdout.write(JSON.stringify({
|
|
1406
|
-
success: true,
|
|
1407
|
-
data: { removed: nameOrHash },
|
|
1408
|
-
meta: {
|
|
1409
|
-
operation: 'nexus.projects.remove',
|
|
1410
|
-
duration_ms: durationMs,
|
|
1411
|
-
timestamp: new Date().toISOString(),
|
|
1412
|
-
},
|
|
1413
|
-
}, null, 2) + '\n');
|
|
1414
|
-
}
|
|
1415
|
-
else {
|
|
1416
|
-
process.stdout.write(`[nexus] Removed: ${nameOrHash}\n`);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
catch (err) {
|
|
1420
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1421
|
-
if (jsonOutput) {
|
|
1422
|
-
process.stdout.write(JSON.stringify({
|
|
1423
|
-
success: false,
|
|
1424
|
-
error: { code: 'E_REMOVE_FAILED', message: msg },
|
|
1425
|
-
meta: {
|
|
1426
|
-
operation: 'nexus.projects.remove',
|
|
1427
|
-
duration_ms: Date.now() - startTime,
|
|
1428
|
-
timestamp: new Date().toISOString(),
|
|
1429
|
-
},
|
|
1430
|
-
}, null, 2) + '\n');
|
|
1431
|
-
}
|
|
1432
|
-
else {
|
|
1433
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1434
|
-
}
|
|
1435
|
-
process.exitCode = 1;
|
|
1436
|
-
}
|
|
1437
|
-
});
|
|
1438
|
-
// ── nexus projects scan ───────────────────────────────────────────────────
|
|
1439
|
-
// Walk filesystem roots to discover .cleo/ directories not in the registry.
|
|
1440
|
-
projects
|
|
1441
|
-
.command('scan')
|
|
1442
|
-
.description('Walk filesystem roots to discover .cleo/ directories not registered in the global nexus registry')
|
|
1443
|
-
.option('--roots <paths>', 'Comma-separated search roots (default: ~/code,~/projects,/mnt/projects)')
|
|
1444
|
-
.option('--max-depth <n>', 'Maximum directory traversal depth (default: 4)', parseInt, 4)
|
|
1445
|
-
.option('--auto-register', 'Register all discovered unregistered projects automatically')
|
|
1446
|
-
.option('--include-existing', 'Also report projects that are already registered')
|
|
1447
|
-
.option('--json', 'Output as JSON (LAFS envelope format)')
|
|
1448
|
-
.action(async (opts) => {
|
|
1449
|
-
const startTime = Date.now();
|
|
1450
|
-
const jsonOutput = !!opts['json'];
|
|
1451
|
-
const autoRegister = !!opts['autoRegister'];
|
|
1452
|
-
const includeExisting = !!opts['includeExisting'];
|
|
1453
|
-
const maxDepth = Math.max(1, Math.min(opts['maxDepth'] ?? 4, 20));
|
|
1454
|
-
// Resolve search roots
|
|
1455
|
-
const { homedir } = await import('node:os');
|
|
1456
|
-
const home = homedir();
|
|
1457
|
-
const defaultRoots = [path.join(home, 'code'), path.join(home, 'projects'), '/mnt/projects'];
|
|
1458
|
-
const rawRoots = typeof opts['roots'] === 'string' && opts['roots'].trim().length > 0
|
|
1459
|
-
? opts['roots']
|
|
1460
|
-
.split(',')
|
|
1461
|
-
.map((r) => r.trim())
|
|
1462
|
-
.filter((r) => r.length > 0)
|
|
1463
|
-
.map((r) => r.startsWith('~') ? path.join(home, r.slice(1)) : path.resolve(r))
|
|
1464
|
-
: defaultRoots;
|
|
1465
|
-
// Filter to roots that actually exist
|
|
1466
|
-
const { existsSync, readdirSync, statSync } = await import('node:fs');
|
|
1467
|
-
const { Dirent } = await import('node:fs');
|
|
1468
|
-
const roots = rawRoots.filter((r) => {
|
|
1469
|
-
try {
|
|
1470
|
-
return existsSync(r) && statSync(r).isDirectory();
|
|
1471
|
-
}
|
|
1472
|
-
catch {
|
|
1473
|
-
return false;
|
|
1474
|
-
}
|
|
1475
|
-
});
|
|
1476
|
-
if (!jsonOutput) {
|
|
1477
|
-
process.stdout.write(`[nexus] Scanning ${roots.length} root(s) up to depth ${maxDepth}:\n` +
|
|
1478
|
-
roots.map((r) => ` ${r}`).join('\n') +
|
|
1479
|
-
'\n');
|
|
1480
|
-
}
|
|
1481
|
-
// ── Filesystem walker ──────────────────────────────────────────────
|
|
1482
|
-
// Walk recursively up to maxDepth. Skip common build/cache directories.
|
|
1483
|
-
// Does NOT follow symlinks. Does NOT cross mount points.
|
|
1484
|
-
const SKIP_DIRS = new Set([
|
|
1485
|
-
'node_modules',
|
|
1486
|
-
'.git',
|
|
1487
|
-
'target', // Rust build
|
|
1488
|
-
'dist',
|
|
1489
|
-
'build',
|
|
1490
|
-
'.svelte-kit',
|
|
1491
|
-
'.next',
|
|
1492
|
-
'.cache',
|
|
1493
|
-
'coverage',
|
|
1494
|
-
'.turbo',
|
|
1495
|
-
'.nx',
|
|
1496
|
-
'__pycache__',
|
|
1497
|
-
'.venv',
|
|
1498
|
-
'venv',
|
|
1499
|
-
'.tox',
|
|
1500
|
-
'vendor',
|
|
1501
|
-
]);
|
|
1502
|
-
/**
|
|
1503
|
-
* Return the device number for a path, or -1 on error.
|
|
1504
|
-
* Used to detect filesystem boundary crossings.
|
|
1505
|
-
*/
|
|
1506
|
-
function getDevice(p) {
|
|
1507
|
-
try {
|
|
1508
|
-
return statSync(p).dev;
|
|
1509
|
-
}
|
|
1510
|
-
catch {
|
|
1511
|
-
return -1;
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
/**
|
|
1515
|
-
* Walk a directory tree looking for directories named `.cleo/`.
|
|
1516
|
-
* Candidates are returned as absolute parent directory paths (the project root).
|
|
1517
|
-
*
|
|
1518
|
-
* @param dir - Absolute directory path to walk.
|
|
1519
|
-
* @param depth - Current recursion depth (0 = root).
|
|
1520
|
-
* @param rootDev - Device number of the search root for boundary checks.
|
|
1521
|
-
*/
|
|
1522
|
-
function walkForCleo(dir, depth, rootDev) {
|
|
1523
|
-
if (depth > maxDepth)
|
|
1524
|
-
return [];
|
|
1525
|
-
let entries;
|
|
1526
|
-
try {
|
|
1527
|
-
// withFileTypes:true + default encoding returns Dirent<string>[]
|
|
1528
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1529
|
-
}
|
|
1530
|
-
catch {
|
|
1531
|
-
return [];
|
|
1532
|
-
}
|
|
1533
|
-
const found = [];
|
|
1534
|
-
for (const entry of entries) {
|
|
1535
|
-
// Only process directories; skip symlinks (security requirement).
|
|
1536
|
-
if (!entry.isDirectory())
|
|
1537
|
-
continue;
|
|
1538
|
-
if (entry.isSymbolicLink())
|
|
1539
|
-
continue;
|
|
1540
|
-
const fullPath = path.join(dir, entry.name);
|
|
1541
|
-
if (entry.name === '.cleo') {
|
|
1542
|
-
// Parent dir is a CLEO project root
|
|
1543
|
-
found.push(dir);
|
|
1544
|
-
// Do not recurse into .cleo/ itself
|
|
1545
|
-
continue;
|
|
1546
|
-
}
|
|
1547
|
-
if (SKIP_DIRS.has(entry.name))
|
|
1548
|
-
continue;
|
|
1549
|
-
// Filesystem boundary check — don't traverse into different mount points.
|
|
1550
|
-
const childDev = getDevice(fullPath);
|
|
1551
|
-
if (childDev !== rootDev && childDev !== -1)
|
|
1552
|
-
continue;
|
|
1553
|
-
const nested = walkForCleo(fullPath, depth + 1, rootDev);
|
|
1554
|
-
for (const n of nested)
|
|
1555
|
-
found.push(n);
|
|
1556
|
-
}
|
|
1557
|
-
return found;
|
|
1558
|
-
}
|
|
1559
|
-
// Collect all candidates
|
|
1560
|
-
const allCandidates = [];
|
|
1561
|
-
for (const root of roots) {
|
|
1562
|
-
const rootDev = getDevice(root);
|
|
1563
|
-
const found = walkForCleo(root, 0, rootDev);
|
|
1564
|
-
for (const f of found)
|
|
1565
|
-
allCandidates.push(f);
|
|
1566
|
-
}
|
|
1567
|
-
// Deduplicate (a root could itself be a .cleo parent)
|
|
1568
|
-
const candidates = [...new Set(allCandidates)];
|
|
1569
|
-
// ── Cross-reference with registry ─────────────────────────────────
|
|
1570
|
-
let registeredPaths = new Set();
|
|
1571
|
-
try {
|
|
1572
|
-
const { nexusList: listProjects } = await import('@cleocode/core/internal');
|
|
1573
|
-
const projects = await listProjects();
|
|
1574
|
-
for (const p of projects) {
|
|
1575
|
-
registeredPaths.add(path.resolve(p.path));
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
catch {
|
|
1579
|
-
// Registry unavailable — treat all as unregistered
|
|
1580
|
-
registeredPaths = new Set();
|
|
1581
|
-
}
|
|
1582
|
-
const unregistered = [];
|
|
1583
|
-
const registered = [];
|
|
1584
|
-
for (const candidate of candidates) {
|
|
1585
|
-
const resolved = path.resolve(candidate);
|
|
1586
|
-
if (registeredPaths.has(resolved)) {
|
|
1587
|
-
registered.push(resolved);
|
|
1588
|
-
}
|
|
1589
|
-
else {
|
|
1590
|
-
unregistered.push(resolved);
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
const tally = {
|
|
1594
|
-
total: candidates.length,
|
|
1595
|
-
unregistered: unregistered.length,
|
|
1596
|
-
registered: registered.length,
|
|
1597
|
-
};
|
|
1598
|
-
// ── Auto-register ─────────────────────────────────────────────────
|
|
1599
|
-
const autoRegistered = [];
|
|
1600
|
-
const autoRegisterErrors = [];
|
|
1601
|
-
if (autoRegister && unregistered.length > 0) {
|
|
1602
|
-
const { nexusRegister: doRegister } = await import('@cleocode/core/internal');
|
|
1603
|
-
for (const projectPath of unregistered) {
|
|
1604
|
-
try {
|
|
1605
|
-
await doRegister(projectPath);
|
|
1606
|
-
autoRegistered.push(projectPath);
|
|
1607
|
-
}
|
|
1608
|
-
catch (err) {
|
|
1609
|
-
autoRegisterErrors.push({
|
|
1610
|
-
path: projectPath,
|
|
1611
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
// ── Audit log ─────────────────────────────────────────────────────
|
|
1617
|
-
// Best-effort: never let audit failure surface to the user.
|
|
1618
|
-
try {
|
|
1619
|
-
const { getNexusDb } = await import('@cleocode/core/store/nexus-sqlite');
|
|
1620
|
-
const { nexusAuditLog: auditTable } = await import('@cleocode/core/store/nexus-schema');
|
|
1621
|
-
const { randomUUID } = await import('node:crypto');
|
|
1622
|
-
const db = await getNexusDb();
|
|
1623
|
-
await db.insert(auditTable).values({
|
|
1624
|
-
id: randomUUID(),
|
|
1625
|
-
action: 'projects.scan',
|
|
1626
|
-
domain: 'nexus',
|
|
1627
|
-
operation: 'projects.scan',
|
|
1628
|
-
success: 1,
|
|
1629
|
-
detailsJson: JSON.stringify({
|
|
1630
|
-
roots,
|
|
1631
|
-
found: candidates.length,
|
|
1632
|
-
unregistered: unregistered.length,
|
|
1633
|
-
registered: registered.length,
|
|
1634
|
-
autoRegistered: autoRegistered.length,
|
|
1635
|
-
}),
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
|
-
catch {
|
|
1639
|
-
// Audit failure is non-fatal
|
|
1640
|
-
}
|
|
1641
|
-
const durationMs = Date.now() - startTime;
|
|
1642
|
-
// ── Output ────────────────────────────────────────────────────────
|
|
1643
|
-
if (jsonOutput) {
|
|
1644
|
-
const data = {
|
|
1645
|
-
roots,
|
|
1646
|
-
unregistered,
|
|
1647
|
-
tally,
|
|
1648
|
-
};
|
|
1649
|
-
if (includeExisting)
|
|
1650
|
-
data['registered'] = registered;
|
|
1651
|
-
if (autoRegister) {
|
|
1652
|
-
data['autoRegistered'] = autoRegistered;
|
|
1653
|
-
data['autoRegisterErrors'] = autoRegisterErrors;
|
|
1654
|
-
}
|
|
1655
|
-
process.stdout.write(JSON.stringify({
|
|
1656
|
-
success: true,
|
|
1657
|
-
data,
|
|
1658
|
-
meta: {
|
|
1659
|
-
operation: 'nexus.projects.scan',
|
|
1660
|
-
duration_ms: durationMs,
|
|
1661
|
-
timestamp: new Date().toISOString(),
|
|
1662
|
-
},
|
|
1663
|
-
}, null, 2) + '\n');
|
|
1664
|
-
}
|
|
1665
|
-
else {
|
|
1666
|
-
// Human-readable output
|
|
1667
|
-
process.stdout.write(`\n[nexus] Scan complete — ${tally.total} project(s) found ` +
|
|
1668
|
-
`(${tally.unregistered} unregistered, ${tally.registered} registered)\n`);
|
|
1669
|
-
if (unregistered.length > 0) {
|
|
1670
|
-
process.stdout.write('\n Unregistered:\n');
|
|
1671
|
-
for (const p of unregistered) {
|
|
1672
|
-
process.stdout.write(` ${p}\n`);
|
|
1673
|
-
}
|
|
1674
|
-
if (!autoRegister) {
|
|
1675
|
-
process.stdout.write('\n Tip: run with --auto-register to register all of the above.\n');
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
if (includeExisting && registered.length > 0) {
|
|
1679
|
-
process.stdout.write('\n Already registered:\n');
|
|
1680
|
-
for (const p of registered) {
|
|
1681
|
-
process.stdout.write(` ${p}\n`);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
if (autoRegister) {
|
|
1685
|
-
process.stdout.write(`\n Auto-registered: ${autoRegistered.length} project(s)` +
|
|
1686
|
-
(autoRegisterErrors.length > 0 ? `, ${autoRegisterErrors.length} failed` : '') +
|
|
1687
|
-
'\n');
|
|
1688
|
-
for (const e of autoRegisterErrors) {
|
|
1689
|
-
process.stdout.write(` FAILED ${e.path}: ${e.error}\n`);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
});
|
|
1694
|
-
// ── nexus projects clean ─────────────────────────────────────────────────
|
|
1695
|
-
// Bulk purge project_registry rows matching path criteria.
|
|
1696
|
-
// Provides safety rails: at least one criteria flag required, always shows
|
|
1697
|
-
// a preview before deletion, runs deletion in a single transaction.
|
|
1698
|
-
projects
|
|
1699
|
-
.command('clean')
|
|
1700
|
-
.description('Bulk purge project_registry rows matching path criteria (requires at least one filter flag)')
|
|
1701
|
-
.option('--dry-run', 'List matching projects without deleting anything')
|
|
1702
|
-
.option('--pattern <regex>', 'JS regex matched against project_path')
|
|
1703
|
-
.option('--include-temp', 'Preset: match paths containing a .temp/ segment')
|
|
1704
|
-
.option('--include-tests', 'Preset: match paths containing tmp/test/fixture/scratch/sandbox segments')
|
|
1705
|
-
.option('--unhealthy', 'Also match rows where health_status is "unhealthy"')
|
|
1706
|
-
.option('--never-indexed', 'Also match rows where last_indexed IS NULL')
|
|
1707
|
-
.option('--yes', 'Skip confirmation prompt (still shows preview)')
|
|
1708
|
-
.option('--json', 'Output as JSON (LAFS envelope format)')
|
|
1709
|
-
.action(async (opts) => {
|
|
1710
|
-
const startTime = Date.now();
|
|
1711
|
-
const jsonOutput = !!opts['json'];
|
|
1712
|
-
const dryRun = !!opts['dryRun'];
|
|
1713
|
-
const skipPrompt = !!opts['yes'];
|
|
1714
|
-
const patternRaw = opts['pattern'];
|
|
1715
|
-
const includeTemp = !!opts['includeTemp'];
|
|
1716
|
-
const includeTests = !!opts['includeTests'];
|
|
1717
|
-
const matchUnhealthy = !!opts['unhealthy'];
|
|
1718
|
-
const matchNeverIndexed = !!opts['neverIndexed'];
|
|
1719
|
-
// Require at least one real criteria flag (not just --dry-run / --yes / --json)
|
|
1720
|
-
const hasCriteria = patternRaw !== undefined ||
|
|
1721
|
-
includeTemp ||
|
|
1722
|
-
includeTests ||
|
|
1723
|
-
matchUnhealthy ||
|
|
1724
|
-
matchNeverIndexed;
|
|
1725
|
-
if (!hasCriteria) {
|
|
1726
|
-
const errMsg = 'No filter criteria provided. Refusing to purge all projects without explicit criteria.\n' +
|
|
1727
|
-
'Use at least one of: --pattern <regex>, --include-temp, --include-tests, --unhealthy, --never-indexed';
|
|
1728
|
-
if (jsonOutput) {
|
|
1729
|
-
process.stdout.write(JSON.stringify({
|
|
1730
|
-
success: false,
|
|
1731
|
-
error: { code: 'E_NO_CRITERIA', message: errMsg },
|
|
1732
|
-
meta: {
|
|
1733
|
-
operation: 'nexus.projects.clean',
|
|
1734
|
-
duration_ms: Date.now() - startTime,
|
|
1735
|
-
timestamp: new Date().toISOString(),
|
|
1736
|
-
},
|
|
1737
|
-
}, null, 2) + '\n');
|
|
1738
|
-
}
|
|
1739
|
-
else {
|
|
1740
|
-
process.stderr.write(`[nexus] Error: ${errMsg}\n`);
|
|
1741
|
-
}
|
|
1742
|
-
process.exitCode = 6;
|
|
1743
|
-
return;
|
|
1744
|
-
}
|
|
1745
|
-
// Compile user-supplied regex (if any)
|
|
1746
|
-
let patternRegex = null;
|
|
1747
|
-
if (patternRaw !== undefined) {
|
|
1748
|
-
try {
|
|
1749
|
-
patternRegex = new RegExp(patternRaw);
|
|
1750
|
-
}
|
|
1751
|
-
catch (err) {
|
|
1752
|
-
const msg = `Invalid --pattern regex: ${err instanceof Error ? err.message : String(err)}`;
|
|
1753
|
-
if (jsonOutput) {
|
|
1754
|
-
process.stdout.write(JSON.stringify({
|
|
1755
|
-
success: false,
|
|
1756
|
-
error: { code: 'E_INVALID_PATTERN', message: msg },
|
|
1757
|
-
meta: {
|
|
1758
|
-
operation: 'nexus.projects.clean',
|
|
1759
|
-
duration_ms: Date.now() - startTime,
|
|
1760
|
-
timestamp: new Date().toISOString(),
|
|
1761
|
-
},
|
|
1762
|
-
}, null, 2) + '\n');
|
|
1763
|
-
}
|
|
1764
|
-
else {
|
|
1765
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1766
|
-
}
|
|
1767
|
-
process.exitCode = 6;
|
|
1768
|
-
return;
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
// Preset regexes
|
|
1772
|
-
const TEMP_RE = /(^|\/)\.temp(\/|$)/;
|
|
1773
|
-
const TESTS_RE = /(^|\/)(tmp|test|fixture|scratch|sandbox)(\/|$)/;
|
|
1774
|
-
/**
|
|
1775
|
-
* Return true if a project_path matches any of the active criteria.
|
|
1776
|
-
*/
|
|
1777
|
-
function matchesCriteria(projectPath, healthStatus, lastIndexed) {
|
|
1778
|
-
if (patternRegex?.test(projectPath))
|
|
1779
|
-
return true;
|
|
1780
|
-
if (includeTemp && TEMP_RE.test(projectPath))
|
|
1781
|
-
return true;
|
|
1782
|
-
if (includeTests && TESTS_RE.test(projectPath))
|
|
1783
|
-
return true;
|
|
1784
|
-
if (matchUnhealthy && healthStatus === 'unhealthy')
|
|
1785
|
-
return true;
|
|
1786
|
-
if (matchNeverIndexed && lastIndexed === null)
|
|
1787
|
-
return true;
|
|
1788
|
-
return false;
|
|
1789
|
-
}
|
|
1790
|
-
try {
|
|
1791
|
-
const { getNexusDb } = await import('@cleocode/core/store/nexus-sqlite');
|
|
1792
|
-
const { projectRegistry: regTable, nexusAuditLog: auditTable } = await import('@cleocode/core/store/nexus-schema');
|
|
1793
|
-
const { randomUUID } = await import('node:crypto');
|
|
1794
|
-
const { inArray } = await import('drizzle-orm');
|
|
1795
|
-
const db = await getNexusDb();
|
|
1796
|
-
// Fetch all registry rows in one query — avoid N+1.
|
|
1797
|
-
// Cast is required because dynamic imports with `as string` suppress
|
|
1798
|
-
// the real module types; the actual schema column types match this shape.
|
|
1799
|
-
const allRows = (await db
|
|
1800
|
-
.select({
|
|
1801
|
-
projectId: regTable.projectId,
|
|
1802
|
-
projectPath: regTable.projectPath,
|
|
1803
|
-
healthStatus: regTable.healthStatus,
|
|
1804
|
-
lastIndexed: regTable.lastIndexed,
|
|
1805
|
-
})
|
|
1806
|
-
.from(regTable));
|
|
1807
|
-
const matches = allRows.filter((row) => matchesCriteria(row.projectPath, row.healthStatus, row.lastIndexed));
|
|
1808
|
-
const totalCount = allRows.length;
|
|
1809
|
-
const matchCount = matches.length;
|
|
1810
|
-
const samplePaths = matches.slice(0, 10).map((r) => r.projectPath);
|
|
1811
|
-
// Always show preview
|
|
1812
|
-
if (!jsonOutput) {
|
|
1813
|
-
process.stdout.write(`[nexus] Clean preview — ${matchCount} project(s) of ${totalCount} total match criteria:\n`);
|
|
1814
|
-
if (matchCount === 0) {
|
|
1815
|
-
process.stdout.write(' (no matches)\n');
|
|
1816
|
-
}
|
|
1817
|
-
else {
|
|
1818
|
-
for (const p of samplePaths) {
|
|
1819
|
-
process.stdout.write(` ${p}\n`);
|
|
1820
|
-
}
|
|
1821
|
-
if (matchCount > 10) {
|
|
1822
|
-
process.stdout.write(` ... and ${matchCount - 10} more\n`);
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
if (matchCount === 0) {
|
|
1827
|
-
const durationMs = Date.now() - startTime;
|
|
1828
|
-
if (jsonOutput) {
|
|
1829
|
-
process.stdout.write(JSON.stringify({
|
|
1830
|
-
success: true,
|
|
1831
|
-
data: {
|
|
1832
|
-
dryRun,
|
|
1833
|
-
matched: 0,
|
|
1834
|
-
purged: 0,
|
|
1835
|
-
remaining: totalCount,
|
|
1836
|
-
sample: [],
|
|
1837
|
-
},
|
|
1838
|
-
meta: {
|
|
1839
|
-
operation: 'nexus.projects.clean',
|
|
1840
|
-
duration_ms: durationMs,
|
|
1841
|
-
timestamp: new Date().toISOString(),
|
|
1842
|
-
},
|
|
1843
|
-
}, null, 2) + '\n');
|
|
1844
|
-
}
|
|
1845
|
-
return;
|
|
1846
|
-
}
|
|
1847
|
-
// Dry-run: stop here
|
|
1848
|
-
if (dryRun) {
|
|
1849
|
-
const durationMs = Date.now() - startTime;
|
|
1850
|
-
if (jsonOutput) {
|
|
1851
|
-
process.stdout.write(JSON.stringify({
|
|
1852
|
-
success: true,
|
|
1853
|
-
data: {
|
|
1854
|
-
dryRun: true,
|
|
1855
|
-
matched: matchCount,
|
|
1856
|
-
purged: 0,
|
|
1857
|
-
remaining: totalCount,
|
|
1858
|
-
sample: samplePaths,
|
|
1859
|
-
},
|
|
1860
|
-
meta: {
|
|
1861
|
-
operation: 'nexus.projects.clean',
|
|
1862
|
-
duration_ms: durationMs,
|
|
1863
|
-
timestamp: new Date().toISOString(),
|
|
1864
|
-
},
|
|
1865
|
-
}, null, 2) + '\n');
|
|
1866
|
-
}
|
|
1867
|
-
else {
|
|
1868
|
-
process.stdout.write(`[nexus] Dry-run — ${matchCount} project(s) would be purged. Rerun without --dry-run to delete.\n`);
|
|
1869
|
-
}
|
|
1870
|
-
return;
|
|
1871
|
-
}
|
|
1872
|
-
// Confirmation prompt (skip with --yes)
|
|
1873
|
-
if (!skipPrompt) {
|
|
1874
|
-
const { createInterface } = await import('node:readline');
|
|
1875
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1876
|
-
const confirmed = await new Promise((resolve) => {
|
|
1877
|
-
rl.question(`\n[nexus] Delete ${matchCount} project(s) from the registry? [y/N] `, (answer) => {
|
|
1878
|
-
rl.close();
|
|
1879
|
-
resolve(answer.trim().toLowerCase() === 'y');
|
|
1880
|
-
});
|
|
1881
|
-
});
|
|
1882
|
-
if (!confirmed) {
|
|
1883
|
-
process.stdout.write('[nexus] Aborted — no projects deleted.\n');
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
// Delete in a single transaction
|
|
1888
|
-
const idsToDelete = matches.map((r) => r.projectId);
|
|
1889
|
-
await db.delete(regTable).where(inArray(regTable.projectId, idsToDelete));
|
|
1890
|
-
const remaining = totalCount - matchCount;
|
|
1891
|
-
// Audit log (best-effort)
|
|
1892
|
-
try {
|
|
1893
|
-
await db.insert(auditTable).values({
|
|
1894
|
-
id: randomUUID(),
|
|
1895
|
-
action: 'projects.clean',
|
|
1896
|
-
domain: 'nexus',
|
|
1897
|
-
operation: 'projects.clean',
|
|
1898
|
-
success: 1,
|
|
1899
|
-
detailsJson: JSON.stringify({
|
|
1900
|
-
pattern: patternRaw ?? null,
|
|
1901
|
-
presets: {
|
|
1902
|
-
includeTemp,
|
|
1903
|
-
includeTests,
|
|
1904
|
-
matchUnhealthy,
|
|
1905
|
-
matchNeverIndexed,
|
|
1906
|
-
},
|
|
1907
|
-
count: matchCount,
|
|
1908
|
-
sample: samplePaths,
|
|
1909
|
-
}),
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
catch {
|
|
1913
|
-
// Audit failure is non-fatal
|
|
1914
|
-
}
|
|
1915
|
-
const durationMs = Date.now() - startTime;
|
|
1916
|
-
if (jsonOutput) {
|
|
1917
|
-
process.stdout.write(JSON.stringify({
|
|
1918
|
-
success: true,
|
|
1919
|
-
data: {
|
|
1920
|
-
dryRun: false,
|
|
1921
|
-
matched: matchCount,
|
|
1922
|
-
purged: matchCount,
|
|
1923
|
-
remaining,
|
|
1924
|
-
sample: samplePaths,
|
|
1925
|
-
},
|
|
1926
|
-
meta: {
|
|
1927
|
-
operation: 'nexus.projects.clean',
|
|
1928
|
-
duration_ms: durationMs,
|
|
1929
|
-
timestamp: new Date().toISOString(),
|
|
1930
|
-
},
|
|
1931
|
-
}, null, 2) + '\n');
|
|
1932
|
-
}
|
|
1933
|
-
else {
|
|
1934
|
-
process.stdout.write(`[nexus] Purged ${matchCount} project(s). ${remaining} project(s) remaining in registry.\n`);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
catch (err) {
|
|
1938
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1939
|
-
const durationMs = Date.now() - startTime;
|
|
1940
|
-
if (jsonOutput) {
|
|
1941
|
-
process.stdout.write(JSON.stringify({
|
|
1942
|
-
success: false,
|
|
1943
|
-
error: { code: 'E_CLEAN_FAILED', message: msg },
|
|
1944
|
-
meta: {
|
|
1945
|
-
operation: 'nexus.projects.clean',
|
|
1946
|
-
duration_ms: durationMs,
|
|
1947
|
-
timestamp: new Date().toISOString(),
|
|
1948
|
-
},
|
|
1949
|
-
}, null, 2) + '\n');
|
|
1950
|
-
}
|
|
1951
|
-
else {
|
|
1952
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
1953
|
-
}
|
|
1954
|
-
process.exitCode = 1;
|
|
1955
|
-
}
|
|
1956
|
-
});
|
|
1957
|
-
// ── nexus refresh-bridge ──────────────────────────────────────────────────
|
|
1958
|
-
nexus
|
|
1959
|
-
.command('refresh-bridge [path]')
|
|
1960
|
-
.description('Regenerate .cleo/nexus-bridge.md from the existing nexus.db index (does not re-index)')
|
|
1961
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
1962
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from path)')
|
|
1963
|
-
.action(async (targetPath, opts) => {
|
|
1964
|
-
const startTime = Date.now();
|
|
1965
|
-
const jsonOutput = !!opts['json'];
|
|
1966
|
-
const projectIdOverride = opts['projectId'];
|
|
1967
|
-
const repoPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
1968
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
1969
|
-
try {
|
|
1970
|
-
const { writeNexusBridge } = await import('@cleocode/core/internal');
|
|
1971
|
-
const result = await writeNexusBridge(repoPath, projectId);
|
|
1972
|
-
const durationMs = Date.now() - startTime;
|
|
1973
|
-
if (jsonOutput) {
|
|
1974
|
-
process.stdout.write(JSON.stringify({
|
|
1975
|
-
success: true,
|
|
1976
|
-
data: { path: result.path, written: result.written, projectId, repoPath },
|
|
1977
|
-
meta: {
|
|
1978
|
-
operation: 'nexus.refresh-bridge',
|
|
1979
|
-
duration_ms: durationMs,
|
|
1980
|
-
timestamp: new Date().toISOString(),
|
|
1981
|
-
},
|
|
1982
|
-
}, null, 2) + '\n');
|
|
1983
|
-
}
|
|
1984
|
-
else if (result.written) {
|
|
1985
|
-
process.stdout.write(`[nexus] nexus-bridge.md refreshed at ${result.path}\n`);
|
|
1986
|
-
}
|
|
1987
|
-
else {
|
|
1988
|
-
process.stdout.write(`[nexus] nexus-bridge.md unchanged at ${result.path}\n`);
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
catch (err) {
|
|
1992
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1993
|
-
if (jsonOutput) {
|
|
1994
|
-
process.stdout.write(JSON.stringify({
|
|
1995
|
-
success: false,
|
|
1996
|
-
error: { code: 'E_BRIDGE_FAILED', message: msg },
|
|
1997
|
-
meta: {
|
|
1998
|
-
operation: 'nexus.refresh-bridge',
|
|
1999
|
-
duration_ms: Date.now() - startTime,
|
|
2000
|
-
timestamp: new Date().toISOString(),
|
|
2001
|
-
},
|
|
2002
|
-
}, null, 2) + '\n');
|
|
2003
|
-
}
|
|
2004
|
-
else {
|
|
2005
|
-
process.stderr.write(`[nexus] Error refreshing bridge: ${msg}\n`);
|
|
2006
|
-
}
|
|
2007
|
-
process.exitCode = 1;
|
|
2008
|
-
}
|
|
2009
|
-
});
|
|
2010
|
-
// ── nexus export ──────────────────────────────────────────────────────────
|
|
2011
|
-
/**
|
|
2012
|
-
* Export nexus graph to GEXF (Gephi) or JSON format.
|
|
2013
|
-
*
|
|
2014
|
-
* Queries graph_nodes and graph_edges from nexus.db and emits GEXF format
|
|
2015
|
-
* suitable for visualization in Gephi. Supports optional project filtering.
|
|
2016
|
-
*
|
|
2017
|
-
* @task T626-M7
|
|
2018
|
-
*/
|
|
2019
|
-
nexus
|
|
2020
|
-
.command('export')
|
|
2021
|
-
.description('Export nexus graph to GEXF (Gephi) or JSON format')
|
|
2022
|
-
.option('--format <format>', 'Output format: gexf, json', 'gexf')
|
|
2023
|
-
.option('--output <file>', 'Output file path (stdout if omitted)')
|
|
2024
|
-
.option('--project <id>', 'Filter by project ID (exports all projects if omitted)')
|
|
2025
|
-
.action(async (opts) => {
|
|
2026
|
-
const startTime = Date.now();
|
|
2027
|
-
const format = opts['format'] ?? 'gexf';
|
|
2028
|
-
const outputFile = opts['output'];
|
|
2029
|
-
const projectFilter = opts['project'];
|
|
2030
|
-
try {
|
|
2031
|
-
const { getNexusDb, nexusSchema } = await import('@cleocode/core/store/nexus-sqlite');
|
|
2032
|
-
const db = await getNexusDb();
|
|
2033
|
-
// Load all nodes and relations
|
|
2034
|
-
let allNodes = [];
|
|
2035
|
-
let allRelations = [];
|
|
2036
|
-
try {
|
|
2037
|
-
allNodes = db.select().from(nexusSchema.nexusNodes).all();
|
|
2038
|
-
allRelations = db.select().from(nexusSchema.nexusRelations).all();
|
|
2039
|
-
}
|
|
2040
|
-
catch {
|
|
2041
|
-
// DB may be empty
|
|
2042
|
-
}
|
|
2043
|
-
// Filter by project if specified
|
|
2044
|
-
const nodes = projectFilter
|
|
2045
|
-
? allNodes.filter((n) => n['projectId'] === projectFilter)
|
|
2046
|
-
: allNodes;
|
|
2047
|
-
const relations = projectFilter
|
|
2048
|
-
? allRelations.filter((r) => r['projectId'] === projectFilter)
|
|
2049
|
-
: allRelations;
|
|
2050
|
-
let output = '';
|
|
2051
|
-
if (format === 'json') {
|
|
2052
|
-
output = JSON.stringify({
|
|
2053
|
-
nodes: nodes.map((n) => ({
|
|
2054
|
-
id: n['id'],
|
|
2055
|
-
kind: n['kind'],
|
|
2056
|
-
label: n['label'],
|
|
2057
|
-
name: n['name'],
|
|
2058
|
-
filePath: n['filePath'],
|
|
2059
|
-
language: n['language'],
|
|
2060
|
-
isExported: n['isExported'],
|
|
2061
|
-
startLine: n['startLine'],
|
|
2062
|
-
endLine: n['endLine'],
|
|
2063
|
-
projectId: n['projectId'],
|
|
2064
|
-
})),
|
|
2065
|
-
edges: relations.map((r) => ({
|
|
2066
|
-
id: r['id'],
|
|
2067
|
-
source: r['sourceId'],
|
|
2068
|
-
target: r['targetId'],
|
|
2069
|
-
type: r['type'],
|
|
2070
|
-
confidence: r['confidence'],
|
|
2071
|
-
reason: r['reason'],
|
|
2072
|
-
})),
|
|
2073
|
-
}, null, 2);
|
|
2074
|
-
}
|
|
2075
|
-
else if (format === 'gexf') {
|
|
2076
|
-
// GEXF format (Gephi standard)
|
|
2077
|
-
output = generateGexf(nodes, relations);
|
|
2078
|
-
}
|
|
2079
|
-
else {
|
|
2080
|
-
process.stderr.write(`[nexus] Error: Unknown format '${format}'. Supported: gexf, json\n`);
|
|
2081
|
-
process.exitCode = 1;
|
|
2082
|
-
return;
|
|
2083
|
-
}
|
|
2084
|
-
if (outputFile) {
|
|
2085
|
-
const { writeFileSync } = await import('node:fs');
|
|
2086
|
-
writeFileSync(outputFile, output, 'utf-8');
|
|
2087
|
-
process.stdout.write(`[nexus] Exported to ${outputFile} (${nodes.length} nodes, ${relations.length} edges)\n`);
|
|
2088
|
-
}
|
|
2089
|
-
else {
|
|
2090
|
-
process.stdout.write(output);
|
|
2091
|
-
if (!output.endsWith('\n'))
|
|
2092
|
-
process.stdout.write('\n');
|
|
2093
|
-
}
|
|
2094
|
-
const durationMs = Date.now() - startTime;
|
|
2095
|
-
if (outputFile) {
|
|
2096
|
-
process.stderr.write(`[nexus] Export completed in ${durationMs}ms\n`);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
catch (err) {
|
|
2100
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2101
|
-
process.stderr.write(`[nexus] Error: ${msg}\n`);
|
|
2102
|
-
process.exitCode = 1;
|
|
2103
|
-
}
|
|
2104
|
-
});
|
|
2105
|
-
// ── nexus diff ────────────────────────────────────────────────────────────
|
|
2106
|
-
/**
|
|
2107
|
-
* Compare NEXUS index state between two git commits.
|
|
2108
|
-
*
|
|
2109
|
-
* Runs an incremental re-analysis against the current working tree state
|
|
2110
|
-
* (representing the "after" snapshot) and compares relation/node counts
|
|
2111
|
-
* against the pre-analysis snapshot. Reports new relations, removed
|
|
2112
|
-
* relations, and any regressions detected.
|
|
2113
|
-
*
|
|
2114
|
-
* @task T625
|
|
2115
|
-
*/
|
|
2116
|
-
nexus
|
|
2117
|
-
.command('diff')
|
|
2118
|
-
.description('Compare NEXUS index state between two git commits — shows new/removed relations and broken call chains')
|
|
2119
|
-
.option('--before <sha>', 'Git SHA or ref for the "before" snapshot (default: HEAD~1)')
|
|
2120
|
-
.option('--after <sha>', 'Git SHA or ref for the "after" snapshot (default: HEAD)', 'HEAD')
|
|
2121
|
-
.option('--path <dir>', 'Repository directory to analyze (default: cwd)')
|
|
2122
|
-
.option('--json', 'Output result as JSON (LAFS envelope format)')
|
|
2123
|
-
.option('--project-id <id>', 'Override the project ID (default: auto-detected from path)')
|
|
2124
|
-
.action(async (opts) => {
|
|
2125
|
-
const startTime = Date.now();
|
|
2126
|
-
const jsonOutput = !!opts['json'];
|
|
2127
|
-
const repoPath = opts['path'] ? path.resolve(opts['path']) : process.cwd();
|
|
2128
|
-
const projectIdOverride = opts['projectId'];
|
|
2129
|
-
const projectId = projectIdOverride ?? Buffer.from(repoPath).toString('base64url').slice(0, 32);
|
|
2130
|
-
const beforeRef = opts['before'] ?? 'HEAD~1';
|
|
2131
|
-
const afterRef = opts['after'] ?? 'HEAD';
|
|
2132
|
-
if (!jsonOutput) {
|
|
2133
|
-
process.stderr.write(`[nexus] Diffing relations: ${beforeRef}..${afterRef} in ${repoPath}\n`);
|
|
2134
|
-
}
|
|
2135
|
-
try {
|
|
2136
|
-
const { execFile: execFileNode } = await import('node:child_process');
|
|
2137
|
-
const { promisify } = await import('node:util');
|
|
2138
|
-
const execFileAsync = promisify(execFileNode);
|
|
2139
|
-
/** Resolve a git ref to a short SHA. Falls back to the ref itself on failure. */
|
|
2140
|
-
const resolveSha = async (ref) => {
|
|
2141
|
-
try {
|
|
2142
|
-
const { stdout } = await execFileAsync('git', ['rev-parse', '--short', ref], {
|
|
2143
|
-
timeout: 5_000,
|
|
2144
|
-
cwd: repoPath,
|
|
2145
|
-
});
|
|
2146
|
-
return stdout.trim();
|
|
2147
|
-
}
|
|
2148
|
-
catch {
|
|
2149
|
-
return ref;
|
|
2150
|
-
}
|
|
2151
|
-
};
|
|
2152
|
-
const [beforeSha, afterSha] = await Promise.all([
|
|
2153
|
-
resolveSha(beforeRef),
|
|
2154
|
-
resolveSha(afterRef),
|
|
2155
|
-
]);
|
|
2156
|
-
// Get files changed between the two refs (TypeScript/JavaScript/Rust only)
|
|
2157
|
-
let changedFiles = [];
|
|
2158
|
-
try {
|
|
2159
|
-
const { stdout: diffOutput } = await execFileAsync('git', ['diff', '--name-only', beforeSha, afterSha], { timeout: 10_000, cwd: repoPath });
|
|
2160
|
-
changedFiles = diffOutput
|
|
2161
|
-
.split('\n')
|
|
2162
|
-
.map((f) => f.trim())
|
|
2163
|
-
.filter((f) => f.length > 0 && (f.endsWith('.ts') || f.endsWith('.js') || f.endsWith('.rs')));
|
|
2164
|
-
}
|
|
2165
|
-
catch {
|
|
2166
|
-
// git diff failed — proceed with full status comparison
|
|
2167
|
-
}
|
|
2168
|
-
// Load nexus DB and snapshot relation/node counts before incremental analysis
|
|
2169
|
-
const { getNexusDb, nexusSchema } = await import('@cleocode/core/store/nexus-sqlite');
|
|
2170
|
-
const db = await getNexusDb();
|
|
2171
|
-
let relationsBefore = 0;
|
|
2172
|
-
let nodesBefore = 0;
|
|
2173
|
-
try {
|
|
2174
|
-
const allRelsBefore = db.select().from(nexusSchema.nexusRelations).all();
|
|
2175
|
-
const allNodesBefore = db.select().from(nexusSchema.nexusNodes).all();
|
|
2176
|
-
relationsBefore = allRelsBefore.filter((r) => r['projectId'] === projectId).length;
|
|
2177
|
-
nodesBefore = allNodesBefore.filter((n) => n['projectId'] === projectId).length;
|
|
2178
|
-
}
|
|
2179
|
-
catch {
|
|
2180
|
-
// DB not yet initialized — start from zero
|
|
2181
|
-
}
|
|
2182
|
-
// Run incremental pipeline to reflect the afterRef state
|
|
2183
|
-
const { runPipeline } = await import('@cleocode/nexus/pipeline');
|
|
2184
|
-
const pipelineResult = await runPipeline(repoPath, projectId, db, {
|
|
2185
|
-
nexusNodes: nexusSchema.nexusNodes,
|
|
2186
|
-
nexusRelations: nexusSchema.nexusRelations,
|
|
2187
|
-
}, undefined, // no progress callback in diff mode
|
|
2188
|
-
{ incremental: true });
|
|
2189
|
-
// Snapshot counts after incremental analysis
|
|
2190
|
-
let relationsAfter = 0;
|
|
2191
|
-
let nodesAfter = 0;
|
|
2192
|
-
try {
|
|
2193
|
-
const allRelsAfter = db.select().from(nexusSchema.nexusRelations).all();
|
|
2194
|
-
const allNodesAfter = db.select().from(nexusSchema.nexusNodes).all();
|
|
2195
|
-
relationsAfter = allRelsAfter.filter((r) => r['projectId'] === projectId).length;
|
|
2196
|
-
nodesAfter = allNodesAfter.filter((n) => n['projectId'] === projectId).length;
|
|
2197
|
-
}
|
|
2198
|
-
catch {
|
|
2199
|
-
// Fallback: use pipeline result counts directly
|
|
2200
|
-
relationsAfter = pipelineResult.relationCount;
|
|
2201
|
-
nodesAfter = pipelineResult.nodeCount;
|
|
2202
|
-
}
|
|
2203
|
-
const newRelations = Math.max(0, relationsAfter - relationsBefore);
|
|
2204
|
-
const removedRelations = Math.max(0, relationsBefore - relationsAfter);
|
|
2205
|
-
const newNodes = Math.max(0, nodesAfter - nodesBefore);
|
|
2206
|
-
const removedNodes = Math.max(0, nodesBefore - nodesAfter);
|
|
2207
|
-
const durationMs = Date.now() - startTime;
|
|
2208
|
-
// Classify regressions: significant relation or node loss
|
|
2209
|
-
const regressions = [];
|
|
2210
|
-
if (removedRelations > 5) {
|
|
2211
|
-
regressions.push(`${removedRelations} relations removed — verify no broken call chains`);
|
|
2212
|
-
}
|
|
2213
|
-
if (removedNodes > 0) {
|
|
2214
|
-
regressions.push(`${removedNodes} symbols removed — callers may be broken`);
|
|
2215
|
-
}
|
|
2216
|
-
const diffHealthStatus = regressions.length > 0
|
|
2217
|
-
? 'REGRESSIONS_DETECTED'
|
|
2218
|
-
: removedRelations > 0
|
|
2219
|
-
? 'RELATIONS_REDUCED'
|
|
2220
|
-
: newRelations > 0
|
|
2221
|
-
? 'RELATIONS_ADDED'
|
|
2222
|
-
: 'STABLE';
|
|
2223
|
-
if (jsonOutput) {
|
|
2224
|
-
process.stdout.write(JSON.stringify({
|
|
2225
|
-
success: true,
|
|
2226
|
-
data: {
|
|
2227
|
-
beforeRef,
|
|
2228
|
-
afterRef,
|
|
2229
|
-
beforeSha,
|
|
2230
|
-
afterSha,
|
|
2231
|
-
projectId,
|
|
2232
|
-
repoPath,
|
|
2233
|
-
changedFiles,
|
|
2234
|
-
nodesBefore,
|
|
2235
|
-
nodesAfter,
|
|
2236
|
-
newNodes,
|
|
2237
|
-
removedNodes,
|
|
2238
|
-
relationsBefore,
|
|
2239
|
-
relationsAfter,
|
|
2240
|
-
newRelations,
|
|
2241
|
-
removedRelations,
|
|
2242
|
-
healthStatus: diffHealthStatus,
|
|
2243
|
-
regressions,
|
|
2244
|
-
},
|
|
2245
|
-
meta: {
|
|
2246
|
-
operation: 'nexus.diff',
|
|
2247
|
-
duration_ms: durationMs,
|
|
2248
|
-
timestamp: new Date().toISOString(),
|
|
2249
|
-
},
|
|
2250
|
-
}, null, 2) + '\n');
|
|
2251
|
-
}
|
|
2252
|
-
else {
|
|
2253
|
-
process.stdout.write(`[nexus] Diff: ${beforeSha}..${afterSha}\n` +
|
|
2254
|
-
` Changed files: ${changedFiles.length > 0 ? changedFiles.join(', ') : 'n/a'}\n` +
|
|
2255
|
-
` Nodes: before=${nodesBefore} after=${nodesAfter} new=+${newNodes} removed=-${removedNodes}\n` +
|
|
2256
|
-
` Relations: before=${relationsBefore} after=${relationsAfter} new=+${newRelations} removed=-${removedRelations}\n` +
|
|
2257
|
-
` Health: ${diffHealthStatus}\n`);
|
|
2258
|
-
if (regressions.length > 0) {
|
|
2259
|
-
process.stdout.write('\n REGRESSIONS:\n');
|
|
2260
|
-
for (const reg of regressions) {
|
|
2261
|
-
process.stdout.write(` - ${reg}\n`);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
else {
|
|
2265
|
-
process.stdout.write(' No regressions detected.\n');
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
catch (err) {
|
|
2270
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2271
|
-
const durationMs = Date.now() - startTime;
|
|
2272
|
-
if (jsonOutput) {
|
|
2273
|
-
process.stdout.write(JSON.stringify({
|
|
2274
|
-
success: false,
|
|
2275
|
-
error: { code: 'E_DIFF_FAILED', message: msg },
|
|
2276
|
-
meta: {
|
|
2277
|
-
operation: 'nexus.diff',
|
|
2278
|
-
duration_ms: durationMs,
|
|
2279
|
-
timestamp: new Date().toISOString(),
|
|
2280
|
-
},
|
|
2281
|
-
}, null, 2) + '\n');
|
|
2282
|
-
}
|
|
2283
|
-
else {
|
|
2284
|
-
process.stderr.write(`[nexus] Error running diff: ${msg}\n`);
|
|
2285
|
-
}
|
|
2286
|
-
process.exitCode = 1;
|
|
2287
|
-
}
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
//# sourceMappingURL=nexus.js.map
|