@geraldmaron/construct 1.0.2 → 1.0.4

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.
Files changed (97) hide show
  1. package/.env.example +1 -1
  2. package/README.md +4 -4
  3. package/agents/prompts/cx-ai-engineer.md +6 -26
  4. package/agents/prompts/cx-architect.md +1 -0
  5. package/agents/prompts/cx-business-strategist.md +2 -0
  6. package/agents/prompts/cx-data-analyst.md +6 -26
  7. package/agents/prompts/cx-docs-keeper.md +1 -31
  8. package/agents/prompts/cx-explorer.md +1 -0
  9. package/agents/prompts/cx-orchestrator.md +40 -112
  10. package/agents/prompts/cx-platform-engineer.md +2 -22
  11. package/agents/prompts/cx-product-manager.md +2 -1
  12. package/agents/prompts/cx-qa.md +0 -20
  13. package/agents/prompts/cx-rd-lead.md +2 -0
  14. package/agents/prompts/cx-researcher.md +77 -31
  15. package/agents/prompts/cx-security.md +11 -49
  16. package/agents/prompts/cx-sre.md +9 -43
  17. package/agents/prompts/cx-ux-researcher.md +1 -0
  18. package/agents/role-manifests.json +4 -4
  19. package/bin/construct +72 -11
  20. package/bin/construct-postinstall.mjs +1 -1
  21. package/db/schema/004_recommendations.sql +46 -0
  22. package/db/schema/005_strategy.sql +21 -0
  23. package/lib/auto-docs.mjs +1 -2
  24. package/lib/beads-automation.mjs +16 -7
  25. package/lib/bootstrap/resources.mjs +2 -2
  26. package/lib/cli-commands.mjs +8 -2
  27. package/lib/document-ingest.mjs +6 -5
  28. package/lib/embed/cli.mjs +16 -3
  29. package/lib/embed/conflict-detection.mjs +26 -9
  30. package/lib/embed/customer-profiles.mjs +38 -18
  31. package/lib/embed/daemon.mjs +59 -50
  32. package/lib/embed/inbox.mjs +30 -0
  33. package/lib/embed/recommendation-store.mjs +214 -15
  34. package/lib/embed/workspaces.mjs +53 -18
  35. package/lib/evaluator-optimizer.mjs +0 -2
  36. package/lib/features.mjs +11 -0
  37. package/lib/gates-audit.mjs +3 -3
  38. package/lib/health-check.mjs +3 -5
  39. package/lib/hooks/pre-compact.mjs +3 -0
  40. package/lib/hooks/read-tracker.mjs +10 -101
  41. package/lib/host-capabilities.mjs +90 -1
  42. package/lib/init-unified.mjs +119 -3
  43. package/lib/init-update.mjs +246 -131
  44. package/lib/install/first-invocation.mjs +4 -4
  45. package/lib/intake/queue.mjs +1 -1
  46. package/lib/integrations/intake-integrations.mjs +4 -5
  47. package/lib/intent-classifier.mjs +1 -0
  48. package/lib/knowledge/layout.mjs +10 -0
  49. package/lib/knowledge/rag.mjs +16 -0
  50. package/lib/mcp/tools/telemetry.mjs +30 -78
  51. package/lib/model-cheapest-provider.mjs +231 -0
  52. package/lib/model-router.mjs +68 -9
  53. package/lib/ollama-manager.mjs +1 -1
  54. package/lib/opencode-telemetry.mjs +4 -5
  55. package/lib/orchestration-policy.mjs +9 -0
  56. package/lib/parity.mjs +124 -21
  57. package/lib/project-profile.mjs +1 -1
  58. package/lib/prompt-composer.js +106 -29
  59. package/lib/read-tracker-store.mjs +149 -0
  60. package/lib/roles/catalog.mjs +133 -0
  61. package/lib/roles/preference.mjs +74 -0
  62. package/lib/server/index.mjs +109 -47
  63. package/lib/server/insights.mjs +1 -1
  64. package/lib/server/telemetry-login.mjs +17 -25
  65. package/lib/service-manager.mjs +32 -24
  66. package/lib/services/local-postgres.mjs +15 -0
  67. package/lib/services/telemetry-backend.mjs +2 -3
  68. package/lib/setup-prompts.mjs +2 -2
  69. package/lib/setup.mjs +55 -46
  70. package/lib/status.mjs +56 -8
  71. package/lib/storage/backend.mjs +12 -2
  72. package/lib/storage/postgres-backup.mjs +1 -1
  73. package/lib/strategy-store.mjs +371 -0
  74. package/lib/telemetry/backends/local.mjs +6 -4
  75. package/lib/telemetry/client.mjs +185 -0
  76. package/lib/telemetry/ingest.mjs +13 -5
  77. package/lib/telemetry/team-rollup.mjs +9 -2
  78. package/lib/uninstall/uninstall.mjs +1 -1
  79. package/lib/worker/trace.mjs +17 -27
  80. package/package.json +5 -2
  81. package/rules/common/research.md +44 -12
  82. package/skills/docs/backlog-proposal-workflow.md +2 -2
  83. package/skills/docs/customer-profile-workflow.md +1 -1
  84. package/skills/docs/evidence-ingest-workflow.md +5 -5
  85. package/skills/docs/prfaq-workflow.md +1 -1
  86. package/skills/docs/product-intelligence-review.md +1 -1
  87. package/skills/docs/product-intelligence-workflow.md +3 -3
  88. package/skills/docs/product-signal-workflow.md +48 -18
  89. package/skills/docs/research-workflow.md +26 -14
  90. package/skills/docs/strategy-workflow.md +36 -0
  91. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  92. package/skills/roles/researcher.md +28 -15
  93. package/skills/routing.md +8 -1
  94. package/templates/docs/construct_guide.md +2 -2
  95. package/templates/docs/research-brief.md +63 -9
  96. package/templates/docs/strategy.md +36 -0
  97. package/templates/homebrew/construct.rb +7 -7
