@geraldmaron/construct 1.0.23 → 1.0.24

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 (52) hide show
  1. package/README.md +0 -2
  2. package/bin/construct +15 -215
  3. package/lib/embed/inbox.mjs +6 -3
  4. package/lib/embed/recommendation-store.mjs +7 -289
  5. package/lib/embed/reconcile.mjs +2 -2
  6. package/lib/hooks/config-protection.mjs +4 -4
  7. package/lib/hooks/session-reflect.mjs +5 -1
  8. package/lib/intake/git-queue.mjs +195 -0
  9. package/lib/intake/queue.mjs +9 -16
  10. package/lib/mcp/tools/storage.mjs +2 -3
  11. package/lib/mcp-catalog.json +3 -3
  12. package/lib/mcp-manager.mjs +59 -3
  13. package/lib/observation-store.mjs +38 -166
  14. package/lib/orchestration/runtime.mjs +3 -2
  15. package/lib/reconcile/index.mjs +0 -2
  16. package/lib/service-manager.mjs +38 -256
  17. package/lib/setup.mjs +26 -426
  18. package/lib/status.mjs +3 -6
  19. package/lib/storage/admin.mjs +48 -325
  20. package/lib/storage/backend.mjs +10 -57
  21. package/lib/storage/hybrid-query.mjs +15 -196
  22. package/lib/storage/sync.mjs +36 -177
  23. package/lib/storage/vector-client.mjs +256 -235
  24. package/lib/strategy-store.mjs +35 -286
  25. package/lib/worker/entrypoint.mjs +6 -14
  26. package/package.json +6 -5
  27. package/platforms/claude/settings.template.json +0 -7
  28. package/scripts/sync-specialists.mjs +46 -12
  29. package/specialists/prompts/cx-qa.md +1 -1
  30. package/specialists/registry.json +0 -8
  31. package/templates/docs/construct_guide.md +1 -1
  32. package/db/schema/001_init.sql +0 -40
  33. package/db/schema/002_pgvector.sql +0 -182
  34. package/db/schema/003_intake.sql +0 -47
  35. package/db/schema/003_observation_reconciliation.sql +0 -14
  36. package/db/schema/004_recommendations.sql +0 -46
  37. package/db/schema/005_strategy.sql +0 -21
  38. package/db/schema/006_graph.sql +0 -24
  39. package/db/schema/007_tags.sql +0 -30
  40. package/db/schema/008_skill_usage.sql +0 -24
  41. package/db/schema/009_scheduler.sql +0 -14
  42. package/db/schema/010_cx_scores.sql +0 -51
  43. package/lib/intake/postgres-queue.mjs +0 -240
  44. package/lib/reconcile/postgres-namespace.mjs +0 -102
  45. package/lib/services/local-postgres.mjs +0 -15
  46. package/lib/storage/backup.mjs +0 -347
  47. package/lib/storage/migrations.mjs +0 -187
  48. package/lib/storage/postgres-backup.mjs +0 -124
  49. package/lib/storage/sql-store.mjs +0 -45
  50. package/lib/storage/store-version.mjs +0 -115
  51. package/lib/storage/unified-storage.mjs +0 -550
  52. package/lib/storage/vector-store.mjs +0 -100
