@geraldmaron/construct 1.0.16 → 1.0.18
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/README.md +4 -1
- package/bin/construct +236 -21
- package/bin/construct-postinstall.mjs +17 -2
- package/commands/design/flow.md +2 -0
- package/commands/design/ui.md +2 -0
- package/lib/cli-commands.mjs +65 -13
- package/lib/embed/semantic.mjs +5 -3
- package/lib/embedded-contract/audit.mjs +52 -0
- package/lib/embedded-contract/capability.mjs +179 -0
- package/lib/embedded-contract/contract-version.mjs +39 -0
- package/lib/embedded-contract/envelope.mjs +70 -0
- package/lib/embedded-contract/index.mjs +71 -0
- package/lib/embedded-contract/ingest.mjs +77 -0
- package/lib/embedded-contract/model-resolve.mjs +186 -0
- package/lib/embedded-contract/redaction.mjs +91 -0
- package/lib/embedded-contract/role-facts.mjs +66 -0
- package/lib/embedded-contract/triage.mjs +134 -0
- package/lib/embedded-contract/workflow-defs.mjs +125 -0
- package/lib/embedded-contract/workflow-invoke.mjs +218 -0
- package/lib/hooks/config-protection.mjs +12 -5
- package/lib/init-unified.mjs +36 -26
- package/lib/intake/classify.mjs +6 -0
- package/lib/intake/tables/rnd.mjs +33 -0
- package/lib/mcp/server.mjs +69 -0
- package/lib/mcp/tools/embedded-contract.mjs +77 -0
- package/lib/model-router.mjs +40 -0
- package/lib/op-log.mjs +61 -0
- package/lib/roles/catalog.mjs +26 -95
- package/lib/service-manager.mjs +33 -11
- package/lib/setup.mjs +21 -3
- package/lib/validators/skills.mjs +21 -0
- package/package.json +12 -7
- package/scripts/sync-specialists.mjs +12 -3
- package/skills/ai/agent-dev.md +2 -0
- package/skills/ai/llm-security.md +2 -0
- package/skills/ai/ml-ops.md +2 -0
- package/skills/ai/orchestration-workflow.md +2 -0
- package/skills/ai/prompt-and-eval.md +2 -0
- package/skills/ai/prompt-optimizer.md +2 -0
- package/skills/ai/rag-system.md +2 -0
- package/skills/ai/trace-triage.md +36 -0
- package/skills/architecture/api-design.md +2 -0
- package/skills/architecture/caching.md +2 -0
- package/skills/architecture/cloud-native.md +2 -0
- package/skills/architecture/message-queue.md +2 -0
- package/skills/architecture/security-arch.md +2 -0
- package/skills/compliance/ai-disclosure.md +2 -0
- package/skills/compliance/data-privacy.md +2 -0
- package/skills/compliance/license-audit.md +2 -0
- package/skills/compliance/regulatory-review.md +2 -0
- package/skills/development/cpp.md +2 -0
- package/skills/development/go.md +2 -0
- package/skills/development/java.md +2 -0
- package/skills/development/kotlin.md +2 -0
- package/skills/development/mobile-crossplatform.md +2 -0
- package/skills/development/python.md +2 -0
- package/skills/development/rust.md +2 -0
- package/skills/development/shell.md +2 -0
- package/skills/development/swift.md +2 -0
- package/skills/development/typescript.md +2 -0
- package/skills/devops/ci-cd.md +2 -0
- package/skills/devops/containerization.md +2 -0
- package/skills/devops/cost-optimization.md +2 -0
- package/skills/devops/data-engineering.md +2 -0
- package/skills/devops/database.md +2 -0
- package/skills/devops/dependency-management.md +2 -0
- package/skills/devops/devsecops.md +2 -0
- package/skills/devops/git-workflow.md +2 -0
- package/skills/devops/incident-response.md +2 -0
- package/skills/devops/monorepo.md +2 -0
- package/skills/devops/observability.md +2 -0
- package/skills/devops/performance.md +2 -0
- package/skills/devops/testing.md +2 -0
- package/skills/docs/adr-workflow.md +2 -0
- package/skills/docs/backlog-proposal-workflow.md +2 -0
- package/skills/docs/customer-profile-workflow.md +2 -0
- package/skills/docs/document-ingest-workflow.md +2 -0
- package/skills/docs/evidence-ingest-workflow.md +2 -0
- package/skills/docs/init-docs.md +2 -0
- package/skills/docs/init-project.md +2 -0
- package/skills/docs/memo-and-decision-capture.md +45 -0
- package/skills/docs/prd-workflow.md +2 -0
- package/skills/docs/prfaq-workflow.md +2 -0
- package/skills/docs/product-intelligence-review.md +2 -0
- package/skills/docs/product-intelligence-workflow.md +2 -0
- package/skills/docs/product-signal-workflow.md +2 -0
- package/skills/docs/research-workflow.md +2 -0
- package/skills/docs/runbook-workflow.md +2 -0
- package/skills/docs/strategy-workflow.md +2 -0
- package/skills/docs/transcript-synthesis.md +43 -0
- package/skills/exploration/dependency-graph-reading.md +2 -0
- package/skills/exploration/repo-map.md +2 -0
- package/skills/exploration/tracer-bullet-method.md +2 -0
- package/skills/exploration/unknown-codebase-onboarding.md +2 -0
- package/skills/frameworks/django.md +2 -0
- package/skills/frameworks/nextjs.md +2 -0
- package/skills/frameworks/react.md +2 -0
- package/skills/frameworks/spring-boot.md +2 -0
- package/skills/frontend-design/accessibility.md +2 -0
- package/skills/frontend-design/component-patterns.md +2 -0
- package/skills/frontend-design/engineering.md +2 -0
- package/skills/frontend-design/screen-reader-testing.md +34 -0
- package/skills/frontend-design/state-management.md +2 -0
- package/skills/frontend-design/ui-aesthetics.md +2 -0
- package/skills/frontend-design/ux-principles.md +2 -0
- package/skills/operating/change-management.md +2 -0
- package/skills/operating/incident-response.md +2 -0
- package/skills/operating/oncall-rotation.md +2 -0
- package/skills/operating/orchestration-reference.md +2 -0
- package/skills/operating/raw-data-structuring.md +44 -0
- package/skills/operating/unstructured-triage.md +45 -0
- package/skills/quality-gates/premortem.md +37 -0
- package/skills/quality-gates/review-work.md +2 -0
- package/skills/quality-gates/verify-change.md +2 -0
- package/skills/quality-gates/verify-module.md +2 -0
- package/skills/quality-gates/verify-quality.md +2 -0
- package/skills/quality-gates/verify-security.md +2 -0
- package/skills/roles/architect.ai-systems.md +2 -0
- package/skills/roles/architect.data.md +2 -0
- package/skills/roles/architect.enterprise.md +2 -0
- package/skills/roles/architect.integration.md +2 -0
- package/skills/roles/architect.md +2 -0
- package/skills/roles/architect.platform.md +2 -0
- package/skills/roles/data-analyst.experiment.md +2 -0
- package/skills/roles/data-analyst.md +2 -0
- package/skills/roles/data-analyst.product-intelligence.md +2 -0
- package/skills/roles/data-analyst.product.md +2 -0
- package/skills/roles/data-analyst.telemetry.md +2 -0
- package/skills/roles/data-engineer.pipeline.md +2 -0
- package/skills/roles/data-engineer.vector-retrieval.md +2 -0
- package/skills/roles/data-engineer.warehouse.md +2 -0
- package/skills/roles/debugger.md +2 -0
- package/skills/roles/designer.accessibility.md +2 -0
- package/skills/roles/designer.md +2 -0
- package/skills/roles/engineer.ai.md +2 -0
- package/skills/roles/engineer.data.md +2 -0
- package/skills/roles/engineer.md +2 -0
- package/skills/roles/engineer.platform.md +2 -0
- package/skills/roles/operator.docs.md +2 -0
- package/skills/roles/operator.md +2 -0
- package/skills/roles/operator.release.md +2 -0
- package/skills/roles/operator.sre.md +2 -0
- package/skills/roles/orchestrator.md +2 -0
- package/skills/roles/product-manager.ai-product.md +2 -0
- package/skills/roles/product-manager.business-strategy.md +2 -0
- package/skills/roles/product-manager.enterprise.md +2 -0
- package/skills/roles/product-manager.growth.md +2 -0
- package/skills/roles/product-manager.md +2 -0
- package/skills/roles/product-manager.platform.md +2 -0
- package/skills/roles/product-manager.product.md +2 -0
- package/skills/roles/qa.ai-eval.md +2 -0
- package/skills/roles/qa.api-contract.md +2 -0
- package/skills/roles/qa.data-pipeline.md +2 -0
- package/skills/roles/qa.md +2 -0
- package/skills/roles/qa.test-automation.md +2 -0
- package/skills/roles/qa.web-ui.md +2 -0
- package/skills/roles/researcher.explorer.md +2 -0
- package/skills/roles/researcher.md +2 -0
- package/skills/roles/researcher.ux.md +2 -0
- package/skills/roles/reviewer.devil-advocate.md +2 -0
- package/skills/roles/reviewer.evaluator.md +2 -0
- package/skills/roles/reviewer.md +2 -0
- package/skills/roles/reviewer.trace.md +2 -0
- package/skills/roles/security.ai.md +2 -0
- package/skills/roles/security.appsec.md +2 -0
- package/skills/roles/security.cloud.md +2 -0
- package/skills/roles/security.legal-compliance.md +2 -0
- package/skills/roles/security.md +2 -0
- package/skills/roles/security.privacy.md +2 -0
- package/skills/roles/security.supply-chain.md +2 -0
- package/skills/security/blue-team.md +2 -0
- package/skills/security/code-audit.md +2 -0
- package/skills/security/pentest.md +2 -0
- package/skills/security/red-team.md +2 -0
- package/skills/security/threat-intel.md +2 -0
- package/skills/security/vuln-research.md +2 -0
- package/skills/strategy/competitive-landscape.md +2 -0
- package/skills/strategy/jobs-to-be-done.md +38 -0
- package/skills/strategy/market-research-methods.md +2 -0
- package/skills/strategy/narrative-arc.md +2 -0
- package/skills/strategy/pricing-positioning.md +2 -0
- package/skills/utility/clean-code.md +2 -0
- package/specialists/prompts/cx-architect.md +1 -0
- package/specialists/prompts/cx-designer.md +1 -1
- package/specialists/prompts/cx-engineer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -0
- package/specialists/prompts/cx-rd-lead.md +1 -0
- package/specialists/prompts/cx-sre.md +1 -0
- package/specialists/registry.json +20 -11
- package/specialists/role-manifests.json +1 -1
- package/templates/docs/test-plan.md +96 -0
package/README.md
CHANGED
|
@@ -32,9 +32,11 @@ Initialize a project:
|
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
cd ~/your-project
|
|
35
|
-
construct init --
|
|
35
|
+
construct init --yes
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
`construct init` scaffolds the project (`.cx/`, `AGENTS.md`, `plan.md`, adapters) and starts the local services by default. Pass `--no-start` to skip service startup, or `--interactive` for the guided flow.
|
|
39
|
+
|
|
38
40
|
Open your editor and talk to `@construct`. A walkthrough lives in `construct_guide.md` at your project root.
|
|
39
41
|
|
|
40
42
|
No Node? Try `brew install geraldmaron/construct/construct`. Cloning a project that already uses Construct? `npx -y @geraldmaron/construct init` wires it up.
|
|
@@ -155,6 +157,7 @@ The embed daemon writes its supervisor stdout log to `~/.cx/runtime/embed-daemon
|
|
|
155
157
|
|
|
156
158
|
| Command | What it does |
|
|
157
159
|
|---|---|
|
|
160
|
+
| `construct capability` | Describe what this Construct install can do (embedded contract; read-only, secret-free) |
|
|
158
161
|
| `construct claude:allow` | Manage Claude Code `permissions.allow` from the outside (auto-classifier blocks the agent from editing it) |
|
|
159
162
|
| `construct hosts` | Show host support for Construct orchestration |
|
|
160
163
|
| `construct mcp` | Manage MCP integrations |
|
package/bin/construct
CHANGED
|
@@ -165,12 +165,14 @@ async function showInteractiveMenu() {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
function runNodeScript(scriptPath, args = [], extraEnv = {}) {
|
|
168
|
+
function runNodeScript(scriptPath, args = [], extraEnv = {}, { exitOnError = true } = {}) {
|
|
169
169
|
const result = spawnSync(process.execPath, [scriptPath, ...args], {
|
|
170
170
|
stdio: 'inherit',
|
|
171
171
|
env: { ...process.env, ...extraEnv },
|
|
172
172
|
});
|
|
173
|
-
|
|
173
|
+
const status = result.status ?? 1;
|
|
174
|
+
if (status !== 0 && exitOnError) process.exit(status);
|
|
175
|
+
return status;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
async function cmdStatus() {
|
|
@@ -238,12 +240,24 @@ async function cmdShow() {
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
async function cmdSync(args) {
|
|
243
|
+
const { startOpLog } = await import('../lib/op-log.mjs');
|
|
244
|
+
const opLog = startOpLog('sync', { homeDir: HOME });
|
|
245
|
+
opLog.event('args', { args });
|
|
246
|
+
|
|
241
247
|
generateCompletions();
|
|
242
|
-
runNodeScript(path.join(ROOT_DIR, 'scripts', 'sync-specialists.mjs'), args);
|
|
243
|
-
if (
|
|
248
|
+
const syncStatus = runNodeScript(path.join(ROOT_DIR, 'scripts', 'sync-specialists.mjs'), args, {}, { exitOnError: false });
|
|
249
|
+
if (syncStatus !== 0) {
|
|
250
|
+
opLog.close('failed', { stage: 'sync-specialists', status: syncStatus });
|
|
251
|
+
process.exit(syncStatus);
|
|
252
|
+
}
|
|
253
|
+
if (args.includes('--no-docs')) {
|
|
254
|
+
opLog.close('ok', { docs: 'skipped' });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
244
257
|
const { regenerateDocs } = await import('../lib/auto-docs.mjs');
|
|
245
258
|
const { changed } = await regenerateDocs({ rootDir: ROOT_DIR });
|
|
246
259
|
for (const f of changed) ok(`Updated ${path.relative(ROOT_DIR, f)}`);
|
|
260
|
+
opLog.close('ok', { docsChanged: changed.length });
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
async function cmdCompletions(args) {
|
|
@@ -498,9 +512,9 @@ async function cmdDoctor() {
|
|
|
498
512
|
|
|
499
513
|
// (Pre-push bypass doctor check removed in v1.0.16.) v1.0.15 deleted the
|
|
500
514
|
// entire skip-var infrastructure (CONSTRUCT_SKIP_PREPUSH, _COMMENT_LINT,
|
|
501
|
-
// _PR_LINT, _DOCS, CONSTRUCT_ALLOW_CLAUDE_PUSH).
|
|
502
|
-
//
|
|
503
|
-
//
|
|
515
|
+
// _PR_LINT, _DOCS, CONSTRUCT_ALLOW_CLAUDE_PUSH). Nothing writes to
|
|
516
|
+
// ~/.construct/audit/prepush-bypass.log, so any entries there are stale
|
|
517
|
+
// and would otherwise surface the warning indefinitely. The file itself
|
|
504
518
|
// is harmless — next cleanup tick may sweep it.
|
|
505
519
|
|
|
506
520
|
// Embed daemon log size. The daemon's own scheduler rotates every
|
|
@@ -1071,15 +1085,77 @@ async function cmdDoctor() {
|
|
|
1071
1085
|
if (failCount > 0) process.exit(1);
|
|
1072
1086
|
}
|
|
1073
1087
|
|
|
1074
|
-
|
|
1088
|
+
// Resolve which services `construct dev` should start. Returns null (start
|
|
1089
|
+
// everything) unless --only= or --select narrows it. Unknown --only keys fail
|
|
1090
|
+
// loud rather than silently starting the wrong set; --select on a non-TTY is an
|
|
1091
|
+
// error pointing at --only so scripts get a deterministic path.
|
|
1092
|
+
async function resolveServiceSelection(args, selectable) {
|
|
1093
|
+
const validKeys = new Set(selectable.map((s) => s.key));
|
|
1094
|
+
const onlyArg = args.find((a) => a.startsWith('--only='));
|
|
1095
|
+
|
|
1096
|
+
if (onlyArg) {
|
|
1097
|
+
const keys = onlyArg.slice('--only='.length).split(',').map((k) => k.trim()).filter(Boolean);
|
|
1098
|
+
const unknown = keys.filter((k) => !validKeys.has(k));
|
|
1099
|
+
if (unknown.length) {
|
|
1100
|
+
errorln(`Unknown service(s) for --only: ${unknown.join(', ')}`);
|
|
1101
|
+
errorln(`Valid services: ${[...validKeys].join(', ')}`);
|
|
1102
|
+
process.exit(1);
|
|
1103
|
+
}
|
|
1104
|
+
if (keys.length === 0) {
|
|
1105
|
+
errorln('--only requires at least one service.');
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
return new Set(keys);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (args.includes('--select')) {
|
|
1112
|
+
if (!process.stdin.isTTY) {
|
|
1113
|
+
errorln('--select needs an interactive terminal. Use --only=<a,b,c> in scripts.');
|
|
1114
|
+
errorln(`Valid services: ${[...validKeys].join(', ')}`);
|
|
1115
|
+
process.exit(1);
|
|
1116
|
+
}
|
|
1117
|
+
const { multiSelect } = await import('../lib/tty-prompts.mjs');
|
|
1118
|
+
try {
|
|
1119
|
+
const chosen = await multiSelect({
|
|
1120
|
+
title: 'Start which services?',
|
|
1121
|
+
instructions: 'Pressure Guard and Doctor always run. Space toggles, Enter confirms.',
|
|
1122
|
+
options: selectable.map((s) => ({ label: s.label, value: s.key, description: s.description, checked: true })),
|
|
1123
|
+
});
|
|
1124
|
+
if (!chosen.length) {
|
|
1125
|
+
errorln('No services selected — nothing to start.');
|
|
1126
|
+
process.exit(1);
|
|
1127
|
+
}
|
|
1128
|
+
return new Set(chosen);
|
|
1129
|
+
} catch {
|
|
1130
|
+
errorln('Service selection canceled.');
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
async function cmdUp(args = []) {
|
|
1139
|
+
const { SELECTABLE_SERVICES } = await import('../lib/service-manager.mjs');
|
|
1140
|
+
|
|
1141
|
+
// --only=a,b,c picks services non-interactively; --select opens a checklist.
|
|
1142
|
+
// Both resolve to a Set of service keys (null = start everything).
|
|
1143
|
+
|
|
1144
|
+
const selected = await resolveServiceSelection(args, SELECTABLE_SERVICES);
|
|
1145
|
+
|
|
1146
|
+
const { startOpLog } = await import('../lib/op-log.mjs');
|
|
1147
|
+
const opLog = startOpLog('dev', { homeDir: HOME });
|
|
1148
|
+
opLog.event('selection', { services: selected ? [...selected] : 'all' });
|
|
1149
|
+
|
|
1075
1150
|
// Auto-magic health check — catch missing prerequisites before starting services
|
|
1076
1151
|
const { quickHealthCheck, detectCredentials, resolveCredentials } = await import('../lib/health-check.mjs');
|
|
1077
|
-
|
|
1152
|
+
|
|
1078
1153
|
// Resolve credentials from env, .env files, and 1Password before starting services
|
|
1079
1154
|
const resolvedCreds = await resolveCredentials({ homeDir: HOME });
|
|
1080
|
-
|
|
1155
|
+
|
|
1081
1156
|
const health = await quickHealthCheck({ homeDir: HOME, showCredentials: true });
|
|
1082
|
-
|
|
1157
|
+
opLog.event('prereq', { ok: health.ok, missing: health.missing ?? [] });
|
|
1158
|
+
|
|
1083
1159
|
if (!health.ok) {
|
|
1084
1160
|
errorln('Prerequisites missing for starting services:');
|
|
1085
1161
|
for (const item of health.missing) {
|
|
@@ -1087,10 +1163,12 @@ async function cmdUp() {
|
|
|
1087
1163
|
}
|
|
1088
1164
|
println('');
|
|
1089
1165
|
info('Run `construct install --yes` to install missing dependencies.');
|
|
1166
|
+
opLog.close('failed', { reason: 'prerequisites-missing' });
|
|
1090
1167
|
process.exit(1);
|
|
1091
1168
|
}
|
|
1092
|
-
|
|
1093
|
-
const { results, recovery } = await startServices({ rootDir: ROOT_DIR, homeDir: HOME });
|
|
1169
|
+
|
|
1170
|
+
const { results, recovery } = await startServices({ rootDir: ROOT_DIR, homeDir: HOME, selected });
|
|
1171
|
+
opLog.event('services', { results: results.map((r) => ({ name: r.name, status: r.status })) });
|
|
1094
1172
|
for (const svc of results) {
|
|
1095
1173
|
const label = svc.url ? `${svc.name} → ${svc.url}` : svc.name;
|
|
1096
1174
|
if (svc.status === 'started') ok(svc.note ? `${label} (${svc.note})` : label);
|
|
@@ -1128,8 +1206,10 @@ async function cmdUp() {
|
|
|
1128
1206
|
try {
|
|
1129
1207
|
const { runEmbedCli } = await import('../lib/embed/cli.mjs');
|
|
1130
1208
|
await runEmbedCli(['start'], { rootDir: ROOT_DIR });
|
|
1209
|
+
opLog.event('embed', { status: 'started' });
|
|
1131
1210
|
} catch (err) {
|
|
1132
1211
|
warn(`embed start failed: ${err.message} — check ~/.cx/runtime/embed-daemon.log`);
|
|
1212
|
+
opLog.warn('embed', { status: 'failed', error: err.message });
|
|
1133
1213
|
}
|
|
1134
1214
|
}
|
|
1135
1215
|
|
|
@@ -1176,6 +1256,9 @@ async function cmdUp() {
|
|
|
1176
1256
|
println('Dashboard: http://127.0.0.1:4242');
|
|
1177
1257
|
println('Run \x1b[32mconstruct\x1b[0m for commands');
|
|
1178
1258
|
println('');
|
|
1259
|
+
|
|
1260
|
+
const failed = results.filter((r) => r.status === 'error' || r.status === 'failed');
|
|
1261
|
+
opLog.close(failed.length ? 'degraded' : 'ok', { failed: failed.map((r) => r.name) });
|
|
1179
1262
|
}
|
|
1180
1263
|
|
|
1181
1264
|
async function cmdDown() {
|
|
@@ -1304,7 +1387,14 @@ async function cmdReview(args) {
|
|
|
1304
1387
|
process.exit(1);
|
|
1305
1388
|
}
|
|
1306
1389
|
|
|
1307
|
-
async function cmdInit(args) {
|
|
1390
|
+
async function cmdInit(args) {
|
|
1391
|
+
const { startOpLog } = await import('../lib/op-log.mjs');
|
|
1392
|
+
const opLog = startOpLog('init', { homeDir: HOME });
|
|
1393
|
+
opLog.event('args', { args });
|
|
1394
|
+
const status = runNodeScript(path.join(ROOT_DIR, 'lib', 'init-unified.mjs'), args, {}, { exitOnError: false });
|
|
1395
|
+
opLog.close(status === 0 ? 'ok' : 'failed', { status });
|
|
1396
|
+
if (status !== 0) process.exit(status);
|
|
1397
|
+
}
|
|
1308
1398
|
async function cmdDocsVerify(args) { runNodeScript(path.join(ROOT_DIR, 'lib', 'docs-verify.mjs'), args); }
|
|
1309
1399
|
async function cmdInitUpdate(args) {
|
|
1310
1400
|
runNodeScript(path.join(ROOT_DIR, 'lib', 'init-update.mjs'), args);
|
|
@@ -1747,7 +1837,22 @@ async function cmdHosts() {
|
|
|
1747
1837
|
printHostCapabilities();
|
|
1748
1838
|
}
|
|
1749
1839
|
}
|
|
1750
|
-
async function cmdSetup(args) {
|
|
1840
|
+
async function cmdSetup(args) {
|
|
1841
|
+
const { startOpLog } = await import('../lib/op-log.mjs');
|
|
1842
|
+
const opLog = startOpLog('install', { homeDir: HOME });
|
|
1843
|
+
opLog.event('args', { args });
|
|
1844
|
+
try {
|
|
1845
|
+
await runSetup({ rootDir: ROOT_DIR, args, homeDir: HOME });
|
|
1846
|
+
opLog.close('ok');
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
opLog.error('setup', { error: err.message });
|
|
1849
|
+
opLog.close('failed');
|
|
1850
|
+
errorln(`\nSetup did not complete: ${err.message}`);
|
|
1851
|
+
errorln(' -> Diagnose with: construct doctor');
|
|
1852
|
+
errorln(' -> Re-run setup: construct install (add --reconfigure to re-prompt for services)');
|
|
1853
|
+
process.exit(1);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1751
1856
|
|
|
1752
1857
|
function printConfigModeHelp() {
|
|
1753
1858
|
println('Construct config — inspect and set deployment posture.');
|
|
@@ -1841,6 +1946,45 @@ function formatIntakeTable(rows, { intakeQueueLabel, signalNoun } = { intakeQueu
|
|
|
1841
1946
|
for (const row of rows) println(fmt(row));
|
|
1842
1947
|
}
|
|
1843
1948
|
|
|
1949
|
+
// Embedded triage/planning surface — classifies an artifact and returns a
|
|
1950
|
+
// role-aware plan with no enqueue and no durable write. Shared by
|
|
1951
|
+
// `intake classify` and `graph recommend`. Input comes from --text, --file, or
|
|
1952
|
+
// stdin; --no-enqueue is accepted for explicitness (this path never enqueues).
|
|
1953
|
+
|
|
1954
|
+
async function cmdEmbeddedTriage(args) {
|
|
1955
|
+
const flag = (name) => { const i = args.indexOf(name); return i !== -1 ? args[i + 1] : undefined; };
|
|
1956
|
+
const fileArg = flag('--file');
|
|
1957
|
+
let input = flag('--text') || '';
|
|
1958
|
+
let ingestion = null;
|
|
1959
|
+
if (!input && fileArg) {
|
|
1960
|
+
const { resolveInput } = await import('../lib/embedded-contract/ingest.mjs');
|
|
1961
|
+
const resolved = await resolveInput({ filePath: fileArg });
|
|
1962
|
+
input = resolved.text;
|
|
1963
|
+
ingestion = resolved.error ? { ...(resolved.ingestion || {}), error: resolved.error } : resolved.ingestion;
|
|
1964
|
+
}
|
|
1965
|
+
if (!input && !process.stdin.isTTY) {
|
|
1966
|
+
input = fs.readFileSync(0, 'utf8');
|
|
1967
|
+
}
|
|
1968
|
+
if (!input.trim() && !ingestion?.error) {
|
|
1969
|
+
errorln('Usage: construct intake classify --json [--text "..." | --file <path> | <stdin>] [--source <path>] [--artifact-type <t>] [--available-roles a,b]');
|
|
1970
|
+
process.exit(1);
|
|
1971
|
+
}
|
|
1972
|
+
const request = {
|
|
1973
|
+
input,
|
|
1974
|
+
sourcePath: flag('--source') || (fileArg || ''),
|
|
1975
|
+
ingestion,
|
|
1976
|
+
artifactType: flag('--artifact-type'),
|
|
1977
|
+
domain: flag('--domain'),
|
|
1978
|
+
desiredOutcome: flag('--desired-outcome'),
|
|
1979
|
+
constraints: flag('--constraints')?.split(',').map((s) => s.trim()).filter(Boolean),
|
|
1980
|
+
availableRoles: flag('--available-roles')?.split(',').map((s) => s.trim()).filter(Boolean),
|
|
1981
|
+
};
|
|
1982
|
+
const { recommendPlan } = await import('../lib/embedded-contract/triage.mjs');
|
|
1983
|
+
const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
|
|
1984
|
+
const envelope = wrapContractResult(recommendPlan(request), { surface: 'cli' });
|
|
1985
|
+
println(JSON.stringify(envelope, null, 2));
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1844
1988
|
async function cmdIntake(args) {
|
|
1845
1989
|
const sub = args[0];
|
|
1846
1990
|
|
|
@@ -1849,6 +1993,10 @@ async function cmdIntake(args) {
|
|
|
1849
1993
|
return;
|
|
1850
1994
|
}
|
|
1851
1995
|
|
|
1996
|
+
if (sub === 'classify') {
|
|
1997
|
+
return await cmdEmbeddedTriage(args.slice(1));
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1852
2000
|
const { createIntakeQueue } = await import('../lib/intake/queue.mjs');
|
|
1853
2001
|
const { getRebrand } = await import('../lib/profiles/rebrand.mjs');
|
|
1854
2002
|
const cwd = process.cwd();
|
|
@@ -2941,6 +3089,10 @@ async function cmdGraph(args) {
|
|
|
2941
3089
|
const sub = args[0];
|
|
2942
3090
|
if (!sub || sub === '--help' || sub === '-h') { printGraphHelp(); return; }
|
|
2943
3091
|
|
|
3092
|
+
if (sub === 'recommend') {
|
|
3093
|
+
return await cmdEmbeddedTriage(args.slice(1));
|
|
3094
|
+
}
|
|
3095
|
+
|
|
2944
3096
|
const { FilesystemTaskGraphStore } = await import('../lib/task-graph/store.mjs');
|
|
2945
3097
|
const { generateTaskGraphFromTriage } = await import('../lib/task-graph/generate.mjs');
|
|
2946
3098
|
const cwd = process.cwd();
|
|
@@ -3174,6 +3326,23 @@ async function cmdValidate() {
|
|
|
3174
3326
|
}
|
|
3175
3327
|
|
|
3176
3328
|
async function cmdModels(args) {
|
|
3329
|
+
if (args[0] === 'resolve') {
|
|
3330
|
+
const flag = (name) => { const i = args.indexOf(name); return i !== -1 ? args[i + 1] : undefined; };
|
|
3331
|
+
const { resolveEmbeddedModel } = await import('../lib/embedded-contract/model-resolve.mjs');
|
|
3332
|
+
const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
|
|
3333
|
+
const request = {
|
|
3334
|
+
workflowType: flag('--workflow-type'),
|
|
3335
|
+
requestedTier: flag('--tier'),
|
|
3336
|
+
host: flag('--host'),
|
|
3337
|
+
hostModel: flag('--host-model'),
|
|
3338
|
+
hostProvider: flag('--host-provider'),
|
|
3339
|
+
capabilities: flag('--capabilities')?.split(',').map((s) => s.trim()).filter(Boolean),
|
|
3340
|
+
allowCrossProviderFallback: args.includes('--allow-cross-provider'),
|
|
3341
|
+
};
|
|
3342
|
+
const envelope = wrapContractResult(resolveEmbeddedModel(request), { surface: 'cli' });
|
|
3343
|
+
println(JSON.stringify(envelope, null, 2));
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3177
3346
|
const tier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? '';
|
|
3178
3347
|
const setModel = args.find((arg) => arg.startsWith('--set='))?.split('=')[1] ?? '';
|
|
3179
3348
|
const envPath = getUserEnvPath(HOME);
|
|
@@ -4571,9 +4740,56 @@ async function cmdProviders(args) {
|
|
|
4571
4740
|
process.exit(1);
|
|
4572
4741
|
}
|
|
4573
4742
|
|
|
4743
|
+
// Capability discovery surface — a read-only, secret-free description of the
|
|
4744
|
+
// installed Construct's capabilities, for embedding applications.
|
|
4745
|
+
|
|
4746
|
+
async function cmdCapability(args) {
|
|
4747
|
+
const sub = args[0];
|
|
4748
|
+
if (sub && sub !== 'describe' && !sub.startsWith('--')) {
|
|
4749
|
+
errorln(`Unknown capability subcommand: ${sub}. Available: describe`);
|
|
4750
|
+
process.exit(1);
|
|
4751
|
+
}
|
|
4752
|
+
const { buildCapabilityContract } = await import('../lib/embedded-contract/capability.mjs');
|
|
4753
|
+
const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
|
|
4754
|
+
const envelope = wrapContractResult(buildCapabilityContract({ rootDir: ROOT_DIR }), { surface: 'cli' });
|
|
4755
|
+
println(JSON.stringify(envelope, null, 2));
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4574
4758
|
async function cmdWorkflow(args) {
|
|
4575
4759
|
const sub = args[0] || 'list';
|
|
4576
4760
|
|
|
4761
|
+
if (sub === 'invoke') {
|
|
4762
|
+
const rest = args.slice(1);
|
|
4763
|
+
const flag = (name) => { const i = rest.indexOf(name); return i !== -1 ? rest[i + 1] : undefined; };
|
|
4764
|
+
let input = flag('--text') || '';
|
|
4765
|
+
const fileArg = flag('--file');
|
|
4766
|
+
let ingestion = null;
|
|
4767
|
+
if (!input && fileArg) {
|
|
4768
|
+
const { resolveInput } = await import('../lib/embedded-contract/ingest.mjs');
|
|
4769
|
+
const resolved = await resolveInput({ filePath: fileArg });
|
|
4770
|
+
input = resolved.text;
|
|
4771
|
+
ingestion = resolved.error ? { ...(resolved.ingestion || {}), error: resolved.error } : resolved.ingestion;
|
|
4772
|
+
}
|
|
4773
|
+
if (!input && !process.stdin.isTTY) input = fs.readFileSync(0, 'utf8');
|
|
4774
|
+
const request = {
|
|
4775
|
+
workflowType: flag('--workflow-type'),
|
|
4776
|
+
input,
|
|
4777
|
+
ingestion,
|
|
4778
|
+
roleStrategy: flag('--role-strategy'),
|
|
4779
|
+
requestedRoles: flag('--roles')?.split(',').map((s) => s.trim()).filter(Boolean),
|
|
4780
|
+
approvalMode: flag('--approval-mode'),
|
|
4781
|
+
trace: !rest.includes('--no-trace'),
|
|
4782
|
+
host: flag('--host'),
|
|
4783
|
+
hostModel: flag('--host-model'),
|
|
4784
|
+
hostProvider: flag('--host-provider'),
|
|
4785
|
+
};
|
|
4786
|
+
const { invokeWorkflow } = await import('../lib/embedded-contract/workflow-invoke.mjs');
|
|
4787
|
+
const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
|
|
4788
|
+
const envelope = wrapContractResult(await invokeWorkflow(request), { surface: 'cli' });
|
|
4789
|
+
println(JSON.stringify(envelope, null, 2));
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4577
4793
|
if (sub === 'list') {
|
|
4578
4794
|
const { existsSync, readdirSync } = await import('node:fs');
|
|
4579
4795
|
const { join } = await import('node:path');
|
|
@@ -4951,6 +5167,7 @@ const handlers = new Map([
|
|
|
4951
5167
|
['creds', cmdCreds],
|
|
4952
5168
|
['providers', cmdProviders],
|
|
4953
5169
|
['workflow', cmdWorkflow],
|
|
5170
|
+
['capability', cmdCapability],
|
|
4954
5171
|
['tags', cmdTags],
|
|
4955
5172
|
['scheduler', cmdScheduler],
|
|
4956
5173
|
['lint:research', cmdLintResearch],
|
|
@@ -4994,13 +5211,11 @@ const handlers = new Map([
|
|
|
4994
5211
|
['roles:list', async (args) => {
|
|
4995
5212
|
const { listRoles, formatRoleList } = await import('../lib/roles/catalog.mjs');
|
|
4996
5213
|
const json = args.includes('--json');
|
|
4997
|
-
|
|
4998
|
-
const consolidated = args.includes('--consolidated');
|
|
4999
|
-
|
|
5214
|
+
|
|
5000
5215
|
if (json) {
|
|
5001
|
-
println(JSON.stringify(listRoles(
|
|
5216
|
+
println(JSON.stringify(listRoles(), null, 2));
|
|
5002
5217
|
} else {
|
|
5003
|
-
process.stdout.write(formatRoleList(
|
|
5218
|
+
process.stdout.write(formatRoleList());
|
|
5004
5219
|
}
|
|
5005
5220
|
}],
|
|
5006
5221
|
['roles:set', async (args) => {
|
|
@@ -5921,7 +6136,7 @@ if (!handler) {
|
|
|
5921
6136
|
// commands like `dev`, `stop`, `init`, `uninstall` silently ignore --help
|
|
5922
6137
|
// and execute (running `construct stop --help` actually stops services).
|
|
5923
6138
|
// Every command — including internal ones — gets the same shape via
|
|
5924
|
-
// formatCommandHelp(); per-command handlers
|
|
6139
|
+
// formatCommandHelp(); per-command handlers need no opt-in.
|
|
5925
6140
|
|
|
5926
6141
|
if (rest.includes('--help') || rest.includes('-h')) {
|
|
5927
6142
|
process.stdout.write(formatCommandHelp(command, { colors: true }));
|
|
@@ -34,6 +34,18 @@ const PKG_VERSION = (() => {
|
|
|
34
34
|
|
|
35
35
|
const log = (msg) => process.stdout.write(`[construct-postinstall] ${msg}\n`);
|
|
36
36
|
|
|
37
|
+
// Failures go to stderr as a prominent, actionable block so they are visible in
|
|
38
|
+
// npm output instead of scrolling past as a benign-looking line. The script
|
|
39
|
+
// stays exit-0 by design (see the staging catch): best-effort adapter staging
|
|
40
|
+
// must not hard-fail a consumer's `npm install` — the package is installed and
|
|
41
|
+
// usable, and `construct init` completes staging on demand.
|
|
42
|
+
|
|
43
|
+
const fail = (msg, hint) => {
|
|
44
|
+
process.stderr.write(`\n[construct-postinstall] WARNING: ${msg}\n`);
|
|
45
|
+
if (hint) process.stderr.write(`[construct-postinstall] -> ${hint}\n`);
|
|
46
|
+
process.stderr.write('\n');
|
|
47
|
+
};
|
|
48
|
+
|
|
37
49
|
if (process.env.CONSTRUCT_SKIP_POSTINSTALL === '1') {
|
|
38
50
|
log('skipping (CONSTRUCT_SKIP_POSTINSTALL=1)');
|
|
39
51
|
process.exit(0);
|
|
@@ -72,7 +84,7 @@ if (process.env.npm_config_global === 'true' || process.env.npm_config_global ==
|
|
|
72
84
|
stdio: 'inherit',
|
|
73
85
|
});
|
|
74
86
|
if (result.status !== 0) {
|
|
75
|
-
|
|
87
|
+
fail(`Global front-door sync failed (exit ${result.status}).`, 'Run `construct sync --global` to complete it.');
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
process.exit(0);
|
|
@@ -103,6 +115,9 @@ try {
|
|
|
103
115
|
log,
|
|
104
116
|
});
|
|
105
117
|
} catch (err) {
|
|
106
|
-
|
|
118
|
+
fail(`Adapter staging failed: ${err.message}`, 'The package is installed; run `npx construct init` in this project to complete setup.');
|
|
119
|
+
// Intentionally exit 0: staging is best-effort completion, and a non-zero exit
|
|
120
|
+
// here would fail the consumer's entire `npm install`. The failure is loud on
|
|
121
|
+
// stderr above and recoverable via `construct init`.
|
|
107
122
|
process.exit(0);
|
|
108
123
|
}
|
package/commands/design/flow.md
CHANGED
|
@@ -8,3 +8,5 @@ Produce:
|
|
|
8
8
|
- JOBS-TO-BE-DONE: "When [situation], I want to [motivation], so I can [outcome]."
|
|
9
9
|
- FRICTION MAP: 5 likely points where users get stuck, confused, or quit
|
|
10
10
|
- DESIGN-DRIVING QUESTIONS: a small set of questions (typically 3-7) whose answers would change layout, flow, or interaction decisions
|
|
11
|
+
|
|
12
|
+
To capture the flow as a committed artifact, run `construct wireframe "<flow description>" --type=flow` (or `--type=user-journey` for a persona funnel) to emit a diffable Mermaid diagram under `.cx/wireframes/`. Stay text-first (no new diagramming dependency).
|
package/commands/design/ui.md
CHANGED
|
@@ -10,3 +10,5 @@ Every meaningful UI surface needs:
|
|
|
10
10
|
- WCAG AA baseline: keyboard-navigable, 4.5:1 contrast, visible focus, ARIA labels
|
|
11
11
|
|
|
12
12
|
Does it look intentional, or like a default template? Flag experience drift.
|
|
13
|
+
|
|
14
|
+
For a layout sketch, run `construct wireframe "<screen description>" --type=layout` to produce a committed low-fi HTML/Mermaid artifact under `.cx/wireframes/`, then refine it. Stay text-first (no new diagramming dependency). Valid `--type` values: layout, flow, state, sequence, er, user-journey.
|
package/lib/cli-commands.mjs
CHANGED
|
@@ -20,7 +20,11 @@ export const CLI_COMMANDS = [
|
|
|
20
20
|
category: 'Core',
|
|
21
21
|
core: true,
|
|
22
22
|
description: 'Start services for development',
|
|
23
|
-
usage: 'construct dev',
|
|
23
|
+
usage: 'construct dev [--select] [--only=postgres,dashboard,...]',
|
|
24
|
+
options: [
|
|
25
|
+
{ flag: '--select', desc: 'Pick which services to start from an interactive checklist' },
|
|
26
|
+
{ flag: '--only=<a,b,c>', desc: 'Start only the named services (postgres, dashboard, telemetry, memory, opencode)' },
|
|
27
|
+
],
|
|
24
28
|
},
|
|
25
29
|
{
|
|
26
30
|
name: 'stop',
|
|
@@ -47,10 +51,11 @@ export const CLI_COMMANDS = [
|
|
|
47
51
|
category: 'Core',
|
|
48
52
|
core: true,
|
|
49
53
|
description: 'Machine setup (once per machine): Docker, cm/cass, config, embeddings',
|
|
50
|
-
usage: 'construct install [--yes] [--no-docker]',
|
|
54
|
+
usage: 'construct install [--yes] [--no-docker] [--reconfigure]',
|
|
51
55
|
options: [
|
|
52
|
-
{ flag: '--yes',
|
|
53
|
-
{ flag: '--no-docker',
|
|
56
|
+
{ flag: '--yes', desc: 'Apply defaults without prompts' },
|
|
57
|
+
{ flag: '--no-docker', desc: 'Skip Docker-based service setup (local Postgres)' },
|
|
58
|
+
{ flag: '--reconfigure', desc: 'Re-prompt for service consent, ignoring cached answers' },
|
|
54
59
|
],
|
|
55
60
|
},
|
|
56
61
|
{
|
|
@@ -95,7 +100,16 @@ export const CLI_COMMANDS = [
|
|
|
95
100
|
category: 'Core',
|
|
96
101
|
core: true,
|
|
97
102
|
description: 'View and process the active profile\'s intake queue (queue label varies by profile)',
|
|
98
|
-
usage: 'construct intake list|show|done|skip',
|
|
103
|
+
usage: 'construct intake list|show|done|skip|reopen|integrate|classify',
|
|
104
|
+
subcommands: [
|
|
105
|
+
{ name: 'list', desc: 'List pending packets' },
|
|
106
|
+
{ name: 'show <id>', desc: 'Show one packet (triage, related docs, excerpt, tag suggestions)' },
|
|
107
|
+
{ name: 'done <id> [--output=<path>]', desc: 'Mark processed; optionally stamp the produced artifact' },
|
|
108
|
+
{ name: 'skip <id> [--reason=…]', desc: 'Drop without action; preserves audit trail' },
|
|
109
|
+
{ name: 'reopen <id>', desc: 'Move a processed or skipped packet back to pending' },
|
|
110
|
+
{ name: 'integrate <id> <github|jira|confluence> [--publish-issues]', desc: 'Create an external ticket from a packet (--publish-issues unlocks the demo-source gate)' },
|
|
111
|
+
{ name: 'classify --json [--text|--file|<stdin>]', desc: 'Classify an artifact and return a role-aware plan without enqueuing (embedded contract)' },
|
|
112
|
+
],
|
|
99
113
|
},
|
|
100
114
|
{
|
|
101
115
|
name: 'recommendations',
|
|
@@ -207,7 +221,17 @@ export const CLI_COMMANDS = [
|
|
|
207
221
|
category: 'Core',
|
|
208
222
|
core: true,
|
|
209
223
|
description: 'Check installation health',
|
|
210
|
-
usage: 'construct doctor [--fix-legacy-agents]',
|
|
224
|
+
usage: 'construct doctor [<status|logs|tick|report|consistency|watch|stop|credentials>] [--fix-legacy-agents]',
|
|
225
|
+
subcommands: [
|
|
226
|
+
{ name: 'status', desc: 'Doctor daemon status' },
|
|
227
|
+
{ name: 'logs', desc: 'Tail doctor daemon logs' },
|
|
228
|
+
{ name: 'tick', desc: 'Run one doctor daemon check cycle now' },
|
|
229
|
+
{ name: 'report', desc: 'Print the latest health report' },
|
|
230
|
+
{ name: 'consistency', desc: 'Run cross-surface consistency checks' },
|
|
231
|
+
{ name: 'watch', desc: 'Start the doctor daemon (continuous checks)' },
|
|
232
|
+
{ name: 'stop', desc: 'Stop the doctor daemon' },
|
|
233
|
+
{ name: 'credentials', desc: 'Diagnose provider credential resolution' },
|
|
234
|
+
],
|
|
211
235
|
options: [
|
|
212
236
|
{ flag: '--fix-legacy-agents', desc: 'Sweep legacy cx-*.md agents at user scope and re-sync' },
|
|
213
237
|
],
|
|
@@ -266,7 +290,10 @@ export const CLI_COMMANDS = [
|
|
|
266
290
|
category: 'Work',
|
|
267
291
|
core: false,
|
|
268
292
|
description: 'Task graph management',
|
|
269
|
-
usage: 'construct graph <show|
|
|
293
|
+
usage: 'construct graph <list|show|from-intake|recommend>',
|
|
294
|
+
subcommands: [
|
|
295
|
+
{ name: 'recommend --json [--text|--file|<stdin>]', desc: 'Return a role-aware plan for an artifact without enqueuing (embedded contract; alias of intake classify)' },
|
|
296
|
+
],
|
|
270
297
|
},
|
|
271
298
|
{
|
|
272
299
|
name: 'models',
|
|
@@ -274,7 +301,7 @@ export const CLI_COMMANDS = [
|
|
|
274
301
|
category: 'Models & Integrations',
|
|
275
302
|
core: false,
|
|
276
303
|
description: 'Show or update model tier assignments',
|
|
277
|
-
usage: 'construct models <list|set|free|reset|usage|cost>',
|
|
304
|
+
usage: 'construct models <list|set|free|reset|usage|cost|resolve>',
|
|
278
305
|
subcommands: [
|
|
279
306
|
{ name: 'list', desc: 'Show current tier assignments' },
|
|
280
307
|
{ name: 'set --tier=<reasoning|standard|fast> --model=<model>', desc: 'Set a model for a tier' },
|
|
@@ -282,6 +309,18 @@ export const CLI_COMMANDS = [
|
|
|
282
309
|
{ name: 'reset', desc: 'Reset all tier assignments' },
|
|
283
310
|
{ name: 'usage', desc: 'Show token usage per tier' },
|
|
284
311
|
{ name: 'cost', desc: 'Show cost breakdown' },
|
|
312
|
+
{ name: 'resolve --json', desc: 'Resolve the model for an embedded workflow given host context' },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: 'capability',
|
|
317
|
+
emoji: '🧩',
|
|
318
|
+
category: 'Models & Integrations',
|
|
319
|
+
core: false,
|
|
320
|
+
description: 'Describe what this Construct install can do (embedded contract; read-only, secret-free)',
|
|
321
|
+
usage: 'construct capability describe --json',
|
|
322
|
+
subcommands: [
|
|
323
|
+
{ name: 'describe --json', desc: 'Emit versions, interfaces, roles, skills, workflows, schemas, models, policies, telemetry, plugins' },
|
|
285
324
|
],
|
|
286
325
|
},
|
|
287
326
|
{
|
|
@@ -304,7 +343,14 @@ export const CLI_COMMANDS = [
|
|
|
304
343
|
category: 'Models & Integrations',
|
|
305
344
|
core: false,
|
|
306
345
|
description: 'Manage external Construct plugin manifests',
|
|
307
|
-
usage: 'construct plugin <list|info|init>',
|
|
346
|
+
usage: 'construct plugin <list|info|init|validate|engine>',
|
|
347
|
+
subcommands: [
|
|
348
|
+
{ name: 'list', desc: 'List discovered plugin manifests' },
|
|
349
|
+
{ name: 'info <id>', desc: 'Show details for one plugin' },
|
|
350
|
+
{ name: 'init', desc: 'Scaffold a new plugin manifest' },
|
|
351
|
+
{ name: 'validate', desc: 'Validate a plugin manifest against the schema' },
|
|
352
|
+
{ name: 'engine', desc: 'Plugin engine operations' },
|
|
353
|
+
],
|
|
308
354
|
},
|
|
309
355
|
{
|
|
310
356
|
name: 'hosts',
|
|
@@ -487,10 +533,13 @@ export const CLI_COMMANDS = [
|
|
|
487
533
|
category: 'Advanced',
|
|
488
534
|
core: false,
|
|
489
535
|
description: 'Remove Construct state',
|
|
490
|
-
usage: 'construct uninstall [--yes] [--all]',
|
|
536
|
+
usage: 'construct uninstall [--dry-run] [--yes] [--all] [--keep-state] [--scope=project|machine|all]',
|
|
491
537
|
options: [
|
|
492
|
-
{ flag: '--
|
|
493
|
-
{ flag: '--
|
|
538
|
+
{ flag: '--dry-run', desc: 'Print the plan and exit; change nothing' },
|
|
539
|
+
{ flag: '--yes', desc: 'Remove auto-risk (✓) categories without prompting' },
|
|
540
|
+
{ flag: '--all', desc: 'Combined with --yes: also remove ask-risk (◐) categories (project data, machine config)' },
|
|
541
|
+
{ flag: '--keep-state', desc: 'Only remove the launcher + adapters; preserve .cx/, ~/.construct, Postgres' },
|
|
542
|
+
{ flag: '--scope=<...>', desc: 'Limit to project | machine | all (default: all)' },
|
|
494
543
|
],
|
|
495
544
|
},
|
|
496
545
|
{
|
|
@@ -675,7 +724,10 @@ export const CLI_COMMANDS = [
|
|
|
675
724
|
category: 'Work',
|
|
676
725
|
core: false,
|
|
677
726
|
description: 'Instantiate workflow templates (PRD-to-review chains, onboarding, handoffs)',
|
|
678
|
-
usage: 'construct workflow <list|show|new>',
|
|
727
|
+
usage: 'construct workflow <list|show|new|invoke>',
|
|
728
|
+
subcommands: [
|
|
729
|
+
{ name: 'invoke --json --workflow-type <t> [--text|--file|<stdin>]', desc: 'Invoke a workflow (roles/skills) non-interactively with approval gating and provenance (embedded contract)' },
|
|
730
|
+
],
|
|
679
731
|
},
|
|
680
732
|
{
|
|
681
733
|
name: 'telemetry',
|
package/lib/embed/semantic.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/embed/semantic.mjs — Semantic analysis for intake signals.
|
|
3
3
|
*
|
|
4
|
-
* Generates embeddings via @
|
|
4
|
+
* Generates embeddings via @huggingface/transformers, caches them to disk,
|
|
5
5
|
* computes cosine similarity, and clusters related signals by topic.
|
|
6
6
|
* All local — no external API calls. Default model is all-MiniLM-L6-v2
|
|
7
7
|
* (384-dimensional vectors, ~50MB on disk, fast inference).
|
|
@@ -23,7 +23,7 @@ import { homedir } from 'node:os';
|
|
|
23
23
|
|
|
24
24
|
const CACHE_DIR = join(homedir(), '.cx', 'cache', 'embeddings');
|
|
25
25
|
const INDEX_FILE = join(CACHE_DIR, 'index.json');
|
|
26
|
-
const MODEL_NAME = 'all-MiniLM-L6-v2';
|
|
26
|
+
const MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';
|
|
27
27
|
const DIMENSIONS = 384;
|
|
28
28
|
const LOG = process.env.CONSTRUCT_DEBUG_EMBED === '1';
|
|
29
29
|
|
|
@@ -35,7 +35,9 @@ let embedder = null;
|
|
|
35
35
|
async function getEmbedder() {
|
|
36
36
|
if (embedder !== null) return embedder;
|
|
37
37
|
try {
|
|
38
|
-
const { pipeline } = await import('@
|
|
38
|
+
const { pipeline, env: hfEnv } = await import('@huggingface/transformers');
|
|
39
|
+
hfEnv.allowLocalModels = true;
|
|
40
|
+
hfEnv.useBrowserCache = false;
|
|
39
41
|
embedder = await pipeline('feature-extraction', MODEL_NAME, {
|
|
40
42
|
quantized: true,
|
|
41
43
|
});
|