@@ -1,46 +1,92 @@
1
- You have been burned enough times by stale documentation to never trust recall alone. Training knowledge has a cutoff; the world doesn't. If you can't cite a primary source with a date, the claim is a belief, not a finding.
1
+ You have been burned enough times by stale, uncited, or hallucinated sources to treat every unverified claim as a liability. Training knowledge has a cutoff; the world does not. You operate at the standard of a principal researcher or senior academic: every load-bearing claim is traceable to a verifiable primary source with a date, every inference is labeled as such, and every URL has been fetched and confirmed to exist.
2
+
3
+ **Scope boundary** — you handle: external technical evidence, market and competitive research, academic literature, vendor documentation, security advisories, and quantitative benchmarks. For user behavioral research, handoff to `cx-ux-researcher`. For hypothesis design and experiment planning, handoff to `cx-rd-lead`.
2
4
 
3
5
  **What you're instinctively suspicious of:**
4
- - Version-specific claims without a cited source
5
- - "Everyone knows" or "the standard way" without a reference
6
- - Documentation that might be for a different version than the one in use
7
- - Blog posts treated as authoritative
8
- - Research that stopped when the first answer looked plausible
6
+ - Any claim without a publication date on a fast-moving topic
7
+ - URLs included but not fetched an unfetched URL is an unverified claim
8
+ - Stopping research when the first plausible result appears
9
+ - Blog posts or summaries cited in place of the underlying paper, spec, or changelog
10
+ - "Everyone knows" or "the standard approach" as a substitute for a citation
11
+ - Research that confirms the original hypothesis without seriously engaging counter-evidence
9
12
 
10
- **Your productive tension**: cx-rd-lead — R&D lead has hypotheses; you insist on primary source evidence before treating them as validated
13
+ **Your productive tension**: cx-rd-lead — R&D lead has hypotheses; you supply primary-source evidence before those hypotheses are treated as validated
11
14
 
12
- **Your opening question**: What is the version, the publication date, and the primary source?
15
+ **Your opening question**: What is the specific falsifiable claim, what is the most recent authoritative source for it, and what does the strongest counter-evidence say?
13
16
 
14
- **Failure mode warning**: If all your sources are secondhand or undated, the research isn't done. Find the primary source.
17
+ **Failure mode warning**: If your sources are secondhand, undated, or unfetched, the research is not complete. A confident-sounding synthesis of weak sources is worse than an honest "insufficient evidence."
15
18
 
16
19
  **Role guidance**: call `get_skill("roles/researcher")` before drafting.
17
20
 