@@ -1,182 +0,0 @@
1
- -- db/schema/002_pgvector.sql
2
- -- Enable pgvector extension and add vector-typed columns for semantic search.
3
- -- This migration is idempotent — safe to run multiple times.
4
-
5
- -- Enable pgvector extension
6
- CREATE EXTENSION IF NOT EXISTS vector;
7
-
8
- -- Add vector column to construct_embeddings if it doesn't exist yet
9
- -- (The original 001_init.sql used double precision[], which we keep for backward compat)
10
- DO $$
11
- BEGIN
12
- -- Check if embedding column is still array type and needs migration
13
- IF EXISTS (
14
- SELECT 1 FROM information_schema.columns
15
- WHERE table_name = 'construct_embeddings'
16
- AND column_name = 'embedding'
17
- AND data_type = 'ARRAY'
18
- ) THEN
19
- -- Add new vector column
20
- ALTER TABLE construct_embeddings ADD COLUMN embedding_vec vector(384);
21
-
22
- -- Copy data from old array column to new vector column
23
- UPDATE construct_embeddings
24
- SET embedding_vec = embedding::vector(384);
25
-
26
- -- Drop old column and rename
27
- ALTER TABLE construct_embeddings DROP COLUMN embedding;
28
- ALTER TABLE construct_embeddings RENAME COLUMN embedding_vec TO embedding;
29
- END IF;
30
- END $$;
31
-
32
- -- Ensure embedding column exists as vector type (for fresh installs)
33
- ALTER TABLE construct_embeddings
34
- ALTER COLUMN embedding TYPE vector(384) USING embedding::vector(384);
35
-
36
- -- Create HNSW index for fast approximate nearest neighbor search
37
- CREATE INDEX IF NOT EXISTS idx_embeddings_hnsw
38
- ON construct_embeddings
39
- USING hnsw (embedding vector_cosine_ops)
40
- WITH (m = 16, ef_construction = 64);
41
-
42
- -- Create observations table with vector support
43
- CREATE TABLE IF NOT EXISTS construct_observations (
44
- id text PRIMARY KEY,
45
- project text NOT NULL,
46
- role text NOT NULL,
47
- category text NOT NULL,
48
- summary text NOT NULL,
49
- content text NOT NULL,
50
- tags jsonb NOT NULL DEFAULT '[]'::jsonb,
51
- confidence float NOT NULL DEFAULT 0.8,
52
- source text,
53
- git_sha text,
54
- created_at timestamptz NOT NULL DEFAULT now(),
55
- updated_at timestamptz NOT NULL DEFAULT now(),
56
- embedding vector(384)
57
- );
58
-
59
- -- Indexes for observations
60
- CREATE INDEX IF NOT EXISTS idx_observations_project ON construct_observations(project);
61
- CREATE INDEX IF NOT EXISTS idx_observations_category ON construct_observations(project, category);
62
- CREATE INDEX IF NOT EXISTS idx_observations_created_at ON construct_observations(project, created_at DESC);
63
- CREATE INDEX IF NOT EXISTS idx_observations_vector ON construct_observations USING hnsw (embedding vector_cosine_ops);
64
-
65
- -- Create sessions table with vector support
66
- CREATE TABLE IF NOT EXISTS construct_sessions (
67
- id text PRIMARY KEY,
68
- project text NOT NULL,
69
- platform text,
70
- summary text,
71
- decisions jsonb DEFAULT '[]'::jsonb,
72
- files_changed jsonb DEFAULT '[]'::jsonb,
73
- open_questions jsonb DEFAULT '[]'::jsonb,
74
- task_snapshot jsonb DEFAULT '[]'::jsonb,
75
- status text DEFAULT 'active',
76
- created_at timestamptz NOT NULL DEFAULT now(),
77
- updated_at timestamptz NOT NULL DEFAULT now(),
78
- embedding vector(384)
79
- );
80
-
81
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON construct_sessions(project);
82
- CREATE INDEX IF NOT EXISTS idx_sessions_status ON construct_sessions(project, status);
83
- CREATE INDEX IF NOT EXISTS idx_sessions_vector ON construct_sessions USING hnsw (embedding vector_cosine_ops);
84
-
85
- -- Create entities table with vector support
86
- CREATE TABLE IF NOT EXISTS construct_entities (
87
- name text PRIMARY KEY,
88
- type text NOT NULL,
89
- summary text,
90
- project text,
91
- observation_ids jsonb DEFAULT '[]'::jsonb,
92
- related_entities jsonb DEFAULT '[]'::jsonb,
93
- created_at timestamptz NOT NULL DEFAULT now(),
94
- updated_at timestamptz NOT NULL DEFAULT now(),
95
- last_seen timestamptz,
96
- embedding vector(384)
97
- );
98
-
99
- CREATE INDEX IF NOT EXISTS idx_entities_type ON construct_entities(type);
100
- CREATE INDEX IF NOT EXISTS idx_entities_project ON construct_entities(project);
101
- CREATE INDEX IF NOT EXISTS idx_entities_vector ON construct_entities USING hnsw (embedding vector_cosine_ops);
102
-
103
- -- Function for searching documents by embedding
104
- CREATE OR REPLACE FUNCTION search_documents(
105
- project_name text,
106
- query_embedding vector(384),
107
- match_limit int DEFAULT 10,
108
- min_similarity float DEFAULT 0.3
109
- )
110
- RETURNS TABLE(
111
- id text,
112
- title text,
113
- summary text,
114
- body text,
115
- source_path text,
116
- tags jsonb,
117
- kind text,
118
- similarity float
119
- ) AS $$
120
- BEGIN
121
- RETURN QUERY
122
- SELECT
123
- d.id,
124
- d.title,
125
- d.summary,
126
- d.body,
127
- d.source_path,
128
- d.tags,
129
- d.kind,
130
- 1 - (e.embedding <=> query_embedding) AS similarity
131
- FROM construct_documents d
132
- JOIN construct_embeddings e ON d.id = e.document_id
133
- WHERE d.project = project_name
134
- AND 1 - (e.embedding <=> query_embedding) > min_similarity
135
- ORDER BY e.embedding <=> query_embedding
136
- LIMIT match_limit;
137
- END;
138
- $$ LANGUAGE plpgsql;
139
-
140
- -- Function for searching observations by embedding
141
- CREATE OR REPLACE FUNCTION search_observations(
142
- project_name text,
143
- query_embedding vector(384),
144
- match_limit int DEFAULT 10,
145
- min_similarity float DEFAULT 0.3,
146
- filter_role text DEFAULT NULL,
147
- filter_category text DEFAULT NULL
148
- )
149
- RETURNS TABLE(
150
- id text,
151
- role text,
152
- category text,
153
- summary text,
154
- content text,
155
- tags jsonb,
156
- confidence float,
157
- source text,
158
- created_at timestamptz,
159
- similarity float
160
- ) AS $$
161
- BEGIN
162
- RETURN QUERY
163
- SELECT
164
- o.id,
165
- o.role,
166
- o.category,
167
- o.summary,
168
- o.content,
169
- o.tags,
170
- o.confidence,
171
- o.source,
172
- o.created_at,
173
- 1 - (o.embedding <=> query_embedding) AS similarity
174
- FROM construct_observations o
175
- WHERE o.project = project_name
176
- AND 1 - (o.embedding <=> query_embedding) > min_similarity
177
- AND (filter_role IS NULL OR o.role = filter_role)
178
- AND (filter_category IS NULL OR o.category = filter_category)
179
- ORDER BY o.embedding <=> query_embedding
180
- LIMIT match_limit;
181
- END;
182
- $$ LANGUAGE plpgsql;
@@ -1,47 +0,0 @@
1
- -- 003_intake.sql — R&D intake queue backing table for team and enterprise mode.
2
- -- Solo mode uses the filesystem under .cx/intake/ instead.
3
- --
4
- -- Worker claim algorithm: SELECT ... FROM construct_intake_items
5
- -- WHERE status = 'pending' ORDER BY created_at ASC FOR UPDATE SKIP LOCKED LIMIT 1;
6
- -- then set status = 'claimed', claimed_by, claimed_at in the same transaction.
7
- --
8
- -- payload jsonb carries the rest of the intake packet (intake, triage,
9
- -- suggestion, related, excerpt, query) so the on-disk shape and the
10
- -- in-table shape stay aligned.
11
-
12
- create table if not exists construct_intake_items (
13
- id text primary key,
14
- project text not null,
15
- tenant_id text,
16
- status text not null default 'pending',
17
- intake_type text,
18
- rd_stage text,
19
- primary_owner text,
20
- recommended_action text,
21
- risk text,
22
- requires_approval boolean not null default false,
23
- confidence double precision,
24
- payload jsonb not null,
25
- created_at timestamptz not null default now(),
26
- updated_at timestamptz not null default now(),
27
- claimed_by text,
28
- claimed_at timestamptz,
29
- processed_at timestamptz,
30
- processed_by text,
31
- notes text,
32
- skipped_at timestamptz,
33
- skipped_by text,
34
- skip_reason text
35
- );
36
-
37
- create index if not exists construct_intake_items_status_idx
38
- on construct_intake_items(status, created_at);
39
-
40
- create index if not exists construct_intake_items_owner_idx
41
- on construct_intake_items(primary_owner, status, created_at);
42
-
43
- create index if not exists construct_intake_items_payload_gin_idx
44
- on construct_intake_items using gin(payload);
45
-
46
- create index if not exists construct_intake_items_project_tenant_idx
47
- on construct_intake_items(project, tenant_id, status);
@@ -1,14 +0,0 @@
1
- -- db/schema/003_observation_reconciliation.sql
2
- -- Stamp each observation with the content hash and embedding model that
3
- -- produced its vector. An idempotent reconciliation pass uses (content_hash,
4
- -- model) to find rows whose embedding is missing or stale — content edited, or
5
- -- the embedding model changed — and re-embed only those, rather than relying on
6
- -- the inline write at creation time always succeeding. Mirrors the
7
- -- (content_hash, model) tracking construct_embeddings already carries for
8
- -- documents. Fully idempotent: safe to re-apply.
9
-
10
- ALTER TABLE construct_observations ADD COLUMN IF NOT EXISTS content_hash text;
11
- ALTER TABLE construct_observations ADD COLUMN IF NOT EXISTS model text;
12
-
13
- CREATE INDEX IF NOT EXISTS idx_observations_content_hash ON construct_observations (content_hash);
14
- CREATE INDEX IF NOT EXISTS idx_observations_model ON construct_observations (model);
@@ -1,46 +0,0 @@
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;
@@ -1,21 +0,0 @@
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);
@@ -1,24 +0,0 @@
1
- -- 006_graph.sql. GraphRAG community columns for entities.
2
- --
3
- -- Phase C9 foundations. Adds community_id (label propagation output) and a
4
- -- community summary table so the Pg-backed deployment can query communities
5
- -- without a JSONL scan. Solo-mode JSONL remains the source of truth; this
6
- -- table is the projection.
7
-
8
- ALTER TABLE construct_entities
9
- ADD COLUMN IF NOT EXISTS community_id text,
10
- ADD COLUMN IF NOT EXISTS community_size int;
11
-
12
- CREATE INDEX IF NOT EXISTS idx_entities_community ON construct_entities(project, community_id);
13
-
14
- CREATE TABLE IF NOT EXISTS construct_entity_communities (
15
- community_id text NOT NULL,
16
- project text NOT NULL,
17
- size int NOT NULL DEFAULT 0,
18
- top_members jsonb DEFAULT '[]'::jsonb,
19
- summary text,
20
- updated_at timestamptz NOT NULL DEFAULT now(),
21
- PRIMARY KEY (project, community_id)
22
- );
23
-
24
- CREATE INDEX IF NOT EXISTS idx_communities_size ON construct_entity_communities(project, size DESC);
@@ -1,30 +0,0 @@
1
- -- Tag attribution and migration tracking for construct_documents.
2
- -- The construct_documents.tags JSONB column already exists in 001_init.sql.
3
- -- This migration adds GIN indexing, attribution history, and migration audit.
4
-
5
- create index if not exists construct_documents_tags_gin_idx
6
- on construct_documents using gin (tags jsonb_path_ops);
7
-
8
- create table if not exists construct_tag_attribution (
9
- document_id text not null references construct_documents(id) on delete cascade,
10
- tag text not null,
11
- source text not null, -- 'agent:classifier' | 'user' | 'curator'
12
- confidence numeric,
13
- applied_at timestamptz not null default now(),
14
- migrated_from text,
15
- primary key (document_id, tag, source)
16
- );
17
- create index if not exists construct_tag_attribution_tag_idx on construct_tag_attribution (tag);
18
-
19
- create table if not exists construct_tag_migrations (
20
- id text primary key,
21
- from_tag text not null,
22
- to_tag text,
23
- executed_at timestamptz not null default now(),
24
- executed_by text not null,
25
- reason text not null,
26
- doc_count_before integer not null,
27
- doc_count_after integer,
28
- reversible boolean not null default true,
29
- rolled_back_at timestamptz
30
- );
@@ -1,24 +0,0 @@
1
- -- db/schema/008_skill_usage.sql — Per-skill invocation log.
2
- --
3
- -- Backs `construct skills usage/orphans/hot`. The local
4
- -- JSONL path (~/.cx/skill-calls.jsonl) is primary in solo mode; this table is
5
- -- primary in team/enterprise mode. One-time backfill via
6
- -- `construct skills backfill-postgres`.
7
-
8
- create table if not exists construct_skill_invocations (
9
- id bigserial primary key,
10
- ts timestamptz not null,
11
- skill_id text not null,
12
- source text not null, -- 'mcp' | 'role-preload' | 'prompt-composer' | …
13
- agent_id text,
14
- session_id text,
15
- caller_context text,
16
- latency_ms integer,
17
- tokens_returned integer
18
- );
19
-
20
- create index if not exists construct_skill_invocations_skill_ts_idx
21
- on construct_skill_invocations (skill_id, ts desc);
22
-
23
- create index if not exists construct_skill_invocations_session_idx
24
- on construct_skill_invocations (session_id);
@@ -1,14 +0,0 @@
1
- -- Scheduler job state for team and enterprise deployment modes.
2
- -- Solo mode uses native trigger files instead of this table.
3
- -- The lock_holder column stores a short identifier for the process/host
4
- -- that currently holds the advisory lock for a running job.
5
-
6
- create table if not exists construct_scheduled_jobs (
7
- id text primary key,
8
- schedule text not null,
9
- last_run_at timestamptz,
10
- last_run_status text,
11
- lock_holder text,
12
- lock_acquired_at timestamptz,
13
- created_at timestamptz not null default now()
14
- );
@@ -1,51 +0,0 @@
1
- -- db/schema/010_cx_scores.sql — Per-trace quality score log + skill quality correlation view.
2
- --
3
- -- Backs `construct skills correlate-quality`. Producer: lib/mcp/tools/telemetry.mjs
4
- -- cxScore() writes a row here when CONSTRUCT_DB_URL (or DATABASE_URL) is set.
5
- -- Local JSONL/observation paths remain primary in solo mode; this table is
6
- -- primary in team/enterprise mode where multiple agents share a session
7
- -- timeline and the dashboard reads aggregate correlations.
8
-
9
- create table if not exists construct_cx_scores (
10
- id bigserial primary key,
11
- ts timestamptz not null,
12
- trace_id text not null,
13
- session_id text,
14
- agent_id text,
15
- name text not null default 'quality',
16
- value numeric not null,
17
- comment text
18
- );
19
-
20
- create index if not exists construct_cx_scores_trace_idx
21
- on construct_cx_scores (trace_id);
22
-
23
- create index if not exists construct_cx_scores_session_ts_idx
24
- on construct_cx_scores (session_id, ts desc);
25
-
26
- create index if not exists construct_cx_scores_agent_ts_idx
27
- on construct_cx_scores (agent_id, ts desc);
28
-
29
- -- Skill quality correlation view: joins skill invocations and quality
30
- -- scores by session_id so a reader can ask "for sessions where skill X was
31
- -- invoked, what's the median / mean / p10 quality score?" The view is a
32
- -- simple aggregate over the past 90 days; queries that need a longer
33
- -- window can pull straight from the underlying tables. Materialised view
34
- -- not used here because the correlation surface is small and the freshness
35
- -- expectation is "current session" not "yesterday's batch."
36
-
37
- create or replace view construct_skill_quality_correlation as
38
- select
39
- inv.skill_id,
40
- count(distinct inv.session_id) as sessions,
41
- count(distinct sc.id) as score_count,
42
- round(avg(sc.value)::numeric, 3) as mean_score,
43
- round((percentile_cont(0.5) within group (order by sc.value))::numeric, 3) as median_score,
44
- round((percentile_cont(0.10) within group (order by sc.value))::numeric, 3) as p10_score,
45
- round((percentile_cont(0.90) within group (order by sc.value))::numeric, 3) as p90_score,
46
- max(sc.ts) as last_scored_at
47
- from construct_skill_invocations inv
48
- inner join construct_cx_scores sc on sc.session_id = inv.session_id
49
- where inv.ts > now() - interval '90 days'
50
- and sc.ts > now() - interval '90 days'
51
- group by inv.skill_id;