@geraldmaron/construct 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +69 -0
- package/LICENSE +21 -0
- package/README.md +344 -0
- package/agents/prompts/cx-accessibility.md +29 -0
- package/agents/prompts/cx-ai-engineer.md +31 -0
- package/agents/prompts/cx-architect.md +40 -0
- package/agents/prompts/cx-business-strategist.md +25 -0
- package/agents/prompts/cx-data-analyst.md +30 -0
- package/agents/prompts/cx-data-engineer.md +32 -0
- package/agents/prompts/cx-debugger.md +27 -0
- package/agents/prompts/cx-designer.md +33 -0
- package/agents/prompts/cx-devil-advocate.md +36 -0
- package/agents/prompts/cx-docs-keeper.md +50 -0
- package/agents/prompts/cx-engineer.md +36 -0
- package/agents/prompts/cx-evaluator.md +26 -0
- package/agents/prompts/cx-explorer.md +38 -0
- package/agents/prompts/cx-legal-compliance.md +25 -0
- package/agents/prompts/cx-operations.md +28 -0
- package/agents/prompts/cx-orchestrator.md +30 -0
- package/agents/prompts/cx-platform-engineer.md +25 -0
- package/agents/prompts/cx-product-manager.md +30 -0
- package/agents/prompts/cx-qa.md +42 -0
- package/agents/prompts/cx-rd-lead.md +25 -0
- package/agents/prompts/cx-release-manager.md +31 -0
- package/agents/prompts/cx-researcher.md +31 -0
- package/agents/prompts/cx-reviewer.md +36 -0
- package/agents/prompts/cx-security.md +37 -0
- package/agents/prompts/cx-sre.md +26 -0
- package/agents/prompts/cx-test-automation.md +35 -0
- package/agents/prompts/cx-trace-reviewer.md +76 -0
- package/agents/prompts/cx-ux-researcher.md +30 -0
- package/agents/registry.json +879 -0
- package/agents/teams.json +94 -0
- package/bin/construct +613 -0
- package/commands/build/feature.md +21 -0
- package/commands/build/fix.md +20 -0
- package/commands/design/access.md +19 -0
- package/commands/design/flow.md +16 -0
- package/commands/design/ui.md +18 -0
- package/commands/measure/experiment.md +18 -0
- package/commands/measure/metrics.md +18 -0
- package/commands/measure/results.md +18 -0
- package/commands/plan/api.md +19 -0
- package/commands/plan/challenge.md +18 -0
- package/commands/plan/decide.md +20 -0
- package/commands/plan/feature.md +49 -0
- package/commands/plan/requirements.md +24 -0
- package/commands/remember/context.md +26 -0
- package/commands/remember/handoff.md +19 -0
- package/commands/remember/runbook.md +20 -0
- package/commands/review/code.md +21 -0
- package/commands/review/quality.md +19 -0
- package/commands/review/security.md +16 -0
- package/commands/ship/ready.md +19 -0
- package/commands/ship/release.md +19 -0
- package/commands/ship/status.md +18 -0
- package/commands/understand/docs.md +22 -0
- package/commands/understand/research.md +22 -0
- package/commands/understand/this.md +23 -0
- package/commands/understand/why.md +18 -0
- package/commands/work/clean.md +32 -0
- package/commands/work/drive.md +69 -0
- package/commands/work/optimize-prompts.md +46 -0
- package/commands/work/parallel-review.md +40 -0
- package/db/migrations/001_init.sql +38 -0
- package/langfuse/docker-compose.yml +82 -0
- package/lib/audit-skills.mjs +118 -0
- package/lib/auto-docs.mjs +293 -0
- package/lib/cli-commands.mjs +409 -0
- package/lib/codex-config.mjs +114 -0
- package/lib/comment-lint.mjs +191 -0
- package/lib/completions.mjs +170 -0
- package/lib/context-state.mjs +99 -0
- package/lib/cost.mjs +213 -0
- package/lib/distill.mjs +403 -0
- package/lib/efficiency.mjs +139 -0
- package/lib/env-config.mjs +53 -0
- package/lib/eval-harness.mjs +59 -0
- package/lib/features.mjs +209 -0
- package/lib/headhunt.mjs +597 -0
- package/lib/hooks/adaptive-lint.mjs +112 -0
- package/lib/hooks/agent-tracker.mjs +50 -0
- package/lib/hooks/bootstrap-guard.mjs +90 -0
- package/lib/hooks/comment-lint.mjs +26 -0
- package/lib/hooks/config-protection.mjs +52 -0
- package/lib/hooks/console-warn.mjs +43 -0
- package/lib/hooks/context-window-recovery.mjs +90 -0
- package/lib/hooks/continuation-enforcer.mjs +72 -0
- package/lib/hooks/dep-audit.mjs +155 -0
- package/lib/hooks/drive-guard.mjs +89 -0
- package/lib/hooks/edit-accumulator.mjs +36 -0
- package/lib/hooks/edit-error-recovery.mjs +46 -0
- package/lib/hooks/edit-guard.mjs +109 -0
- package/lib/hooks/env-check.mjs +80 -0
- package/lib/hooks/guard-bash.mjs +83 -0
- package/lib/hooks/mcp-audit.mjs +57 -0
- package/lib/hooks/mcp-health-check.mjs +51 -0
- package/lib/hooks/mcp-task-scope.mjs +47 -0
- package/lib/hooks/model-fallback.mjs +105 -0
- package/lib/hooks/pre-compact.mjs +212 -0
- package/lib/hooks/pre-push-gate.mjs +129 -0
- package/lib/hooks/read-tracker.mjs +129 -0
- package/lib/hooks/registry-sync.mjs +23 -0
- package/lib/hooks/scan-secrets.mjs +76 -0
- package/lib/hooks/session-start.mjs +95 -0
- package/lib/hooks/stop-notify.mjs +191 -0
- package/lib/hooks/stop-typecheck.mjs +83 -0
- package/lib/hooks/task-completed-guard.mjs +43 -0
- package/lib/hooks/teammate-idle-guard.mjs +54 -0
- package/lib/hooks/workflow-guard.mjs +62 -0
- package/lib/host-capabilities.mjs +123 -0
- package/lib/init-docs.mjs +382 -0
- package/lib/mcp/server.mjs +1123 -0
- package/lib/mcp-catalog.json +243 -0
- package/lib/mcp-manager.mjs +433 -0
- package/lib/mcp-platform-config.mjs +104 -0
- package/lib/model-router.mjs +810 -0
- package/lib/opencode-config.mjs +44 -0
- package/lib/opencode-runtime-plugin.mjs +909 -0
- package/lib/opencode-telemetry.mjs +433 -0
- package/lib/orchestration-policy.mjs +258 -0
- package/lib/prompt-composer.mjs +196 -0
- package/lib/prompt-metadata.mjs +68 -0
- package/lib/review.mjs +429 -0
- package/lib/role-preload.mjs +52 -0
- package/lib/schemas/decision.json +50 -0
- package/lib/schemas/implementation.json +51 -0
- package/lib/schemas/review-report.json +32 -0
- package/lib/schemas/test-report.json +43 -0
- package/lib/server/index.mjs +334 -0
- package/lib/server/static/app.js +841 -0
- package/lib/server/static/index.html +820 -0
- package/lib/service-manager.mjs +225 -0
- package/lib/setup.mjs +319 -0
- package/lib/status.mjs +707 -0
- package/lib/storage/backend.mjs +44 -0
- package/lib/storage/embeddings.mjs +70 -0
- package/lib/storage/hybrid-query.mjs +184 -0
- package/lib/storage/sql-store.mjs +55 -0
- package/lib/storage/state-source.mjs +101 -0
- package/lib/storage/sync.mjs +164 -0
- package/lib/storage/vector-store.mjs +59 -0
- package/lib/telemetry/backends/langfuse.mjs +58 -0
- package/lib/telemetry/backends/noop.mjs +16 -0
- package/lib/telemetry/langfuse-ingest.mjs +108 -0
- package/lib/telemetry/langfuse-model-sync.mjs +270 -0
- package/lib/telemetry/team-rollup.mjs +143 -0
- package/lib/toolkit-env.mjs +30 -0
- package/lib/validator.mjs +181 -0
- package/lib/workflow-state.mjs +794 -0
- package/package.json +53 -0
- package/personas/construct.md +94 -0
- package/platforms/claude/CLAUDE.md +30 -0
- package/platforms/claude/settings.template.json +472 -0
- package/platforms/opencode/config.template.json +65 -0
- package/platforms/opencode/plugins/construct-fallback.js +5 -0
- package/platforms/opencode/sync-config.mjs +69 -0
- package/rules/common/agents.md +55 -0
- package/rules/common/code-review.md +129 -0
- package/rules/common/coding-style.md +95 -0
- package/rules/common/comments.md +139 -0
- package/rules/common/cx-agent-routing.md +61 -0
- package/rules/common/cx-skill-routing.md +11 -0
- package/rules/common/development-workflow.md +49 -0
- package/rules/common/git-workflow.md +29 -0
- package/rules/common/hooks.md +35 -0
- package/rules/common/patterns.md +36 -0
- package/rules/common/performance.md +71 -0
- package/rules/common/security.md +34 -0
- package/rules/common/testing.md +62 -0
- package/rules/golang/coding-style.md +37 -0
- package/rules/golang/hooks.md +22 -0
- package/rules/golang/patterns.md +50 -0
- package/rules/golang/security.md +39 -0
- package/rules/golang/testing.md +36 -0
- package/rules/python/coding-style.md +47 -0
- package/rules/python/hooks.md +24 -0
- package/rules/python/patterns.md +44 -0
- package/rules/python/security.md +35 -0
- package/rules/python/testing.md +43 -0
- package/rules/swift/coding-style.md +52 -0
- package/rules/swift/hooks.md +25 -0
- package/rules/swift/patterns.md +71 -0
- package/rules/swift/security.md +38 -0
- package/rules/swift/testing.md +50 -0
- package/rules/typescript/coding-style.md +204 -0
- package/rules/typescript/hooks.md +27 -0
- package/rules/typescript/patterns.md +57 -0
- package/rules/typescript/security.md +33 -0
- package/rules/typescript/testing.md +23 -0
- package/rules/web/coding-style.md +101 -0
- package/rules/web/design-quality.md +68 -0
- package/rules/web/hooks.md +125 -0
- package/rules/web/patterns.md +84 -0
- package/rules/web/performance.md +69 -0
- package/rules/web/security.md +62 -0
- package/rules/web/testing.md +60 -0
- package/rules/zh/README.md +113 -0
- package/rules/zh/agents.md +55 -0
- package/rules/zh/code-review.md +129 -0
- package/rules/zh/coding-style.md +53 -0
- package/rules/zh/development-workflow.md +49 -0
- package/rules/zh/git-workflow.md +29 -0
- package/rules/zh/hooks.md +35 -0
- package/rules/zh/patterns.md +36 -0
- package/rules/zh/performance.md +60 -0
- package/rules/zh/security.md +34 -0
- package/rules/zh/testing.md +34 -0
- package/skills/ai/agent-dev.md +102 -0
- package/skills/ai/llm-security.md +105 -0
- package/skills/ai/ml-ops.md +169 -0
- package/skills/ai/orchestration-workflow.md +87 -0
- package/skills/ai/prompt-and-eval.md +114 -0
- package/skills/ai/prompt-optimizer.md +114 -0
- package/skills/ai/rag-system.md +104 -0
- package/skills/architecture/api-design.md +100 -0
- package/skills/architecture/caching.md +101 -0
- package/skills/architecture/cloud-native.md +97 -0
- package/skills/architecture/message-queue.md +100 -0
- package/skills/architecture/security-arch.md +105 -0
- package/skills/development/cpp.md +127 -0
- package/skills/development/go.md +129 -0
- package/skills/development/java.md +134 -0
- package/skills/development/kotlin.md +140 -0
- package/skills/development/mobile-crossplatform.md +144 -0
- package/skills/development/python.md +109 -0
- package/skills/development/rust.md +127 -0
- package/skills/development/shell.md +127 -0
- package/skills/development/swift.md +129 -0
- package/skills/development/typescript.md +129 -0
- package/skills/devops/ci-cd.md +108 -0
- package/skills/devops/containerization.md +106 -0
- package/skills/devops/cost-optimization.md +105 -0
- package/skills/devops/data-engineering.md +181 -0
- package/skills/devops/database.md +95 -0
- package/skills/devops/dependency-management.md +91 -0
- package/skills/devops/devsecops.md +96 -0
- package/skills/devops/git-workflow.md +100 -0
- package/skills/devops/incident-response.md +167 -0
- package/skills/devops/monorepo.md +87 -0
- package/skills/devops/observability.md +103 -0
- package/skills/devops/performance.md +104 -0
- package/skills/devops/testing.md +104 -0
- package/skills/docs/adr-workflow.md +32 -0
- package/skills/docs/backlog-proposal-workflow.md +19 -0
- package/skills/docs/customer-profile-workflow.md +22 -0
- package/skills/docs/evidence-ingest-workflow.md +23 -0
- package/skills/docs/init-docs.md +160 -0
- package/skills/docs/init-project.md +50 -0
- package/skills/docs/prd-workflow.md +57 -0
- package/skills/docs/prfaq-workflow.md +25 -0
- package/skills/docs/product-intelligence-review.md +22 -0
- package/skills/docs/product-intelligence-workflow.md +62 -0
- package/skills/docs/product-signal-workflow.md +27 -0
- package/skills/docs/research-workflow.md +28 -0
- package/skills/docs/runbook-workflow.md +24 -0
- package/skills/exploration/repo-map.md +297 -0
- package/skills/frameworks/django.md +159 -0
- package/skills/frameworks/nextjs.md +148 -0
- package/skills/frameworks/react.md +132 -0
- package/skills/frameworks/spring-boot.md +165 -0
- package/skills/frontend-design/accessibility.md +153 -0
- package/skills/frontend-design/component-patterns.md +111 -0
- package/skills/frontend-design/engineering.md +122 -0
- package/skills/frontend-design/state-management.md +118 -0
- package/skills/frontend-design/ui-aesthetics.md +102 -0
- package/skills/frontend-design/ux-principles.md +126 -0
- package/skills/quality-gates/review-work.md +90 -0
- package/skills/quality-gates/verify-change.md +94 -0
- package/skills/quality-gates/verify-module.md +96 -0
- package/skills/quality-gates/verify-quality.md +93 -0
- package/skills/quality-gates/verify-security.md +95 -0
- package/skills/roles/architect.ai-systems.md +37 -0
- package/skills/roles/architect.data.md +37 -0
- package/skills/roles/architect.enterprise.md +37 -0
- package/skills/roles/architect.integration.md +37 -0
- package/skills/roles/architect.md +68 -0
- package/skills/roles/architect.platform.md +37 -0
- package/skills/roles/data-analyst.experiment.md +37 -0
- package/skills/roles/data-analyst.md +68 -0
- package/skills/roles/data-analyst.product-intelligence.md +37 -0
- package/skills/roles/data-analyst.product.md +37 -0
- package/skills/roles/data-analyst.telemetry.md +37 -0
- package/skills/roles/data-engineer.pipeline.md +37 -0
- package/skills/roles/data-engineer.vector-retrieval.md +37 -0
- package/skills/roles/data-engineer.warehouse.md +37 -0
- package/skills/roles/debugger.md +68 -0
- package/skills/roles/designer.accessibility.md +43 -0
- package/skills/roles/designer.md +68 -0
- package/skills/roles/engineer.ai.md +49 -0
- package/skills/roles/engineer.data.md +49 -0
- package/skills/roles/engineer.md +85 -0
- package/skills/roles/engineer.platform.md +49 -0
- package/skills/roles/operator.docs.md +43 -0
- package/skills/roles/operator.md +68 -0
- package/skills/roles/operator.release.md +43 -0
- package/skills/roles/operator.sre.md +43 -0
- package/skills/roles/orchestrator.md +66 -0
- package/skills/roles/product-manager.ai-product.md +37 -0
- package/skills/roles/product-manager.business-strategy.md +43 -0
- package/skills/roles/product-manager.enterprise.md +37 -0
- package/skills/roles/product-manager.growth.md +37 -0
- package/skills/roles/product-manager.md +67 -0
- package/skills/roles/product-manager.platform.md +37 -0
- package/skills/roles/product-manager.product.md +37 -0
- package/skills/roles/qa.ai-eval.md +37 -0
- package/skills/roles/qa.api-contract.md +37 -0
- package/skills/roles/qa.data-pipeline.md +37 -0
- package/skills/roles/qa.md +68 -0
- package/skills/roles/qa.test-automation.md +49 -0
- package/skills/roles/qa.web-ui.md +37 -0
- package/skills/roles/researcher.explorer.md +43 -0
- package/skills/roles/researcher.md +68 -0
- package/skills/roles/researcher.ux.md +43 -0
- package/skills/roles/reviewer.devil-advocate.md +43 -0
- package/skills/roles/reviewer.evaluator.md +43 -0
- package/skills/roles/reviewer.md +68 -0
- package/skills/roles/reviewer.trace.md +43 -0
- package/skills/roles/security.ai.md +37 -0
- package/skills/roles/security.appsec.md +37 -0
- package/skills/roles/security.cloud.md +37 -0
- package/skills/roles/security.legal-compliance.md +49 -0
- package/skills/roles/security.md +68 -0
- package/skills/roles/security.privacy.md +37 -0
- package/skills/roles/security.supply-chain.md +37 -0
- package/skills/routing.md +139 -0
- package/skills/security/blue-team.md +83 -0
- package/skills/security/code-audit.md +94 -0
- package/skills/security/pentest.md +84 -0
- package/skills/security/red-team.md +81 -0
- package/skills/security/threat-intel.md +89 -0
- package/skills/security/vuln-research.md +87 -0
- package/skills/utility/clean-code.md +165 -0
- package/sync-agents.mjs +899 -0
- package/templates/docs/adr.md +27 -0
- package/templates/docs/backlog-proposal.md +26 -0
- package/templates/docs/customer-profile.md +34 -0
- package/templates/docs/evidence-brief.md +37 -0
- package/templates/docs/incident-report.md +46 -0
- package/templates/docs/memo.md +27 -0
- package/templates/docs/meta-prd.md +56 -0
- package/templates/docs/one-pager.md +24 -0
- package/templates/docs/prd-business.md +61 -0
- package/templates/docs/prd-platform.md +62 -0
- package/templates/docs/prd.md +47 -0
- package/templates/docs/prfaq.md +31 -0
- package/templates/docs/product-intelligence-report.md +31 -0
- package/templates/docs/research-brief.md +29 -0
- package/templates/docs/rfc-platform.md +60 -0
- package/templates/docs/rfc.md +46 -0
- package/templates/docs/runbook.md +33 -0
- package/templates/docs/signal-brief.md +25 -0
package/bin/construct
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bin/construct — construct CLI entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Parses the command string, dispatches to the appropriate handler, and
|
|
6
|
+
* loads environment config from ~/.construct/.env. This is the only
|
|
7
|
+
* public-facing binary; all commands are exported from lib/.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
|
|
15
|
+
import { CLI_COMMANDS, CLI_COMMANDS_BY_CATEGORY, CATEGORY_ORDER } from '../lib/cli-commands.mjs';
|
|
16
|
+
import { buildStatus, formatStatusReport } from '../lib/status.mjs';
|
|
17
|
+
import { runWorkflowCli, loadWorkflow, validateWorkflowState } from '../lib/workflow-state.mjs';
|
|
18
|
+
import { validateRegistry } from '../lib/validator.mjs';
|
|
19
|
+
import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig, pollFreeModels, topForTier, selectForTier, applyToEnv, resetEnv, setTierModel, setModelWithTierInference } from '../lib/model-router.mjs';
|
|
20
|
+
import { cmdMcpList, cmdMcpAdd, cmdMcpRemove, cmdMcpInfo } from '../lib/mcp-manager.mjs';
|
|
21
|
+
import { printHostCapabilities } from '../lib/host-capabilities.mjs';
|
|
22
|
+
import { runSetup } from '../lib/setup.mjs';
|
|
23
|
+
import { loadConstructEnv, getUserEnvPath } from '../lib/env-config.mjs';
|
|
24
|
+
import { runDistillCli } from '../lib/distill.mjs';
|
|
25
|
+
import { generateCompletions } from '../lib/completions.mjs';
|
|
26
|
+
import { runHeadhunt, listTeamTemplates } from '../lib/headhunt.mjs';
|
|
27
|
+
import { auditSkills, runAuditSkillsCli } from '../lib/audit-skills.mjs';
|
|
28
|
+
import { runTeamReviewCli } from '../lib/telemetry/team-rollup.mjs';
|
|
29
|
+
import { readDashboardState, startDashboard, startServices, stopDashboard } from '../lib/service-manager.mjs';
|
|
30
|
+
import { readCostLog, summarizeCostData, formatCostReport, clearCostLog } from '../lib/cost.mjs';
|
|
31
|
+
import { readEfficiencyLog, summarizeEfficiencyData, formatEfficiencyReport } from '../lib/efficiency.mjs';
|
|
32
|
+
import { buildHybridSearchResultsAsync } from '../lib/storage/hybrid-query.mjs';
|
|
33
|
+
import { createSqlClient, closeSqlClient, readVectorConfig } from '../lib/storage/backend.mjs';
|
|
34
|
+
import { describeVectorStore } from '../lib/storage/vector-store.mjs';
|
|
35
|
+
import { describeSqlStore } from '../lib/storage/sql-store.mjs';
|
|
36
|
+
import { syncFileStateToSql } from '../lib/storage/sync.mjs';
|
|
37
|
+
|
|
38
|
+
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
39
|
+
const HOME = os.homedir();
|
|
40
|
+
const ENV = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: HOME, env: process.env });
|
|
41
|
+
for (const [key, value] of Object.entries(ENV)) {
|
|
42
|
+
if (!(key in process.env)) process.env[key] = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const COLORS = (!process.env.NO_COLOR && process.stdout.isTTY && process.env.TERM !== 'dumb')
|
|
46
|
+
? {
|
|
47
|
+
bold: '\u001b[1m', dim: '\u001b[2m', reset: '\u001b[0m',
|
|
48
|
+
red: '\u001b[31m', green: '\u001b[32m', yellow: '\u001b[33m', cyan: '\u001b[36m',
|
|
49
|
+
}
|
|
50
|
+
: { bold: '', dim: '', reset: '', red: '', green: '', yellow: '', cyan: '' };
|
|
51
|
+
|
|
52
|
+
function println(message = '') { process.stdout.write(`${message}\n`); }
|
|
53
|
+
function errorln(message = '') { process.stderr.write(`${message}\n`); }
|
|
54
|
+
function ok(message) { println(` ${COLORS.green}✓${COLORS.reset} ${message}`); }
|
|
55
|
+
function warn(message) { println(` ${COLORS.yellow}⚠${COLORS.reset} ${message}`); }
|
|
56
|
+
function info(message) { println(` ${COLORS.cyan}→${COLORS.reset} ${message}`); }
|
|
57
|
+
|
|
58
|
+
function usage() {
|
|
59
|
+
println(`${COLORS.bold}construct${COLORS.reset} — AI agent harness`);
|
|
60
|
+
println(`\n${COLORS.dim}Usage:${COLORS.reset} construct <command> [options]\n`);
|
|
61
|
+
for (const category of CATEGORY_ORDER) {
|
|
62
|
+
const commands = CLI_COMMANDS_BY_CATEGORY[category] ?? [];
|
|
63
|
+
if (!commands.length) continue;
|
|
64
|
+
println(`${COLORS.bold}${category}${COLORS.reset}`);
|
|
65
|
+
for (const command of commands) {
|
|
66
|
+
println(` ${command.emoji} ${command.name.padEnd(14)} ${command.description}`);
|
|
67
|
+
}
|
|
68
|
+
println('');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function runNodeScript(scriptPath, args = [], extraEnv = {}) {
|
|
73
|
+
const result = spawnSync(process.execPath, [scriptPath, ...args], {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
env: { ...process.env, ...extraEnv },
|
|
76
|
+
});
|
|
77
|
+
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function cmdStatus() {
|
|
81
|
+
const jsonOutput = restArgsCache.includes('--json');
|
|
82
|
+
const status = await buildStatus({ rootDir: ROOT_DIR, cwd: process.cwd(), homeDir: HOME, env: process.env });
|
|
83
|
+
if (jsonOutput) {
|
|
84
|
+
println(JSON.stringify(status, null, 2));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
println(formatStatusReport(status));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function cmdShow() {
|
|
91
|
+
const status = await buildStatus({ rootDir: ROOT_DIR, cwd: process.cwd(), homeDir: HOME, env: process.env });
|
|
92
|
+
const dashboard = readDashboardState(HOME);
|
|
93
|
+
println('');
|
|
94
|
+
println('Construct Services');
|
|
95
|
+
println('══════════════════');
|
|
96
|
+
println('');
|
|
97
|
+
if (dashboard) println(` • Managed dashboard PID ${dashboard.pid} on ${dashboard.url}`);
|
|
98
|
+
for (const service of status.system.services) {
|
|
99
|
+
const icon = service.status === 'healthy' ? '✓' : service.status === 'degraded' ? '⚠' : '✗';
|
|
100
|
+
const note = service.note ? ` (${service.note})` : '';
|
|
101
|
+
println(` ${icon} ${service.name.padEnd(28)} ${service.url}${note}`);
|
|
102
|
+
}
|
|
103
|
+
println('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function cmdSync(args) {
|
|
107
|
+
generateCompletions();
|
|
108
|
+
runNodeScript(path.join(ROOT_DIR, 'sync-agents.mjs'), args);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function cmdList() {
|
|
112
|
+
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'agents', 'registry.json'), 'utf8'));
|
|
113
|
+
println('Construct Agents\n');
|
|
114
|
+
println('Personas:');
|
|
115
|
+
for (const persona of registry.personas ?? []) {
|
|
116
|
+
println(` ${persona.displayName} [${persona.role}] ${persona.description}`);
|
|
117
|
+
}
|
|
118
|
+
println('\nSpecialists:');
|
|
119
|
+
for (const agent of registry.agents ?? []) {
|
|
120
|
+
println(` ${registry.prefix}-${agent.name} ${agent.description}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function cmdDoctor() {
|
|
125
|
+
const checks = [];
|
|
126
|
+
const add = (label, pass, optional = false) => checks.push({ label, pass, optional });
|
|
127
|
+
add('registry.json exists', fs.existsSync(path.join(ROOT_DIR, 'agents', 'registry.json')));
|
|
128
|
+
add('sync-agents.mjs exists', fs.existsSync(path.join(ROOT_DIR, 'sync-agents.mjs')));
|
|
129
|
+
add('Node.js 20+ (recommended)', Number.parseInt(process.versions.node.split('.')[0], 10) >= 20);
|
|
130
|
+
add('npm available', true);
|
|
131
|
+
const constructOnPath = spawnSync('zsh', ['-lc', 'command -v construct'], { encoding: 'utf8', env: process.env });
|
|
132
|
+
const constructPath = constructOnPath.status === 0 ? constructOnPath.stdout.trim() : '';
|
|
133
|
+
const constructVersion = constructPath
|
|
134
|
+
? spawnSync(constructPath, ['version'], { encoding: 'utf8', env: process.env })
|
|
135
|
+
: null;
|
|
136
|
+
add('construct command on PATH', Boolean(constructPath), true);
|
|
137
|
+
add('construct command is this CLI', Boolean(constructVersion && constructVersion.status === 0 && /^construct v\d+\./.test(constructVersion.stdout.trim())), true);
|
|
138
|
+
add('OpenCode config exists', fs.existsSync(path.join(HOME, '.config', 'opencode', 'opencode.json')), true);
|
|
139
|
+
add('Claude Code agents dir', fs.existsSync(path.join(HOME, '.claude', 'agents')), true);
|
|
140
|
+
add('Codex agents dir', fs.existsSync(path.join(HOME, '.codex', 'agents')), true);
|
|
141
|
+
add('Copilot prompts dir', fs.existsSync(path.join(HOME, '.github', 'prompts')), true);
|
|
142
|
+
add('User config ready', fs.existsSync(getUserEnvPath(HOME)) || fs.existsSync(path.join(HOME, '.construct')), true);
|
|
143
|
+
add('skills/ directory', fs.existsSync(path.join(ROOT_DIR, 'skills')));
|
|
144
|
+
add('rules/common/', fs.existsSync(path.join(ROOT_DIR, 'rules', 'common')));
|
|
145
|
+
add('agents/prompts/ directory', fs.existsSync(path.join(ROOT_DIR, 'agents', 'prompts')));
|
|
146
|
+
add('Hybrid storage env keys documented', fs.existsSync(path.join(ROOT_DIR, '.env.example')));
|
|
147
|
+
fs.mkdirSync(path.join(HOME, '.cx', 'performance-reviews'), { recursive: true });
|
|
148
|
+
add('~/.cx/performance-reviews/ ready', true);
|
|
149
|
+
|
|
150
|
+
// Validate promptFile references and skill bindings
|
|
151
|
+
try {
|
|
152
|
+
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'agents', 'registry.json'), 'utf8'));
|
|
153
|
+
const brokenPromptFiles = (registry.agents ?? []).filter((a) => {
|
|
154
|
+
if (!a.promptFile) return false;
|
|
155
|
+
return !fs.existsSync(path.join(ROOT_DIR, a.promptFile));
|
|
156
|
+
});
|
|
157
|
+
add('All agent promptFiles resolve', brokenPromptFiles.length === 0);
|
|
158
|
+
const headhuntClassifierAgents = ['cx-docs-keeper', 'cx-researcher', 'cx-architect', 'cx-security', 'cx-engineer'];
|
|
159
|
+
const registryNames = new Set((registry.agents ?? []).map((a) => `cx-${a.name}`));
|
|
160
|
+
const classifierOrphans = headhuntClassifierAgents.filter((n) => !registryNames.has(n));
|
|
161
|
+
add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
|
|
162
|
+
const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
|
|
163
|
+
add('No declared skills missing on disk', skillAudit.pass, false);
|
|
164
|
+
|
|
165
|
+
const sqlConfig = describeSqlStore(process.env);
|
|
166
|
+
const sqlClient = createSqlClient(process.env);
|
|
167
|
+
if (sqlConfig.configured && sqlClient) {
|
|
168
|
+
try {
|
|
169
|
+
await sqlClient`select 1 as ok`;
|
|
170
|
+
add('SQL backend reachable', true);
|
|
171
|
+
} catch {
|
|
172
|
+
add('SQL backend reachable', false);
|
|
173
|
+
} finally {
|
|
174
|
+
await closeSqlClient(sqlClient);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
add('SQL backend configured', sqlConfig.fallbackAvailable);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const vectorConfig = describeVectorStore(process.env);
|
|
181
|
+
add('Vector backend configured', Boolean(vectorConfig.endpoint || vectorConfig.indexPath || vectorConfig.mode === 'file'));
|
|
182
|
+
} catch {
|
|
183
|
+
add('Registry integrity check', false);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
println('Construct Health Check');
|
|
187
|
+
println('══════════════════════');
|
|
188
|
+
println('');
|
|
189
|
+
let okCount = 0; let warnCount = 0; let failCount = 0;
|
|
190
|
+
for (const check of checks) {
|
|
191
|
+
const symbol = check.pass ? '✓' : check.optional ? '⚠' : '✗';
|
|
192
|
+
println(` ${check.label.padEnd(40)} ${symbol}`);
|
|
193
|
+
if (check.pass) okCount += 1;
|
|
194
|
+
else if (check.optional) warnCount += 1;
|
|
195
|
+
else failCount += 1;
|
|
196
|
+
}
|
|
197
|
+
println('');
|
|
198
|
+
println(`Results: ${okCount} passed, ${warnCount} warnings, ${failCount} failed`);
|
|
199
|
+
if (failCount > 0) process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function cmdUp() {
|
|
203
|
+
const { results } = await startServices({ rootDir: ROOT_DIR, homeDir: HOME });
|
|
204
|
+
for (const svc of results) {
|
|
205
|
+
const label = svc.url ? `${svc.name} → ${svc.url}` : svc.name;
|
|
206
|
+
if (svc.status === 'started') ok(svc.note ? `${label} (${svc.note})` : label);
|
|
207
|
+
else if (svc.status === 'reused') info(`${label} (already running)`);
|
|
208
|
+
else if (svc.status === 'configured') info(`${svc.name}: ${svc.note}`);
|
|
209
|
+
else if (svc.status === 'failed') errorln(`${svc.name}: ${svc.note}`);
|
|
210
|
+
else warn(`${svc.name}: ${svc.note}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function cmdDown() {
|
|
215
|
+
const result = stopDashboard(HOME);
|
|
216
|
+
if (result.stopped) ok(`Dashboard stopped (pid ${result.pid}, port ${result.port}).`);
|
|
217
|
+
else if (result.reason === 'stale-state') warn('Dashboard state was stale (process already gone). Cleaned up.');
|
|
218
|
+
else warn('No managed dashboard was running.');
|
|
219
|
+
info('Other services still need to be stopped with their native commands or process manager.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function cmdServe() {
|
|
223
|
+
const dashboard = await startDashboard({ rootDir: ROOT_DIR, homeDir: HOME });
|
|
224
|
+
if (dashboard.reused) {
|
|
225
|
+
println(`Construct dashboard already running on ${dashboard.url} (pid ${dashboard.pid})`);
|
|
226
|
+
} else {
|
|
227
|
+
println(`Started Construct dashboard on ${dashboard.url} (pid ${dashboard.pid})`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function cmdVersion() {
|
|
232
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
|
|
233
|
+
println(`construct v${pkg.version}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function cmdReview(args) {
|
|
237
|
+
fs.mkdirSync(path.join(HOME, '.cx', 'performance-reviews'), { recursive: true });
|
|
238
|
+
if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) {
|
|
239
|
+
errorln('Error: LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY must be set.');
|
|
240
|
+
errorln('Configure Langfuse credentials in ~/.construct/config.env or .env.');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
runNodeScript(path.join(ROOT_DIR, 'lib', 'review.mjs'), args);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function cmdInitDocs(args) { runNodeScript(path.join(ROOT_DIR, 'lib', 'init-docs.mjs'), args); }
|
|
247
|
+
async function cmdDistill(args) { runDistillCli(args); }
|
|
248
|
+
|
|
249
|
+
function parseQueryCommandArgs(args, defaults = {}) {
|
|
250
|
+
const flags = {};
|
|
251
|
+
const queryParts = [];
|
|
252
|
+
for (const arg of args) {
|
|
253
|
+
if (arg.startsWith('--')) {
|
|
254
|
+
const [key, ...valueParts] = arg.slice(2).split('=');
|
|
255
|
+
flags[key] = valueParts.length ? valueParts.join('=') : true;
|
|
256
|
+
} else {
|
|
257
|
+
queryParts.push(arg);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
query: queryParts.join(' ').trim(),
|
|
262
|
+
dir: typeof flags.dir === 'string' ? flags.dir : defaults.dir,
|
|
263
|
+
ext: typeof flags.ext === 'string' ? flags.ext : defaults.ext,
|
|
264
|
+
depth: typeof flags.depth === 'string' ? flags.depth : defaults.depth,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function cmdResearch(args) {
|
|
269
|
+
const parsed = parseQueryCommandArgs(args, { dir: 'docs' });
|
|
270
|
+
const query = parsed.query;
|
|
271
|
+
if (!query) {
|
|
272
|
+
errorln('Usage: construct research <query> [--dir=PATH] [--ext=LIST] [--depth=N]');
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
const distillArgs = [parsed.dir, '--format=extract', `--query=${query}`, '--mode=json'];
|
|
276
|
+
if (parsed.ext) distillArgs.push(`--ext=${parsed.ext}`);
|
|
277
|
+
if (parsed.depth) distillArgs.push(`--depth=${parsed.depth}`);
|
|
278
|
+
await runDistillCli(distillArgs);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function cmdDocs(args) {
|
|
282
|
+
const parsed = parseQueryCommandArgs(args, { dir: 'docs', ext: 'md,mdx,rst,txt' });
|
|
283
|
+
const query = parsed.query;
|
|
284
|
+
if (!query) {
|
|
285
|
+
errorln('Usage: construct docs <query> [--dir=PATH] [--depth=N]');
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const distillArgs = [parsed.dir, '--format=extract', `--query=${query}`, '--mode=json', `--ext=${parsed.ext}`];
|
|
289
|
+
if (parsed.depth) distillArgs.push(`--depth=${parsed.depth}`);
|
|
290
|
+
await runDistillCli(distillArgs);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function cmdSearch(args) {
|
|
294
|
+
const flags = new Set();
|
|
295
|
+
const queryParts = [];
|
|
296
|
+
for (const arg of args) {
|
|
297
|
+
if (arg.startsWith('--')) flags.add(arg);
|
|
298
|
+
else queryParts.push(arg);
|
|
299
|
+
}
|
|
300
|
+
const query = queryParts.join(' ').trim();
|
|
301
|
+
if (!query) {
|
|
302
|
+
errorln('Usage: construct search <query> [--limit=N]');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
const limitFlag = [...flags].find((flag) => flag.startsWith('--limit='));
|
|
306
|
+
const limit = limitFlag ? Number.parseInt(limitFlag.split('=')[1], 10) : 10;
|
|
307
|
+
const results = await buildHybridSearchResultsAsync(ROOT_DIR, query, { limit: Number.isFinite(limit) ? limit : 10, env: process.env });
|
|
308
|
+
println(JSON.stringify(results, null, 2));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function cmdStorage(args) {
|
|
312
|
+
const [action, ...rest] = args;
|
|
313
|
+
if (!action) {
|
|
314
|
+
errorln('Usage: construct storage <sync|status>');
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
if (action === 'sync') {
|
|
318
|
+
const result = await syncFileStateToSql(process.cwd(), { env: process.env, project: 'construct' });
|
|
319
|
+
println(JSON.stringify(result, null, 2));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (action === 'status') {
|
|
323
|
+
const sqlClient = createSqlClient(process.env);
|
|
324
|
+
const vector = readVectorConfig(process.env);
|
|
325
|
+
let sqlStatus = { status: 'unavailable', message: 'No SQL store configured' };
|
|
326
|
+
if (sqlClient) {
|
|
327
|
+
try {
|
|
328
|
+
await sqlClient`select 1 as ok`;
|
|
329
|
+
sqlStatus = { status: 'healthy', message: 'Shared Postgres store reachable' };
|
|
330
|
+
} catch (error) {
|
|
331
|
+
sqlStatus = { status: 'degraded', message: `SQL store unreachable: ${error?.message || 'unknown error'}` };
|
|
332
|
+
} finally {
|
|
333
|
+
await closeSqlClient(sqlClient);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
println(JSON.stringify({ sql: sqlStatus, vector }, null, 2));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
errorln('Usage: construct storage <sync|status>');
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
async function cmdHeadhunt(args) { await runHeadhunt({ args, cwd: process.cwd(), homeDir: HOME }); }
|
|
343
|
+
async function cmdHosts() { printHostCapabilities(); }
|
|
344
|
+
async function cmdSetup(args) { await runSetup({ rootDir: ROOT_DIR, args, homeDir: HOME }); }
|
|
345
|
+
|
|
346
|
+
async function cmdDo(args) {
|
|
347
|
+
const goal = args.join(' ').trim();
|
|
348
|
+
if (!goal) {
|
|
349
|
+
errorln('Usage: construct do <goal>');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
println(`@construct ${goal}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function cmdValidate() {
|
|
356
|
+
const reg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'agents', 'registry.json'), 'utf8'));
|
|
357
|
+
const result = validateRegistry(reg, { rootDir: ROOT_DIR });
|
|
358
|
+
const workflow = loadWorkflow(process.cwd());
|
|
359
|
+
const workflowResult = workflow ? validateWorkflowState(workflow) : { valid: true, errors: [] };
|
|
360
|
+
if (result.valid && workflowResult.valid) {
|
|
361
|
+
println(`✓ Registry valid (${result.summary})`);
|
|
362
|
+
if (workflow) println(`✓ Workflow valid (${workflow.id})`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (!result.valid) result.errors.forEach((e, i) => errorln(`${i + 1}. ${e}`));
|
|
366
|
+
if (!workflowResult.valid) workflowResult.errors.forEach((e, i) => errorln(`${i + 1}. ${e}`));
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function cmdModels(args) {
|
|
371
|
+
const tier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? '';
|
|
372
|
+
const setModel = args.find((arg) => arg.startsWith('--set='))?.split('=')[1] ?? '';
|
|
373
|
+
const envPath = getUserEnvPath(HOME);
|
|
374
|
+
if (args.includes('--reset')) {
|
|
375
|
+
resetEnv(envPath);
|
|
376
|
+
println('Removed CX_MODEL_* overrides from ~/.construct/config.env.');
|
|
377
|
+
await cmdSync([]);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (tier && setModel) {
|
|
381
|
+
const preferFree = args.includes('--prefer-free');
|
|
382
|
+
const preferFreeSameFamily = args.includes('--prefer-free-same-family');
|
|
383
|
+
const inferred = setModelWithTierInference(envPath, tier, setModel, registry.models ?? {}, { preferFree, preferFreeSameFamily });
|
|
384
|
+
println(`Set ${tier} -> ${setModel} in ~/.construct/config.env`);
|
|
385
|
+
if (preferFreeSameFamily) println('Prefer-free-same-family mode: enabled');
|
|
386
|
+
if (preferFree) println('Prefer-free mode: enabled');
|
|
387
|
+
println(`Resolved tier set:`);
|
|
388
|
+
println(` reasoning ${String(inferred.reasoning)}`);
|
|
389
|
+
println(` standard ${String(inferred.standard)}`);
|
|
390
|
+
println(` fast ${String(inferred.fast)}`);
|
|
391
|
+
await cmdSync([]);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (args.includes('--poll')) {
|
|
395
|
+
const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
|
|
396
|
+
if (!apiKey) {
|
|
397
|
+
errorln('Error: no OpenRouter API key found in OPENROUTER_API_KEY or OpenCode config.');
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
const models = await pollFreeModels(apiKey);
|
|
401
|
+
println('Available free models on OpenRouter:\n');
|
|
402
|
+
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
403
|
+
const top = topForTier(models, currentTier, 3);
|
|
404
|
+
println(` ${currentTier} (top ${top.length}):`);
|
|
405
|
+
top.forEach((model, index) => println(` ${index + 1}. ${model.id}`));
|
|
406
|
+
println('');
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (args.includes('--apply')) {
|
|
411
|
+
const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
|
|
412
|
+
const models = await pollFreeModels(apiKey);
|
|
413
|
+
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'agents', 'registry.json'), 'utf8'));
|
|
414
|
+
const selections = {};
|
|
415
|
+
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
416
|
+
const fallbacks = registry.models?.[currentTier]?.fallback ?? [];
|
|
417
|
+
const id = selectForTier(models, currentTier, fallbacks);
|
|
418
|
+
if (id) selections[currentTier] = id;
|
|
419
|
+
}
|
|
420
|
+
applyToEnv(envPath, selections);
|
|
421
|
+
println('Written to ~/.construct/config.env. Running construct sync...');
|
|
422
|
+
await cmdSync([]);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'agents', 'registry.json'), 'utf8'));
|
|
426
|
+
const result = readCurrentModels(envPath, registry.models ?? {});
|
|
427
|
+
println('Current model assignments:');
|
|
428
|
+
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
429
|
+
println(` ${currentTier.padEnd(11)}${String(result[currentTier] ?? '(none)')}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function cmdMcp(args) {
|
|
434
|
+
const subcmd = args[0] ?? 'list';
|
|
435
|
+
const id = args[1];
|
|
436
|
+
if (subcmd === 'list') return cmdMcpList();
|
|
437
|
+
if (subcmd === 'add') return cmdMcpAdd(id);
|
|
438
|
+
if (subcmd === 'remove') return cmdMcpRemove(id);
|
|
439
|
+
if (subcmd === 'info') return cmdMcpInfo(id);
|
|
440
|
+
errorln(`Unknown mcp subcommand: ${subcmd}`);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function cmdDiff() {
|
|
445
|
+
warn('construct diff is not yet ported to the Node CLI surface. Use git diff for now.');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function cmdOptimize() {
|
|
449
|
+
warn('construct optimize remains available only in environments with Python + DSPy ready.');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function cmdCleanup() {
|
|
453
|
+
warn('construct cleanup is not yet fully ported cross-platform.');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function cmdCost(args) {
|
|
457
|
+
const jsonOutput = args.includes('--json');
|
|
458
|
+
const resetFlag = args.includes('--reset');
|
|
459
|
+
const daysArg = args.find((a) => a.startsWith('--days='));
|
|
460
|
+
const days = daysArg ? Number.parseInt(daysArg.split('=')[1], 10) || undefined : undefined;
|
|
461
|
+
const agentArg = args.find((a) => a.startsWith('--agent='));
|
|
462
|
+
const agent = agentArg ? agentArg.split('=')[1] : undefined;
|
|
463
|
+
|
|
464
|
+
if (resetFlag) {
|
|
465
|
+
clearCostLog(HOME);
|
|
466
|
+
println('Cost log cleared.');
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const entries = readCostLog(HOME);
|
|
471
|
+
const data = summarizeCostData(entries, { days, agent });
|
|
472
|
+
|
|
473
|
+
if (jsonOutput) {
|
|
474
|
+
println(JSON.stringify(data, null, 2));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
process.stdout.write(formatCostReport(data, COLORS));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function cmdEfficiency(args) {
|
|
482
|
+
const jsonOutput = args.includes('--json');
|
|
483
|
+
const stats = readEfficiencyLog(HOME);
|
|
484
|
+
const data = summarizeEfficiencyData(stats);
|
|
485
|
+
if (jsonOutput) {
|
|
486
|
+
println(JSON.stringify(data, null, 2));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
process.stdout.write(formatEfficiencyReport(data, COLORS));
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function cmdEvals(args) {
|
|
493
|
+
const jsonOutput = args.includes('--json');
|
|
494
|
+
const catalog = {
|
|
495
|
+
description: 'Evaluators are managed via Langfuse quality scores. Use cx_score MCP tool or POST /api/public/scores to record quality scores.',
|
|
496
|
+
backendUrl: process.env.LANGFUSE_BASEURL ?? 'http://localhost:3000',
|
|
497
|
+
configured: Boolean(process.env.LANGFUSE_PUBLIC_KEY && process.env.LANGFUSE_SECRET_KEY),
|
|
498
|
+
};
|
|
499
|
+
if (jsonOutput) {
|
|
500
|
+
println(JSON.stringify(catalog, null, 2));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
println('Evaluator Catalog');
|
|
504
|
+
println('═════════════════');
|
|
505
|
+
println('');
|
|
506
|
+
println(`Backend: Langfuse (${catalog.backendUrl})`);
|
|
507
|
+
println(`Configured: ${catalog.configured ? 'yes' : 'no — set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY'}`);
|
|
508
|
+
println('');
|
|
509
|
+
println('Quality scores are recorded by agents via the cx_score MCP tool.');
|
|
510
|
+
println('View and manage scores at your Langfuse dashboard.');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function cmdDocsUpdate(args) {
|
|
514
|
+
const check = args.includes('--check');
|
|
515
|
+
const { regenerateDocs: regen } = await import('../lib/auto-docs.mjs');
|
|
516
|
+
const { changed, checked } = await regen({ rootDir: ROOT_DIR, check });
|
|
517
|
+
if (checked) {
|
|
518
|
+
if (changed.length) {
|
|
519
|
+
errorln(` ✗ Docs are out of date. Run 'construct docs:update' to regenerate:`);
|
|
520
|
+
for (const f of changed) errorln(` ${path.relative(ROOT_DIR, f)}`);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
} else {
|
|
523
|
+
ok('Docs are up to date.');
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
if (changed.length) {
|
|
527
|
+
for (const f of changed) ok(`Updated ${path.relative(ROOT_DIR, f)}`);
|
|
528
|
+
} else {
|
|
529
|
+
ok('Docs already up to date — nothing changed.');
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async function cmdDocsSite() {
|
|
535
|
+
const { buildSite } = await import('../lib/auto-docs.mjs');
|
|
536
|
+
await buildSite({ rootDir: ROOT_DIR });
|
|
537
|
+
ok('site/docs/ generated successfully.');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function cmdLintComments(args) {
|
|
541
|
+
const fix = args.includes('--fix');
|
|
542
|
+
const { lintRepo: lint, formatResults: fmt } = await import('../lib/comment-lint.mjs');
|
|
543
|
+
const results = lint({ rootDir: ROOT_DIR, fix });
|
|
544
|
+
const { output, exitCode } = fmt(results);
|
|
545
|
+
process.stdout.write(output);
|
|
546
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
550
|
+
const restArgsCache = rest;
|
|
551
|
+
|
|
552
|
+
const handlers = new Map([
|
|
553
|
+
['up', cmdUp],
|
|
554
|
+
['down', cmdDown],
|
|
555
|
+
['status', cmdStatus],
|
|
556
|
+
['show', cmdShow],
|
|
557
|
+
['serve', cmdServe],
|
|
558
|
+
['setup', cmdSetup],
|
|
559
|
+
['sync', cmdSync],
|
|
560
|
+
['list', cmdList],
|
|
561
|
+
['doctor', cmdDoctor],
|
|
562
|
+
['validate', cmdValidate],
|
|
563
|
+
['diff', cmdDiff],
|
|
564
|
+
['do', cmdDo],
|
|
565
|
+
['distill', cmdDistill],
|
|
566
|
+
['research', cmdResearch],
|
|
567
|
+
['docs', cmdDocs],
|
|
568
|
+
['search', cmdSearch],
|
|
569
|
+
['storage', cmdStorage],
|
|
570
|
+
['headhunt', cmdHeadhunt],
|
|
571
|
+
['workflow', async (args) => runWorkflowCli(args)],
|
|
572
|
+
['init-docs', cmdInitDocs],
|
|
573
|
+
['models', cmdModels],
|
|
574
|
+
['mcp', cmdMcp],
|
|
575
|
+
['hosts', cmdHosts],
|
|
576
|
+
['review', cmdReview],
|
|
577
|
+
['optimize', cmdOptimize],
|
|
578
|
+
['cost', cmdCost],
|
|
579
|
+
['efficiency', cmdEfficiency],
|
|
580
|
+
['evals', cmdEvals],
|
|
581
|
+
['cleanup', cmdCleanup],
|
|
582
|
+
['version', cmdVersion],
|
|
583
|
+
['docs:update', cmdDocsUpdate],
|
|
584
|
+
['docs:site', cmdDocsSite],
|
|
585
|
+
['lint:comments', cmdLintComments],
|
|
586
|
+
['audit', async (args) => {
|
|
587
|
+
const sub = args[0];
|
|
588
|
+
if (sub === 'skills') return runAuditSkillsCli(args.slice(1));
|
|
589
|
+
errorln(`Unknown audit subcommand: ${sub}. Available: skills`);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}],
|
|
592
|
+
['team', async (args) => {
|
|
593
|
+
const sub = args[0];
|
|
594
|
+
if (sub === 'review') return runTeamReviewCli(args.slice(1));
|
|
595
|
+
if (sub === 'templates') { listTeamTemplates(); return; }
|
|
596
|
+
errorln(`Unknown team subcommand: ${sub}. Available: review, templates`);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}],
|
|
599
|
+
]);
|
|
600
|
+
|
|
601
|
+
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
602
|
+
usage();
|
|
603
|
+
process.exit(0);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const handler = handlers.get(command);
|
|
607
|
+
if (!handler) {
|
|
608
|
+
errorln(`Unknown command: ${command}`);
|
|
609
|
+
usage();
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
await handler(rest);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
commands/build/feature.md — Build a feature — implement it end to end, tested and ready to ship
|
|
3
|
+
|
|
4
|
+
Build a feature — implement it end to end, tested and ready to ship
|
|
5
|
+
-->
|
|
6
|
+
---
|
|
7
|
+
description: Build a feature — implement it end to end, tested and ready to ship
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are Construct. Build the following: $ARGUMENTS
|
|
11
|
+
|
|
12
|
+
Before writing a line:
|
|
13
|
+
1. Read every file you will touch. Understand the existing pattern before deviating.
|
|
14
|
+
2. If the approach is uncertain, consult cx-architect first.
|
|
15
|
+
|
|
16
|
+
Verification before done:
|
|
17
|
+
- [ ] Changed files compile without errors
|
|
18
|
+
- [ ] Existing tests still pass
|
|
19
|
+
- [ ] New behavior has test coverage
|
|
20
|
+
- [ ] No hardcoded secrets or debug statements
|
|
21
|
+
- [ ] No file over 800 lines
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
commands/build/fix.md — Fix something broken — reproduce, find the root cause, apply the smallest safe change
|
|
3
|
+
|
|
4
|
+
Fix something broken — reproduce, find the root cause, apply the smallest safe change
|
|
5
|
+
-->
|
|
6
|
+
---
|
|
7
|
+
description: Fix something broken — reproduce, find the root cause, apply the smallest safe change
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are Construct.
|
|
11
|
+
|
|
12
|
+
Debugging protocol for: $ARGUMENTS
|
|
13
|
+
|
|
14
|
+
1. Reproduce — confirm the failure is real and consistent
|
|
15
|
+
2. Isolate — narrow to the smallest failing case
|
|
16
|
+
3. Trace — follow execution to where the invariant breaks
|
|
17
|
+
4. Root cause — the one upstream cause that prevents the failure when fixed
|
|
18
|
+
5. Fix — the smallest safe change that restores the invariant
|
|
19
|
+
|
|
20
|
+
Do not propose a fix until root cause is confirmed. After 3 failed attempts, stop and escalate.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
commands/design/access.md — Accessibility audit — WCAG 2.1 AA, keyboard, screen readers, contrast, motion
|
|
3
|
+
|
|
4
|
+
Accessibility audit — WCAG 2.1 AA, keyboard, screen readers, contrast, motion
|
|
5
|
+
-->
|
|
6
|
+
---
|
|
7
|
+
description: Accessibility audit — WCAG 2.1 AA, keyboard, screen readers, contrast, motion
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are Construct. Audit: $ARGUMENTS
|
|
11
|
+
|
|
12
|
+
WCAG 2.1 AA baseline:
|
|
13
|
+
- 1.4.3 Contrast: 4.5:1 normal text, 3:1 large
|
|
14
|
+
- 2.1.1 Keyboard: all functionality reachable by keyboard
|
|
15
|
+
- 2.4.7 Focus visible: keyboard focus indicator visible
|
|
16
|
+
- 4.1.2 Name/role/value: accessible name, role, and state on every UI component
|
|
17
|
+
- 1.3.1 Structure conveyed semantically
|
|
18
|
+
|
|
19
|
+
For each finding: WCAG criterion, user impact, element location, concrete fix.
|