18
- Start order:
19
- 1. Internal project evidence first: `.cx/research/`, `.cx/product-intel/`, `docs/prd/`, `docs/meta-prd/`, ADRs, runbooks, ingested artifacts, repo code/config/tests
20
- 2. Primary external sources second
21
- 3. Secondary sources third
22
- 4. Tertiary sources only as discovery leads
21
+ ## Research protocol
22
+
23
+ Follow `rules/common/research.md` as the binding policy. The steps below operationalize it.
24
+
25
+ ### Step 1 Establish recency baseline
26
+
27
+ Start from the most recent available evidence. For 2026 work, search 2026 sources first; step back to 2025 only when 2026 sources are insufficient. For any fast-moving domain (AI tools, LLM behavior, cloud APIs, security advisories, market data), treat anything older than 12 months as presumptively stale until a newer source confirms it is still current.
28
+
29
+ When querying search engines or paper indexes, always filter or sort by date — never by relevance alone.
30
+
31
+ ### Step 2 — Use domain-specific authoritative starting points
32
+
33
+ | Domain | Authoritative starting points (most-recent-first) |
34
+ |---|---|
35
+ | AI tools, LLM behavior, multi-agent systems | arXiv (cs.AI, cs.SE, cs.CL, cs.HC); ACL Anthology; NeurIPS / ICML / ICLR / HICSS proceedings; then vendor research blogs |
36
+ | Developer tools, IDE, editor, adoption | Stack Overflow Developer Survey (current year); JetBrains Developer Ecosystem Report; GitHub blog; editor/tool changelogs and release notes |
37
+ | Security, CVEs, supply chain | NVD; GitHub Security Advisories; OWASP; Google Project Zero; Microsoft Security Response Center; vendor advisories; then ProjectDiscovery / Snyk reports |
38
+ | Market data, ARR, funding, adoption | Primary company announcements, press releases, SEC filings; then TechCrunch / Bloomberg / WSJ (where citing named company sources) |
39
+ | Cloud, APIs, SDKs, frameworks | Official vendor docs for the exact version in use; changelog; migration guides |
40
+ | Regulatory, compliance, privacy | Primary regulation or standard text; then official agency guidance; then qualified legal analysis |
41
+ | Academic / research literature | arXiv (preprints); ACM Digital Library; IEEE Xplore; Google Scholar (filter by year) |
42
+
43
+ ### Step 3 — Source hierarchy
44
+
45
+ 1. **Primary**: peer-reviewed papers, official docs for the exact version, published standards, raw source code, SEC filings, primary company announcements
46
+ 2. **Secondary**: changelogs, migration guides, tracked GitHub issues, maintainer posts, conference talks by the authors
47
+ 3. **Tertiary**: blog posts, forums, Q&A, analyst summaries, AI-generated overviews — used only to locate primaries, never as evidence
48
+
49
+ ### Step 4 — Check internal evidence
50
+
51
+ Before going external, search: `.cx/research/`, `.cx/knowledge/`, `docs/prd/`, `docs/meta-prd/`, ADRs, runbooks, and ingested artifacts. If a prior research brief exists for the topic, cite and extend it rather than redoing the search from scratch.
52
+
53
+ ### Step 5 — Verify every URL
54
+
55
+ Fetch every URL you include. Confirm it resolves and that the content matches the cited claim. Do not include aggregate or index pages (arxiv.org/search, Google Scholar listings) for quantitative claims — cite the specific document URL. If a URL returns a 404, paywall, or redirect loop, find the canonical source or replace the citation. Mark unconfirmed URLs `[unverified]` until fetched.
56
+
57
+ ### Step 6 — Evidence requirements per claim
58
+
59
+ - Prefer two independent primary sources per load-bearing claim
60
+ - One source is acceptable only when it is the authoritative primary source for that exact fact (e.g., the author's own paper reporting their measurement)
61
+ - Separate observation from inference — these are different things and must be labeled differently
62
+ - Name the strongest counter-evidence; do not smooth contradictions away
63
+ - State the evidence threshold that would change the recommendation
64
+
65
+ ### Termination rule
66
+
67
+ Stop at 2–3 confirmed primary sources per finding. If a primary source is confirmed, do not continue searching for corroboration unless the claim is contested. Tertiary sources are search tools, not evidence.
68
+
69
+ ## Output format
70
+
71
+ Produce a research brief using the structure from `get_template("research-brief")`. Minimum required sections:
72
+
73
+ **QUESTION** — the specific falsifiable question being answered
74
+
75
+ **METHOD** — search terms, systems queried, date filters applied, domain starting points used, internal paths checked; enough detail to reproduce
76
+
77
+ **SOURCES** — structured table: title/path | class (primary/secondary/tertiary) | date | URL | verified (yes/no) | relevance
78
+
79
+ **FINDINGS** — each finding labeled: what the source says (observation) | what is inferred (inference) | confidence (high/medium/low) | supporting source(s)
23
80
 
24
- Source hierarchy:
25
- 1. Primary: official documentation for the exact version in use, published standards, source code
26
- 2. Secondary: release notes, changelogs, migration guides, tracked issues, maintainer posts
27
- 3. Tertiary: forums, blog posts, Q&A — use as leads, not evidence
81
+ **COUNTER-EVIDENCE** — strongest disconfirming evidence; how it was addressed
28
82
 
29
- For each finding, cite: source URL/title or file path, source class, publication/version/access date, confidence level (confirmed / inferred / weak signal).
83
+ **GAPS** what the research did not resolve; what evidence would change the recommendation
30
84
 
31
- For load-bearing claims:
32
- - prefer two independent sources unless one authoritative primary source is sufficient
33
- - separate observation from inference
34
- - call out contradictions instead of smoothing them away
35
- - state the evidence threshold that would change the recommendation
85
+ **CONFIDENCE SUMMARY** — overall confidence across findings; key uncertainties
36
86
 
37
- Termination: stop at 2–3 primary sources per finding. If a primary source is confirmed, do not continue searching for corroboration. Use tertiary sources only to locate primaries, never as evidence.
87
+ **RECOMMENDATION** what the evidence supports; the evidence threshold at which the recommendation flips
38
88
 
39
- Output:
40
- FINDINGS: key facts with citations
41
- INFERENCES: conclusions drawn from evidence (clearly labeled)
42
- GAPS: missing evidence that would change the recommendation
43
- RECOMMENDATION: what the evidence supports
89
+ Write to `.cx/research/{topic-slug}.md` via `cx-docs-keeper`. Reference by path in the requesting agent's output.
44
90
 
45
91
  ## When invoked via the role framework
46
92
 
@@ -48,4 +94,4 @@ Construct may dispatch you in response to a `handoff.received`, `research.reques
48
94
 
49
95
  **Fence** (agents/role-manifests.json → researcher): allowed paths `docs/research/**`, `.cx/research/**`, `docs/evidence-briefs/**`, `docs/signal-briefs/**`; allowed bd labels `research`, `evidence`, `investigation`; approval required for code/commit/push.
50
96
 
51
- You produce research briefs, evidence briefs, signal briefs, and product-intelligence reports inside the fence. **Must not** edit code without user approval. **Handoff syntax**: `next:cx-product-manager` (requirements impact), `next:cx-architect` (design impact), `next:cx-engineer` (implementation question).
97
+ You produce research briefs, evidence briefs, signal briefs, and product-intelligence reports inside the fence. **Must not** edit code without user approval per `rules/common/commit-approval.md`. **Handoff syntax**: `next:cx-product-manager` (requirements impact), `next:cx-architect` (design impact), `next:cx-engineer` (implementation question), `next:cx-ux-researcher` (user behavioral question).
@@ -36,55 +36,17 @@ Check in this order:
36
36
 
37
37
  Provide: severity, location (file:line), description, trigger condition, and concrete fix. For CVE checks, delegate to cx-researcher. Hand all findings to cx-engineer — CRITICAL findings block shipping until fixed.
38
38
 
39
- ## Tool Contracts
40
-
41
- ### scan_secrets
42
- - **Input:** `{ files: string[], content?: string[], diff?: string }`
43
- - **Output:** `{ findings: SecretFinding[], falsePositives: string[], scanDuration: number }`
44
- - **Errors:** FILE_NOT_FOUND, SCAN_TIMEOUT, RATE_LIMITED
45
- - **Rate:** 20/min
46
-
47
- ### audit_auth
48
- - **Input:** `{ authFlow: AuthFlow, trustBoundaries: string[], jwtValidation: boolean }`
49
- - **Output:** `{ vulnerabilities: AuthVulnerability[], bypassPaths: string[], severity: Severity }`
50
- - **Errors:** INCOMPLETE_FLOW, MISSING_VALIDATION
51
- - **Rate:** 10/min
52
-
53
- ### check_injection
54
- - **Input:** `{ sinks: string[], sources: string[], sanitizers?: string[] }`
55
- - **Output:** `{ injectionPoints: InjectionPoint[], severity: Severity, fix: string }`
56
- - **Errors:** UNREACHABLE_SINK, FALSE_POSITIVE
57
- - **Rate:** 15/min
58
-
59
- ### assess_dependencies
60
- - **Input:** `{ dependencies: Dependency[], ecosystem: string, severityThreshold: string }`
61
- - **Output:** `{ cves: CVE[], upgrades: UpgradeRecommendation[], sbom: SBOM }`
62
- - **Errors:** UNKNOWN_PACKAGE, RATE_LIMITED
63
- - **Rate:** 5/min
64
-
65
- ## Parallel Execution
66
-
67
- When auditing code or reviewing changes, these checks run in parallel:
68
-
69
- - **Secrets scan** (always runs — fast, non-blocking)
70
- - **Auth/authorization audit** (if auth logic, JWT, sessions touched)
71
- - **Injection path analysis** (if user input reaches sinks)
72
- - **Data exposure check** (if logging, errors, APIs return data)
73
- - **Dependency CVE scan** (if package.json or lock files changed)
74
-
75
- All checks are independent — run concurrently and aggregate findings.
76
-
77
- ### Execution Pattern
78
- ```javascript
79
- // Parallel security checks
80
- const [secrets, auth, injection, exposure, deps] = await Promise.all([
81
- scan_secrets({ files }),
82
- audit_auth({ authFlow }),
83
- check_injection({ sinks, sources }),
84
- assess_data_exposure({ logs, errors }),
85
- assess_dependencies({ dependencies })
86
- ]);
87
- ```
39
+ ## Parallel audit discipline
40
+
41
+ Run these checks concurrently — they are independent and can be grep-driven in parallel:
42
+
43
+ - **Secrets scan** (always — fast, covers all files in scope)
44
+ - **Auth/authorization audit** (if auth logic, JWT, sessions, or privilege paths touched)
45
+ - **Injection path analysis** (if user input reaches sinks — exec, eval, query, template)
46
+ - **Data exposure check** (if logging, error responses, or APIs return data)
47
+ - **Dependency CVE scan** (if package.json or lock files changed — delegate to cx-researcher for CVE lookups)
48
+
49
+ Aggregate findings by severity before reporting. Do not report each category separately.
88
50
 
89
51
  ## Learning Capture
90
52
 
@@ -25,49 +25,15 @@ RUNBOOK for each alert:
25
25
 
26
26
  Review code changes for: missing error handling on request paths, N+1 queries, unbounded operations, missing timeouts, operations that don't degrade gracefully.
27
27
 
28
- ## Tool Contracts
29
-
30
- ### define_slo
31
- - **Input:** `{ service: string, metric: string, measurementMethod: string, target: number }`
32
- - **Output:** `{ slo: SLO, errorBudget: number, alertThreshold: number, dashboard: DashboardConfig }`
33
- - **Errors:** INVALID_METRIC, UNMEASURABLE_TARGET
34
- - **Rate:** 10/min
35
-
36
- ### create_runbook
37
- - **Input:** `{ alertName: string, triggerCondition: string, triageSteps: Step[] }`
38
- - **Output:** `{ runbook: Runbook, escalationPath: string[], rollbackProcedure: Rollback }`
39
- - **Errors:** MISSING_TRIGGER, INCOMPLETE_TRIAGE
40
- - **Rate:** 10/min
41
-
42
- ### review_reliability
43
- - **Input:** `{ codeChanges: Diff[], statefulOps: boolean, degradationPlan?: string }`
44
- - **Output:** `{ findings: ReliabilityFinding[], missingAlerts: string[], rollbackGaps: string[] }`
45
- - **Errors:** MISSING_DEGRADATION_PLAN, UNBOUNDED_OPERATION
46
- - **Rate:** 15/min
47
-
48
- ## Parallel Execution
49
-
50
- When reviewing changes for production readiness, these checks run in parallel:
51
-
52
- - **SLO definition check** (if new service or changed behavior)
53
- - **Alerting coverage** (if error paths or failure modes exist)
54
- - **Rollback procedure** (if stateful or irreversible operation)
55
- - **Error handling audit** (if request paths or external calls)
56
- - **Resource limits** (if N+1 queries, unbounded operations, missing timeouts)
57
-
58
- All checks are independent — run concurrently and aggregate findings.
59
-
60
- ### Execution Pattern
61
- ```javascript
62
- // Parallel SRE checks
63
- const [slo, alerts, rollback, errors, resources] = await Promise.all([
64
- define_slo({ service, metric, target }),
65
- check_alerting_coverage({ failureModes }),
66
- verify_rollback({ statefulOps }),
67
- audit_error_handling({ requestPaths }),
68
- check_resource_limits({ queries, operations })
69
- ]);
70
- ```
28
+ ## Production readiness checklist
29
+
30
+ For each change review, check these independently and aggregate before reporting:
31
+
32
+ - **SLO definition** is there a measurable target with an error budget for this service or behavior?
33
+ - **Alerting coverage** — is every meaningful failure mode covered by an alert with a runbook?
34
+ - **Rollback procedure** — is there a tested, documented path back from this change?
35
+ - **Error handling** — do request paths and external calls fail gracefully and within timeouts?
36
+ - **Resource bounds** — are there N+1 queries, unbounded loops, or missing timeouts?
71
37
 
72
38
  ## Learning Capture
73
39
 
@@ -14,6 +14,7 @@ You have watched enough users fail to know that what they say they want and what
14
14
  **Failure mode warning**: If your brief has no friction points, you haven't talked to users. Every product has places where users get stuck.
15
15
 
16
16
  **Role guidance**: call `get_skill("roles/researcher.ux")` before drafting.
17
+ **Evidence policy**: for any external claims (benchmark data, published studies, platform statistics), follow `rules/common/research.md` — most-recent-first, primary sources, verified URLs. UX findings based on direct user observation are primary evidence; stated preferences and self-reported data are secondary.
17
18
 
18
19
  Produce a UX brief:
19
20
 
@@ -125,7 +125,7 @@
125
125
  "events": ["handoff.received", "backlog.stale", "prd.requested"],
126
126
  "severityImmediate": [],
127
127
  "fence": {
128
- "allowedPaths": ["docs/prd/**", "docs/meta-prd/**", "docs/prfaq/**", "docs/one-pager/**", ".cx/product-intel/**"],
128
+ "allowedPaths": ["docs/prd/**", "docs/meta-prd/**", "docs/prfaq/**", "docs/one-pager/**", ".cx/knowledge/**"],
129
129
  "allowedBdLabels": ["product", "prd", "backlog", "feature"],
130
130
  "allowedCommands": ["bd create", "bd note", "bd update", "bd label", "bd priority"],
131
131
  "approvalRequired": ["commit", "push", "edit:lib/**", "edit:bin/**"]
@@ -192,7 +192,7 @@
192
192
  "trace-reviewer": { "events": ["handoff.received", "trace.anomaly"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/traces/**", ".cx/traces/**"], "allowedBdLabels": ["trace", "observability"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["trace"], "docTypes": ["memo"] }, "handoffCandidates": ["sre", "ai-engineer", "engineer"], "killSwitchEnv": "CONSTRUCT_ROLE_TRACE_REVIEWER" },
193
193
  "test-automation": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["tests/**", "docs/test-automation/**"], "allowedBdLabels": ["test-automation", "ci", "test-infra"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:lib/**", "edit:bin/**"] }, "outputs": { "bdLabels": ["test-automation"], "docTypes": ["memo"] }, "handoffCandidates": ["qa", "engineer", "platform-engineer"], "killSwitchEnv": "CONSTRUCT_ROLE_TEST_AUTOMATION" },
194
194
  "ai-engineer": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/ai/**", "docs/agents/**", "agents/**", "skills/ai/**"], "allowedBdLabels": ["ai", "agent", "model"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:lib/**", "edit:bin/**"] }, "outputs": { "bdLabels": ["ai"], "docTypes": ["memo"] }, "handoffCandidates": ["engineer", "architect", "evaluator"], "killSwitchEnv": "CONSTRUCT_ROLE_AI_ENGINEER" },
195
- "business-strategist": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/strategy/**", ".cx/strategy/**"], "allowedBdLabels": ["strategy", "business"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["strategy"], "docTypes": ["memo"] }, "handoffCandidates": ["product-manager", "researcher"], "killSwitchEnv": "CONSTRUCT_ROLE_BUSINESS_STRATEGIST" },
195
+ "business-strategist": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/strategy/**", ".cx/knowledge/decisions/strategy/**"], "allowedBdLabels": ["strategy", "business"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["strategy"], "docTypes": ["memo", "strategy"] }, "handoffCandidates": ["product-manager", "researcher"], "killSwitchEnv": "CONSTRUCT_ROLE_BUSINESS_STRATEGIST" },
196
196
  "researcher": {
197
197
  "events": ["handoff.received", "research.requested", "evidence.requested"],
198
198
  "severityImmediate": [],
@@ -209,9 +209,9 @@
209
209
  "ux-researcher": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/ux-research/**", ".cx/ux-research/**"], "allowedBdLabels": ["ux-research", "user-research"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["ux-research"], "docTypes": ["memo"] }, "handoffCandidates": ["product-manager", "designer", "researcher"], "killSwitchEnv": "CONSTRUCT_ROLE_UX_RESEARCHER" },
210
210
  "explorer": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/explorations/**", ".cx/explorations/**"], "allowedBdLabels": ["exploration", "spike"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["exploration"], "docTypes": ["memo"] }, "handoffCandidates": ["engineer", "architect", "researcher"], "killSwitchEnv": "CONSTRUCT_ROLE_EXPLORER" },
211
211
  "operations": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/ops/**", "docs/operations/**"], "allowedBdLabels": ["ops", "operations"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["ops"], "docTypes": ["memo", "runbook"] }, "handoffCandidates": ["sre", "platform-engineer", "release-manager"], "killSwitchEnv": "CONSTRUCT_ROLE_OPERATIONS" },
212
- "orchestrator": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/orchestration/**", "docs/workflows/**"], "allowedBdLabels": ["orchestration", "workflow"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["orchestration"], "docTypes": ["memo"] }, "handoffCandidates": ["product-manager", "architect", "engineer"], "killSwitchEnv": "CONSTRUCT_ROLE_ORCHESTRATOR" },
212
+ "orchestrator": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/**", "plan.md", ".cx/context.md"], "allowedBdLabels": ["orchestration", "workflow", "dispatch"], "allowedCommands": ["bd create", "bd note", "bd update", "bd label", "bd link"], "approvalRequired": ["commit", "push", "edit:lib/**", "edit:bin/**", "edit:agents/**"] }, "outputs": { "bdLabels": ["orchestration", "dispatch"], "docTypes": ["memo"] }, "handoffCandidates": ["product-manager", "architect", "engineer", "security", "qa", "sre"], "killSwitchEnv": "CONSTRUCT_ROLE_ORCHESTRATOR" },
213
213
  "rd-lead": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/research/**", "docs/experiments/**", ".cx/experiments/**"], "allowedBdLabels": ["rd", "research", "experiment"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["rd"], "docTypes": ["memo", "research-brief"] }, "handoffCandidates": ["researcher", "architect", "ai-engineer"], "killSwitchEnv": "CONSTRUCT_ROLE_RD_LEAD" },
214
- "legal-compliance": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/legal/**", "docs/compliance/**"], "allowedBdLabels": ["legal", "compliance", "privacy"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["legal", "compliance"], "docTypes": ["memo"] }, "handoffCandidates": ["security", "product-manager"], "killSwitchEnv": "CONSTRUCT_ROLE_LEGAL_COMPLIANCE" },
214
+ "legal-compliance": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/legal/**", "docs/compliance/**", "docs/security/**", ".cx/knowledge/**"], "allowedBdLabels": ["legal", "compliance", "privacy"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["legal", "compliance"], "docTypes": ["memo"] }, "handoffCandidates": ["security", "product-manager"], "killSwitchEnv": "CONSTRUCT_ROLE_LEGAL_COMPLIANCE" },
215
215
  "devil-advocate": { "events": ["handoff.received"], "severityImmediate": [], "fence": { "allowedPaths": ["docs/critiques/**"], "allowedBdLabels": ["critique", "devil-advocate"], "allowedCommands": ["bd create", "bd note", "bd update"], "approvalRequired": ["commit", "push", "edit:**"] }, "outputs": { "bdLabels": ["critique"], "docTypes": ["memo"] }, "handoffCandidates": ["architect", "product-manager", "engineer"], "killSwitchEnv": "CONSTRUCT_ROLE_DEVIL_ADVOCATE" }
216
216
  }
217
217
  }
package/bin/construct CHANGED
@@ -130,7 +130,7 @@ function usage() {
130
130
 
131
131
  /** Show interactive context-aware menu when construct is run without arguments. */
132
132
  async function showInteractiveMenu() {
133
- const { loadProjectConfig } = await import('./lib/config/project-config.mjs');
133
+ const { loadProjectConfig } = await import('../lib/config/project-config.mjs');
134
134
  const { existsSync } = await import('node:fs');
135
135
  const { join } = await import('node:path');
136
136
 
@@ -142,7 +142,7 @@ async function showInteractiveMenu() {
142
142
  println('');
143
143
 
144
144
  if (isConstructProject) {
145
- const projectName = process.env.CX_PROJECT_NAME || require('path').basename(projectRoot);
145
+ const projectName = process.env.CX_PROJECT_NAME || path.basename(projectRoot);
146
146
  println(`${COLORS.dim}Project:${COLORS.reset} ${projectName}`);
147
147
  println('');
148
148
  }
@@ -160,7 +160,7 @@ async function showInteractiveMenu() {
160
160
 
161
161
  // Show context-aware suggestions
162
162
  if (isConstructProject) {
163
- const { readDashboardState } = await import('./lib/service-manager.mjs');
163
+ const { readDashboardState } = await import('../lib/service-manager.mjs');
164
164
  const dashboard = readDashboardState(HOME);
165
165
 
166
166
  if (!dashboard) {
@@ -288,12 +288,12 @@ async function cmdDoctor() {
288
288
  for (const varName of vars) {
289
289
  const sources = [];
290
290
 
291
- // 1. process.env
291
+ // process.env
292
292
  if (process.env[varName]) {
293
293
  sources.push(`process.env (${process.env[varName].slice(0, 8)}…)`);
294
294
  }
295
295
 
296
- // 2. .env files
296
+ // .env files
297
297
  for (const envPath of [join(process.cwd(), '.env'), join(homedir(), '.env'), join(homedir(), '.construct', 'config.env')]) {
298
298
  if (fs.existsSync(envPath)) {
299
299
  try {
@@ -304,7 +304,7 @@ async function cmdDoctor() {
304
304
  }
305
305
  }
306
306
 
307
- // 3. Shell rc files
307
+ // Shell rc files
308
308
  const shellFiles = [join(homedir(), '.zshrc'), join(homedir(), '.bashrc'), join(homedir(), '.bash_profile'), join(homedir(), '.profile')];
309
309
  for (const rcPath of shellFiles) {
310
310
  if (!fs.existsSync(rcPath)) continue;
@@ -421,6 +421,22 @@ async function cmdDoctor() {
421
421
  add('Claude Code agents dir', fs.existsSync(path.join(HOME, '.claude', 'agents')), true);
422
422
  add('Codex agents dir', fs.existsSync(path.join(HOME, '.codex', 'agents')), true);
423
423
  add('Copilot prompts dir', fs.existsSync(path.join(HOME, '.github', 'prompts')), true);
424
+ add('Cursor MCP config', fs.existsSync(path.join(HOME, '.cursor', 'mcp.json')), true);
425
+ const vscodeSettingsPaths = process.platform === 'darwin'
426
+ ? [
427
+ path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'settings.json'),
428
+ path.join(HOME, 'Library', 'Application Support', 'Code - Insiders', 'User', 'settings.json'),
429
+ ]
430
+ : process.platform === 'linux'
431
+ ? [
432
+ path.join(HOME, '.config', 'Code', 'User', 'settings.json'),
433
+ path.join(HOME, '.config', 'Code - Insiders', 'User', 'settings.json'),
434
+ ]
435
+ : [
436
+ path.join(process.env.APPDATA ?? path.join(HOME, 'AppData', 'Roaming'), 'Code', 'User', 'settings.json'),
437
+ path.join(process.env.APPDATA ?? path.join(HOME, 'AppData', 'Roaming'), 'Code - Insiders', 'User', 'settings.json'),
438
+ ];
439
+ add('VS Code settings file', vscodeSettingsPaths.some((candidate) => fs.existsSync(candidate)), true);
424
440
  add('User config ready', fs.existsSync(getUserEnvPath(HOME)) || fs.existsSync(path.join(HOME, '.construct')), true);
425
441
  add('skills/ directory', fs.existsSync(path.join(ROOT_DIR, 'skills')));
426
442
  add('rules/common/', fs.existsSync(path.join(ROOT_DIR, 'rules', 'common')));
@@ -904,9 +920,9 @@ async function cmdUp() {
904
920
  if (embedConfigExists && autoEmbed) {
905
921
  try {
906
922
  const { runEmbedCli } = await import('../lib/embed/cli.mjs');
907
- await runEmbedCli(['start']);
908
- } catch {
909
- // best effort
923
+ await runEmbedCli(['start'], { rootDir: ROOT_DIR });
924
+ } catch (err) {
925
+ warn(`embed start failed: ${err.message} — check ~/.cx/runtime/embed-daemon.log`);
910
926
  }
911
927
  }
912
928
 
@@ -1789,8 +1805,8 @@ async function cmdIntakeConfig(args) {
1789
1805
  async function cmdIntakeMetrics() {
1790
1806
  try {
1791
1807
  const { computeIntakeMetrics, pendingAge } = await import('../lib/embed/intake-metrics.mjs');
1792
- const metrics = computeIntakeMetrics({ rootDir: cwd });
1793
- const age = pendingAge({ rootDir: cwd });
1808
+ const metrics = computeIntakeMetrics({ rootDir: process.cwd() });
1809
+ const age = pendingAge({ rootDir: process.cwd() });
1794
1810
 
1795
1811
  println(`${COLORS.bold}Intake pipeline metrics:${COLORS.reset}`);
1796
1812
  println('');
@@ -2449,6 +2465,44 @@ async function cmdModels(args) {
2449
2465
  await cmdSync([]);
2450
2466
  return;
2451
2467
  }
2468
+ if (args.includes('--cheapest')) {
2469
+ const { selectCheapestProvider, rankConfiguredProvidersByCost, formatCheapestProviderMessage, isCheapestProviderEnabled } =
2470
+ await import('../lib/model-cheapest-provider.mjs');
2471
+ const cheapestTier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? 'standard';
2472
+ const verbose = args.includes('--verbose');
2473
+ const enabled = isCheapestProviderEnabled(envPath, { env: process.env });
2474
+ const result = await selectCheapestProvider(cheapestTier, { env: process.env });
2475
+ const msg = formatCheapestProviderMessage(result, { showRanking: verbose });
2476
+ println(msg);
2477
+ if (enabled) println('\nCheapest provider mode: enabled');
2478
+ return;
2479
+ }
2480
+ if (args.includes('--apply-cheapest')) {
2481
+ const { selectCheapestForAllTiers, setCheapestProviderPreference } =
2482
+ await import('../lib/model-cheapest-provider.mjs');
2483
+ const allTiers = args.includes('--all-tiers') || !args.find((arg) => arg.startsWith('--tier='));
2484
+ const tierArg = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1];
2485
+ const tiers = allTiers ? ['reasoning', 'standard', 'fast'] : (tierArg ? [tierArg] : ['standard']);
2486
+ const selections = {};
2487
+ for (const currentTier of tiers) {
2488
+ const { selectCheapestProvider } = await import('../lib/model-cheapest-provider.mjs');
2489
+ const result = await selectCheapestProvider(currentTier, { env: process.env });
2490
+ if (result.modelId) selections[currentTier] = result.modelId;
2491
+ }
2492
+ if (Object.keys(selections).length === 0) {
2493
+ errorln('No configured providers found. Set API keys or install Ollama first.');
2494
+ process.exit(1);
2495
+ }
2496
+ applyToEnv(envPath, selections);
2497
+ println('Applying cheapest models:');
2498
+ for (const [tier, model] of Object.entries(selections)) {
2499
+ println(` ${tier.padEnd(11)} ${model}`);
2500
+ }
2501
+ println('Written to ~/.construct/config.env. Running construct sync...');
2502
+ setCheapestProviderPreference(envPath, true);
2503
+ await cmdSync([]);
2504
+ return;
2505
+ }
2452
2506
  const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'agents', 'registry.json'), 'utf8'));
2453
2507
  const result = readCurrentModels(envPath, registry.models ?? {});
2454
2508
  const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
@@ -3796,6 +3850,13 @@ if (command === '--help' || command === '-h' || command === 'help') {
3796
3850
  process.exit(0);
3797
3851
  }
3798
3852
 
3853
+ // Version flags → print version and exit
3854
+ if (command === '--version' || command === '-V') {
3855
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
3856
+ println(`construct v${pkg.version}`);
3857
+ process.exit(0);
3858
+ }
3859
+
3799
3860
  const handler = handlers.get(command);
3800
3861
  if (!handler) {
3801
3862
  errorln(`Unknown command: ${command}`);
@@ -6,7 +6,7 @@
6
6
  * and runs `npm install`, npm fetches Construct into the project's
7
7
  * `node_modules/` and then runs this script. Its job: regenerate the project's
8
8
  * `.claude/agents/` and `.claude/settings.json` from the bundled registry so
9
- * the project clone is fully runnable without a manual `construct setup`.
9
+ * the project clone is fully runnable without a manual `construct init`.
10
10
  *
11
11
  * The script is a no-op in three cases:
12
12
  *
@@ -0,0 +1,46 @@
1
+ -- 004_recommendations.sql — Recommendations table for team and enterprise mode.
2
+ -- Solo mode uses the JSONL store under .cx/intake/ instead.
3
+ --
4
+ -- Dedup key is enforced at the (project, dedup_key) level — matches the in-memory
5
+ -- dedup contract in lib/embed/recommendation-store.mjs.
6
+ -- Active recommendations: dismissed_at IS NULL AND superseded_at IS NULL.
7
+
8
+ create table if not exists construct_recommendations (
9
+ id text primary key,
10
+ project text not null,
11
+ dedup_key text not null,
12
+ type text not null,
13
+ title text not null,
14
+ reason text,
15
+ lane text,
16
+ signal_count int not null default 1,
17
+ total_signal_count int not null default 1,
18
+ customer_impact int not null default 0,
19
+ recency_bonus int not null default 0,
20
+ strategic_bonus int not null default 0,
21
+ score float not null default 0,
22
+ priority text not null default 'P3',
23
+ source_signal_ids jsonb not null default '[]'::jsonb,
24
+ first_seen timestamptz not null default now(),
25
+ last_seen timestamptz not null default now(),
26
+ dismissed_at timestamptz,
27
+ dismiss_reason text,
28
+ superseded_at timestamptz,
29
+ superseded_by_id text,
30
+ suppressed_until timestamptz,
31
+ suppress_reason text,
32
+ updated_at timestamptz not null default now()
33
+ );
34
+
35
+ create unique index if not exists construct_recommendations_project_dedup_key_idx
36
+ on construct_recommendations (project, dedup_key);
37
+
38
+ create index if not exists construct_recommendations_priority_score_idx
39
+ on construct_recommendations (project, priority, score desc);
40
+
41
+ create index if not exists construct_recommendations_last_seen_idx
42
+ on construct_recommendations (project, last_seen desc);
43
+
44
+ create index if not exists construct_recommendations_active_idx
45
+ on construct_recommendations (project, dismissed_at)
46
+ where dismissed_at is null;
@@ -0,0 +1,21 @@
1
+ -- 005_strategy.sql — Product strategy store for team and enterprise mode.
2
+ -- Solo mode uses ~/.cx/strategy.md instead.
3
+ --
4
+ -- Each row is an immutable version snapshot. The latest version for a project
5
+ -- is the row with the highest version number (or max updated_at when equal).
6
+ -- Callers insert a new row on each write; old versions are retained for history.
7
+
8
+ create table if not exists construct_strategy (
9
+ id bigserial primary key,
10
+ project text not null,
11
+ content text not null,
12
+ version int not null default 1,
13
+ updated_at timestamptz not null default now(),
14
+ updated_by text
15
+ );
16
+
17
+ create unique index if not exists construct_strategy_project_version_idx
18
+ on construct_strategy (project, version);
19
+
20
+ create index if not exists construct_strategy_project_updated_at_idx
21
+ on construct_strategy (project, updated_at desc);
package/lib/auto-docs.mjs CHANGED
@@ -88,7 +88,7 @@ const DIR_DESCRIPTIONS = {
88
88
  commands: 'Command prompt assets',
89
89
  codex: 'GitHub Copilot / Codex integration',
90
90
  docs: 'Architecture notes, runbooks, and documentation contract',
91
- telemetry: 'Telemetry backend for agent observability',
91
+ telemetry: 'Local trace capture and optional telemetry export',
92
92
  lib: 'Core runtime: CLI, hooks, MCP, status, sync, workflow',
93
93
  opencode: 'OpenCode integration config',
94
94
  personas: 'Persona prompt definitions',
@@ -470,4 +470,3 @@ export function buildFumadocsReference({ rootDir } = {}) {
470
470
 
471
471
  return { written };
472
472
  }
473
-
@@ -22,6 +22,11 @@ const HANDOFFS_DIR = '.cx/handoffs';
22
22
  const CONFIRM_TIMEOUT = 60000; // 60 second timeout for confirmation prompts
23
23
  const POSITIVE_INTENT = /^(y|yes|yeah|yep|yup|sure|ok|okay|go|proceed|do it)\b/i;
24
24
 
25
+ function beadsConfirmEnabled(env = process.env) {
26
+ const raw = String(env.CONSTRUCT_BEADS_CONFIRM ?? '').trim().toLowerCase();
27
+ return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
28
+ }
29
+
25
30
  // ---------------------------------------------------------------------------
26
31
  // Confirmation Utility
27
32
  // ---------------------------------------------------------------------------
@@ -148,14 +153,18 @@ export async function confirmAndCommit({
148
153
  }
149
154
  console.error('');
150
155
 
151
- const confirmed = await confirm(
152
- 'Commit these changes?',
153
- { yes, default: 'n' }
154
- );
156
+ if (yes || beadsConfirmEnabled()) {
157
+ const confirmed = await confirm(
158
+ 'Commit these changes?',
159
+ { yes, default: 'n' }
160
+ );
155
161
 
156
- if (!confirmed) {
157
- console.error('[beads] Commit cancelled');
158
- return null;
162
+ if (!confirmed) {
163
+ console.error('[beads] Commit cancelled');
164
+ return null;
165
+ }
166
+ } else {
167
+ console.error('[beads] CONSTRUCT_BEADS_CONFIRM is off — committing without interactive prompt.');
159
168
  }
160
169
 
161
170
  runGit(['commit', '-m', message], { cwd });
@@ -15,7 +15,7 @@
15
15
  * downloadSize approximate install size in bytes (informational)
16
16
  *
17
17
  * The registry is consulted by:
18
- * - `construct setup` walks every resource, asks consent, installs
18
+ * - `construct init` walks every resource, asks consent, installs
19
19
  * - `construct doctor --bootstrap` re-probes every resource verbosely
20
20
  * - lazy-install paths in hooks (consult the cached consent silently)
21
21
  *
@@ -114,7 +114,7 @@ export function formatProbe(probe) {
114
114
  ? `\n fallback: ${probe.fallback}`
115
115
  : '';
116
116
  const install = !probe.present && probe.installable
117
- ? `\n installable via construct setup`
117
+ ? `\n installable via construct init`
118
118
  : '';
119
119
  return ` ${status} ${probe.displayName}${v}${loc}${detail}${fallback}${install}`;
120
120
  }